docs: audit ARCHITECTURE.md, ABSTRACTIONS.md, GETTING-STARTED.md — remove stale theme/tab/favorites refs
- Remove Theme System sections (removed in ADR-0013) - Delete stale THEMING.md (283 lines documenting removed system) - Remove Favorites from sidebar (removed in codebase) - Remove Closed Tab History section (single note model, ADR-0003) - Fix commands.rs → commands/ (split into modules) - Remove stale config/relations.md and config/semantic-properties.md refs - Fix protected folders list (remove _themes/, theme/) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@ These frontmatter field names have special meaning in Laputa's UI:
|
||||
|
||||
| Field | Meaning | UI behavior |
|
||||
|---|---|---|
|
||||
| `title:` | Human-readable title (synced with filename) | Tab label, breadcrumb, sidebar. Filename = `slugify(title).md` |
|
||||
| `title:` | Human-readable title (synced with filename) | Breadcrumb, sidebar. Filename = `slugify(title).md` |
|
||||
| `type:` | Entity type (Project, Person, Quarter…) | Type chip in note list + sidebar grouping |
|
||||
| `status:` | Lifecycle stage (active, done, blocked…) | Colored chip in note list + editor header |
|
||||
| `url:` | External link | Clickable link chip in editor header |
|
||||
@@ -25,7 +25,7 @@ These frontmatter field names have special meaning in Laputa's UI:
|
||||
| `Belongs to:` | Parent relationship | Relationship chip in Properties panel |
|
||||
| `Related to:` | Lateral relationship | Relationship chip in Properties panel |
|
||||
|
||||
The list of default-shown relationships and semantic property rendering rules can be customized via `config/relations.md` and `config/semantic-properties.md` in the vault.
|
||||
Relationship fields are detected dynamically — any frontmatter field containing `[[wikilink]]` values is treated as a relationship (see [ADR-0010](adr/0010-dynamic-wikilink-relationship-detection.md)).
|
||||
|
||||
### System Properties (underscore convention)
|
||||
|
||||
@@ -72,7 +72,6 @@ classDiagram
|
||||
+Record~string,string[]~ relationships
|
||||
+String[] outgoingLinks
|
||||
+String? status
|
||||
+String? owner
|
||||
+Number? modifiedAt
|
||||
+Number? createdAt
|
||||
+Number wordCount
|
||||
@@ -121,9 +120,8 @@ interface VaultEntry {
|
||||
relationships: Record<string, string[]> // All frontmatter fields containing wikilinks
|
||||
outgoingLinks: string[] // All [[wikilinks]] found in note body
|
||||
status: string | null // Active, Done, Paused, Archived, Dropped
|
||||
owner: string | null // Person responsible
|
||||
cadence: string | null // Update frequency: Weekly, Monthly, etc.
|
||||
modifiedAt: number | null // Unix timestamp (seconds)
|
||||
// Note: owner and cadence are now in the generic `properties` map
|
||||
createdAt: number | null // Unix timestamp (seconds)
|
||||
fileSize: number
|
||||
wordCount: number | null // Body word count (excludes frontmatter)
|
||||
@@ -149,8 +147,7 @@ Type is determined **purely** from the `type:` frontmatter field — it is never
|
||||
├── some-topic.md ← type: Topic
|
||||
├── ...
|
||||
├── type/ ← type definition documents
|
||||
├── config/ ← meta-configuration files (agents.md, etc.)
|
||||
└── theme/ ← vault-based themes (legacy location)
|
||||
└── config/ ← meta-configuration files (agents.md, etc.)
|
||||
```
|
||||
|
||||
New notes are created at the vault root: `{vault}/{slug}.md`. Changing a note's type only requires updating the `type:` field in frontmatter — the file does not move. The `type/` folder exists solely for type definition documents, and `config/` for configuration files.
|
||||
@@ -193,10 +190,8 @@ Standard YAML frontmatter between `---` delimiters:
|
||||
```yaml
|
||||
---
|
||||
title: Write Weekly Essays
|
||||
is_a: Procedure
|
||||
type: Procedure
|
||||
status: Active
|
||||
owner: Luca Rossi
|
||||
cadence: Weekly
|
||||
belongs_to:
|
||||
- "[[grow-newsletter]]"
|
||||
related_to:
|
||||
@@ -270,7 +265,7 @@ type SidebarSelection =
|
||||
|
||||
1. Validates the path exists and is a directory
|
||||
2. Scans root-level `.md` files (non-recursive)
|
||||
3. Recursively scans protected folders: `type/`, `config/`, `attachments/`, `_themes/`, `theme/`
|
||||
3. Recursively scans protected folders: `type/`, `config/`, `attachments/`
|
||||
4. Files in non-protected subfolders are **not indexed** (flat vault enforcement)
|
||||
5. For each `.md` file, calls `parse_md_file()`:
|
||||
- Reads content with `fs::read_to_string()`
|
||||
@@ -373,7 +368,7 @@ interface PulseCommit {
|
||||
|
||||
### Frontend Integration
|
||||
|
||||
- **Modified file badges**: Orange dots in sidebar and tab bar
|
||||
- **Modified file badges**: Orange dots in sidebar
|
||||
- **Diff view**: Toggle in breadcrumb bar → shows unified diff
|
||||
- **Git history**: Shown in Inspector panel for active note
|
||||
- **Commit dialog**: Triggered from sidebar or Cmd+K
|
||||
@@ -443,58 +438,13 @@ Wikilink resolution (`resolveEntry` in `src/utils/wikilink.ts`) uses multi-pass
|
||||
|
||||
Toggle via Cmd+K → "Raw Editor" or breadcrumb bar button. Uses CodeMirror 6 (`useCodeMirror` hook) to edit the raw markdown + frontmatter directly. Changes saved via the same `save_note_content` command.
|
||||
|
||||
## Theme System
|
||||
## Styling
|
||||
|
||||
See [THEMING.md](./THEMING.md) for the full theme system documentation.
|
||||
The app uses a single light theme — the vault-based theming system was removed (see [ADR-0013](adr/0013-remove-theming-system.md)). Styling is defined in two layers:
|
||||
|
||||
### Overview
|
||||
|
||||
Two-layer theming:
|
||||
1. **Global CSS variables** (`src/index.css`): App-wide colors via `:root`, bridged to Tailwind v4
|
||||
2. **Editor theme** (`src/theme.json`): BlockNote typography, flattened to CSS vars by `useEditorTheme`
|
||||
|
||||
### Vault-Based Themes
|
||||
|
||||
Themes are markdown notes in `theme/` with `type: Theme` frontmatter. Each property becomes a CSS variable with `--` prefix.
|
||||
|
||||
```yaml
|
||||
---
|
||||
type: Theme
|
||||
Description: Light theme with warm, paper-like tones
|
||||
background: "#FFFFFF"
|
||||
foreground: "#37352F"
|
||||
accent-blue: "#155DFF"
|
||||
editor-font-size: 16
|
||||
editor-line-height: 1.5
|
||||
---
|
||||
```
|
||||
|
||||
### ThemeManager
|
||||
|
||||
`useThemeManager` hook manages the theme lifecycle:
|
||||
|
||||
```typescript
|
||||
interface ThemeManager {
|
||||
themes: ThemeFile[]
|
||||
activeThemeId: string | null
|
||||
activeTheme: ThemeFile | null
|
||||
isDark: boolean
|
||||
switchTheme(themeId: string): Promise<void>
|
||||
createTheme(name?: string): Promise<string>
|
||||
reloadThemes(): Promise<void>
|
||||
updateThemeProperty(key: string, value: string): Promise<void>
|
||||
}
|
||||
```
|
||||
|
||||
- Detects dark backgrounds via luminance calculation → sets `color-scheme` and `data-theme-mode`
|
||||
- Live preview: re-applies when active theme note is saved
|
||||
- Three built-in themes: Default (light), Dark (deep navy), Minimal (high contrast)
|
||||
- Legacy JSON themes (`_themes/*.json`) supported for backward compatibility
|
||||
|
||||
### Theme Property Editor
|
||||
|
||||
`ThemePropertyEditor` component provides an interactive UI for editing theme properties. Uses `themeSchema.ts` to determine input types (color picker, number slider, text field) based on property names and values.
|
||||
|
||||
## Inspector Abstraction
|
||||
|
||||
The Inspector panel (`src/components/Inspector.tsx`) is composed of sub-panels:
|
||||
@@ -510,10 +460,6 @@ The Inspector panel (`src/components/Inspector.tsx`) is composed of sub-panels:
|
||||
|
||||
4. **GitHistoryPanel**: Shows recent commits from file history with relative timestamps.
|
||||
|
||||
## Closed Tab History
|
||||
|
||||
`useClosedTabHistory` hook (`src/hooks/useClosedTabHistory.ts`) provides a LIFO stack for closed tab entries, used by `useTabManagement` to support Cmd+Shift+T reopen. Each entry stores the note's path, tab index, and full `VaultEntry`. The stack is in-memory only (resets on restart), capped at 20 entries, and deduplicates by path.
|
||||
|
||||
## Search
|
||||
|
||||
### Search
|
||||
|
||||
@@ -114,11 +114,11 @@ flowchart TD
|
||||
WS["WelcomeScreen\n(onboarding)"]
|
||||
SB["Sidebar\n(navigation + filters + types)"]
|
||||
NL["NoteList / PulseView\n(filtered list / activity)"]
|
||||
ED["Editor\n(BlockNote + tabs + diff + raw)"]
|
||||
ED["Editor\n(BlockNote + diff + raw)"]
|
||||
IN["Inspector\n(metadata + relationships)"]
|
||||
AIC["AIChatPanel\n(API-based chat)"]
|
||||
AIP["AiPanel\n(Claude CLI agent + tools)"]
|
||||
SP["SearchPanel\n(keyword/semantic/hybrid)"]
|
||||
SP["SearchPanel\n(keyword search)"]
|
||||
ST["StatusBar\n(vault picker + sync + version)"]
|
||||
CP["CommandPalette\n(Cmd+K launcher)"]
|
||||
|
||||
@@ -132,7 +132,7 @@ flowchart TD
|
||||
FM["frontmatter/"]
|
||||
GIT["git/"]
|
||||
GH["github/"]
|
||||
THEME["theme/"]
|
||||
SETTINGS["settings.rs"]
|
||||
SEARCH["search.rs"]
|
||||
CLI["claude_cli.rs"]
|
||||
end
|
||||
@@ -160,10 +160,10 @@ flowchart TD
|
||||
│Sidebar │ Note List │ Editor │ Inspector │
|
||||
│(250px) │ (300px) │ (flex-1) │ (280px) │
|
||||
│ │ OR │ │ OR │
|
||||
│ All │ Pulse View │ [Tab Bar] │ AI Chat │
|
||||
│ Favs │ │ [Breadcrumb Bar] │ OR │
|
||||
│ Changes│ [Search] │ │ AI Agent │
|
||||
│ Pulse │ [Sort/Filt] │ # My Note │ │
|
||||
│ All │ Pulse View │ [Breadcrumb Bar] │ AI Chat │
|
||||
│ Changes│ │ │ OR │
|
||||
│ Pulse │ [Search] │ # My Note │ AI Agent │
|
||||
│ Inbox │ [Sort/Filt] │ │ │
|
||||
│ │ │ │ Context │
|
||||
│Projects│ Note 1 │ Content here... │ Messages │
|
||||
│Experim.│ Note 2 │ (BlockNote or Raw) │ Actions │
|
||||
@@ -176,9 +176,9 @@ flowchart TD
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- **Sidebar** (150-400px, resizable): Top-level filters (All Notes, Favorites, Changes, Pulse) and collapsible type-based section groups. Each type can have a custom icon, color, sort, and visibility set via its type document in `type/`.
|
||||
- **Sidebar** (150-400px, resizable): Top-level filters (All Notes, Changes, Pulse) and collapsible type-based section groups. Each type can have a custom icon, color, sort, and visibility set via its type document in `type/`.
|
||||
- **Note List / Pulse View** (200-500px, resizable): When a section group or filter is selected, shows filtered notes with snippets, modified dates, and status indicators. When Pulse filter is active, shows `PulseView` — a chronological git activity feed grouped by day.
|
||||
- **Editor** (flex, fills remaining space): Tab bar with modified dots, breadcrumb bar with word count, BlockNote rich text editor with wikilink support. Can toggle to diff view (modified files) or raw CodeMirror view. Decomposed into `Editor` (orchestrator), `EditorContent`, `EditorRightPanel`, `SingleEditorView`, with hooks `useDiffMode`, `useEditorFocus`, `useEditorSave`, `useRawMode`.
|
||||
- **Editor** (flex, fills remaining space): Single note open at a time (no tabs — see ADR-0003). Breadcrumb bar with word count, BlockNote rich text editor with wikilink support. Can toggle to diff view (modified files) or raw CodeMirror view. Decomposed into `Editor` (orchestrator), `EditorContent`, `EditorRightPanel`, `SingleEditorView`, with hooks `useDiffMode`, `useEditorFocus`, `useEditorSave`, `useRawMode`. Navigation history (Cmd+[/]) replaces tabs.
|
||||
- **Inspector / AI Chat / AI Agent** (200-500px or 40px collapsed): Toggles between Inspector (frontmatter, relationships, instances, backlinks, git history), AI Chat panel (API-based), and AI Agent panel (Claude CLI subprocess with tool execution). The Sparkle icon in the breadcrumb bar toggles between them. When viewing a Type note, the Inspector shows an **Instances** section listing all notes of that type (sorted by modified_at desc, capped at 50).
|
||||
|
||||
Panels are separated by `ResizeHandle` components that support drag-to-resize.
|
||||
@@ -413,24 +413,13 @@ flowchart TD
|
||||
G --> H([VaultEntry list ready])
|
||||
```
|
||||
|
||||
## Theme System
|
||||
## Styling
|
||||
|
||||
See [THEMING.md](./THEMING.md) for the full theme system documentation.
|
||||
|
||||
### Two-Layer Architecture
|
||||
The app uses a single light theme with no user-configurable theming (see [ADR-0013](adr/0013-remove-theming-system.md)).
|
||||
|
||||
1. **Global CSS variables** (`src/index.css`): App-wide colors, borders, backgrounds. Bridged to Tailwind v4 via `@theme inline`.
|
||||
2. **Editor theme** (`src/theme.json`): BlockNote-specific typography. Flattened to CSS vars by `useEditorTheme`.
|
||||
|
||||
### Vault-Based Themes
|
||||
|
||||
Themes are markdown notes in the `theme/` folder with `type: Theme` frontmatter (`Is A: Theme` accepted as legacy alias). Each frontmatter property becomes a CSS variable. Managed by `useThemeManager` hook and the `src-tauri/src/theme/` Rust module (create, seed, defaults).
|
||||
|
||||
- **Vault settings**: `.laputa/settings.json` stores the active theme reference
|
||||
- **Legacy support**: `_themes/*.json` files still supported for backward compatibility
|
||||
- **Built-in themes**: Default (light), Dark, Minimal — auto-seeded on vault open
|
||||
- **Live preview**: Re-applies when the active theme note is saved
|
||||
|
||||
## Vault Management
|
||||
|
||||
### Vault List
|
||||
@@ -444,14 +433,14 @@ Persisted at `~/.config/com.laputa.app/vaults.json`:
|
||||
}
|
||||
```
|
||||
|
||||
Managed by `useVaultSwitcher` hook. Switching vaults closes all tabs and resets sidebar.
|
||||
Managed by `useVaultSwitcher` hook. Switching vaults resets sidebar and clears the active note.
|
||||
|
||||
### Vault Config
|
||||
|
||||
Per-vault UI settings stored in `ui.config.md` at vault root (YAML frontmatter in a markdown note):
|
||||
- `zoom`: Float zoom level (0.8–1.5)
|
||||
- `view_mode`: "all" | "editor-list" | "editor-only"
|
||||
- `editor_mode`: "raw" | "preview" (persists across tab switches and sessions)
|
||||
- `editor_mode`: "raw" | "preview" (persists across note switches and sessions)
|
||||
- `tag_colors`, `status_colors`: Custom color overrides
|
||||
- `property_display_modes`: Property display preferences
|
||||
|
||||
@@ -501,7 +490,7 @@ sequenceDiagram
|
||||
participant MCP as MCP Server
|
||||
participant U as User
|
||||
|
||||
T->>T: run_startup_tasks()<br/>(purge trash, seed themes, register MCP)
|
||||
T->>T: run_startup_tasks()<br/>(purge trash, register MCP)
|
||||
T->>MCP: spawn_ws_bridge() — ports 9710 + 9711
|
||||
T->>A: App mounts
|
||||
|
||||
@@ -514,7 +503,6 @@ sequenceDiagram
|
||||
T-->>VL: VaultEntry[]
|
||||
VL->>T: invoke('get_modified_files')
|
||||
VL->>T: useMcpStatus — register if needed
|
||||
VL->>T: useThemeManager — load active theme
|
||||
VL-->>A: entries ready
|
||||
end
|
||||
|
||||
@@ -599,7 +587,6 @@ The vault backend (`src-tauri/src/vault/`) is split into focused submodules:
|
||||
| `frontmatter/` | YAML frontmatter read/write (`mod.rs`, `yaml.rs`, `ops.rs`) |
|
||||
| `git/` | Git operations (`commit.rs`, `status.rs`, `history.rs`, `conflict.rs`, `remote.rs`, `pulse.rs`) |
|
||||
| `github/` | GitHub OAuth + API (`auth.rs`, `api.rs`, `clone.rs`) |
|
||||
| `theme/` | Theme management (`mod.rs`, `create.rs`, `defaults.rs`, `seed.rs`) |
|
||||
| `search.rs` | Keyword search — walkdir-based vault file scan |
|
||||
| `claude_cli.rs` | Claude CLI subprocess spawning + NDJSON stream parsing |
|
||||
| `ai_chat.rs` | Direct Anthropic API client (non-streaming, for Tauri builds) |
|
||||
@@ -740,12 +727,12 @@ No Redux or global context. State lives in the root `App.tsx` and custom hooks:
|
||||
| `App.tsx` | `selection`, panel widths, dialog visibility, toast, view mode | UI state |
|
||||
| `useVaultLoader` | `entries`, `allContent`, `modifiedFiles` | Vault data |
|
||||
| `useNoteActions` | `tabs`, `activeTabPath` | Composes `useNoteCreation` + `useNoteRename` + `frontmatterOps` |
|
||||
| `useNoteCreation` | — (delegates to `useTabManagement`) | Note/type/daily-note creation with optimistic persistence |
|
||||
| `useNoteRename` | — (delegates to `useTabManagement`) | Note renaming with wikilink update |
|
||||
| `useNoteCreation` | — | Note/type/daily-note creation with optimistic persistence |
|
||||
| `useNoteRename` | — | Note renaming with wikilink update |
|
||||
| `frontmatterOps` | — (pure functions) | Frontmatter CRUD: key→VaultEntry mapping, mock/Tauri dispatch |
|
||||
| `useTabManagement` | Tab ordering, pinning, swapping, closed-tab history | Tab lifecycle |
|
||||
| `useTabManagement` | Navigation history, note switching | Note navigation lifecycle |
|
||||
| `useVaultSwitcher` | `vaultPath`, `extraVaults` | Vault switching |
|
||||
| `useThemeManager` | `themes`, `activeThemeId`, `isDark` | Theme state |
|
||||
| `useTheme` | Editor theme CSS vars | Editor typography theme |
|
||||
| `useAIChat` | `messages`, `isStreaming` | AI chat conversation |
|
||||
| `useAiAgent` | `messages`, `status`, tool actions | AI agent conversation |
|
||||
| `useAutoSync` | Sync interval, pull/push state | Git auto-sync |
|
||||
@@ -763,8 +750,7 @@ Data flows unidirectionally: `App` passes data and callbacks as props to child c
|
||||
| Cmd+P | Open quick open palette |
|
||||
| Cmd+N | Create new note |
|
||||
| Cmd+S | Save current note |
|
||||
| Cmd+W | Close active tab |
|
||||
| Cmd+Shift+T | Reopen last closed tab (LIFO history, up to 20) |
|
||||
| Cmd+[ / Cmd+] | Navigate back / forward (replaces tabs) |
|
||||
| Cmd+Z / Cmd+Shift+Z | Undo / Redo |
|
||||
| Cmd+1–9 | Switch to tab N |
|
||||
| Cmd+[ / Cmd+] | Navigate back / forward |
|
||||
|
||||
@@ -46,7 +46,7 @@ laputa-app/
|
||||
│ │ ├── NoteList.tsx # Second panel: filtered note list
|
||||
│ │ ├── NoteItem.tsx # Individual note item
|
||||
│ │ ├── PulseView.tsx # Git activity feed (replaces NoteList)
|
||||
│ │ ├── Editor.tsx # Third panel: tabs + editor orchestration
|
||||
│ │ ├── Editor.tsx # Third panel: editor orchestration
|
||||
│ │ ├── EditorContent.tsx # Editor content area
|
||||
│ │ ├── EditorRightPanel.tsx # Right panel toggle
|
||||
│ │ ├── editorSchema.tsx # BlockNote schema + wikilink type
|
||||
@@ -61,12 +61,11 @@ laputa-app/
|
||||
│ │ ├── SettingsPanel.tsx # App settings
|
||||
│ │ ├── StatusBar.tsx # Bottom bar: vault picker + sync
|
||||
│ │ ├── CommandPalette.tsx # Cmd+K command launcher
|
||||
│ │ ├── TabBar.tsx # Tab management
|
||||
│ │ ├── BreadcrumbBar.tsx # Breadcrumb + word count + actions
|
||||
│ │ ├── WelcomeScreen.tsx # Onboarding screen
|
||||
│ │ ├── GitHubVaultModal.tsx # GitHub vault clone/create
|
||||
│ │ ├── GitHubDeviceFlow.tsx # GitHub OAuth device flow
|
||||
│ │ ├── ThemePropertyEditor.tsx # Interactive theme editor
|
||||
│ │ ├── TitleField.tsx # Editable note title above editor
|
||||
│ │ ├── ConflictResolverModal.tsx # Git conflict resolution
|
||||
│ │ ├── CommitDialog.tsx # Git commit modal
|
||||
│ │ ├── CreateNoteDialog.tsx # New note modal
|
||||
@@ -87,7 +86,6 @@ laputa-app/
|
||||
│ │ ├── useNoteActions.ts # Composes creation + rename + frontmatter
|
||||
│ │ ├── useNoteCreation.ts # Note/type/daily-note creation
|
||||
│ │ ├── useNoteRename.ts # Note renaming + wikilink updates
|
||||
│ │ ├── useTabManagement.ts # Tab ordering + lifecycle
|
||||
│ │ ├── useAIChat.ts # AI chat state
|
||||
│ │ ├── useAiAgent.ts # AI agent state + tool tracking
|
||||
│ │ ├── useAiActivity.ts # MCP UI bridge listener
|
||||
@@ -95,7 +93,6 @@ laputa-app/
|
||||
│ │ ├── useConflictResolver.ts # Git conflict handling
|
||||
│ │ ├── useEditorSave.ts # Auto-save with debounce
|
||||
│ │ ├── useTheme.ts # Flatten theme.json → CSS vars
|
||||
│ │ ├── useThemeManager.ts # Vault theme lifecycle
|
||||
│ │ ├── useUnifiedSearch.ts # Keyword search
|
||||
│ │ ├── useNoteSearch.ts # Note search
|
||||
│ │ ├── useCommandRegistry.ts # Command palette registry
|
||||
@@ -116,7 +113,7 @@ laputa-app/
|
||||
│ │ ├── ai-chat.ts # Chat API client + token estimation
|
||||
│ │ ├── ai-context.ts # Context snapshot builder
|
||||
│ │ ├── noteListHelpers.ts # Sorting, filtering, date formatting
|
||||
│ │ ├── themeSchema.ts # Theme editor schema builder
|
||||
│ │ ├── wikilink.ts # Wikilink resolution
|
||||
│ │ ├── configMigration.ts # localStorage → vault config migration
|
||||
│ │ ├── iconRegistry.ts # Phosphor icon registry
|
||||
│ │ ├── propertyTypes.ts # Property type definitions
|
||||
@@ -138,7 +135,7 @@ laputa-app/
|
||||
│ ├── src/
|
||||
│ │ ├── main.rs # Entry point (calls lib::run())
|
||||
│ │ ├── lib.rs # Tauri setup + command registration (61 commands)
|
||||
│ │ ├── commands.rs # All Tauri command handlers
|
||||
│ │ ├── commands/ # Tauri command handlers (split into modules)
|
||||
│ │ ├── vault/ # Vault module
|
||||
│ │ │ ├── mod.rs # Core types, parse_md_file, scan_vault
|
||||
│ │ │ ├── cache.rs # Git-based incremental caching
|
||||
@@ -155,8 +152,7 @@ laputa-app/
|
||||
│ │ │ ├── conflict.rs, remote.rs, pulse.rs
|
||||
│ │ ├── github/ # GitHub module
|
||||
│ │ │ ├── mod.rs, auth.rs, api.rs, clone.rs
|
||||
│ │ ├── theme/ # Theme module
|
||||
│ │ │ ├── mod.rs, create.rs, defaults.rs, seed.rs
|
||||
│ │ ├── telemetry.rs # Sentry init + path scrubber
|
||||
│ │ ├── search.rs # Keyword search (walkdir-based)
|
||||
│ │ ├── claude_cli.rs # Claude CLI subprocess management
|
||||
│ │ ├── ai_chat.rs # Direct Anthropic API client
|
||||
@@ -197,7 +193,7 @@ laputa-app/
|
||||
|------|---------------|
|
||||
| `src/App.tsx` | Root component. Shows the 4-panel layout, state flow, and how all features connect. |
|
||||
| `src/types.ts` | All shared TypeScript types. Read this first to understand the data model. |
|
||||
| `src-tauri/src/commands.rs` | All 61 Tauri command handlers. This is the frontend-backend API surface. |
|
||||
| `src-tauri/src/commands/` | Tauri command handlers (split into modules). This is the frontend-backend API surface. |
|
||||
| `src-tauri/src/lib.rs` | Tauri setup, command registration, startup tasks, WebSocket bridge lifecycle. |
|
||||
|
||||
### Data layer
|
||||
@@ -225,7 +221,7 @@ laputa-app/
|
||||
|
||||
| File | Why it matters |
|
||||
|------|---------------|
|
||||
| `src/components/Editor.tsx` | BlockNote setup, tab bar, breadcrumb bar, diff/raw toggle. |
|
||||
| `src/components/Editor.tsx` | BlockNote setup, breadcrumb bar, diff/raw toggle. |
|
||||
| `src/components/editorSchema.tsx` | Custom wikilink inline content type definition. |
|
||||
| `src/utils/wikilinks.ts` | Wikilink preprocessing pipeline (markdown ↔ BlockNote). |
|
||||
| `src/components/RawEditorView.tsx` | CodeMirror 6 raw markdown editor. |
|
||||
@@ -239,14 +235,12 @@ laputa-app/
|
||||
| `src/hooks/useAiAgent.ts` | Agent state: messages, streaming, tool tracking, file detection. |
|
||||
| `src/utils/ai-context.ts` | Context snapshot builder for AI conversations. |
|
||||
|
||||
### Styling & Themes
|
||||
### Styling
|
||||
|
||||
| File | Why it matters |
|
||||
|------|---------------|
|
||||
| `src/index.css` | All CSS custom properties. The design token source of truth. |
|
||||
| `src/theme.json` | Editor-specific theme (fonts, headings, lists, code blocks). |
|
||||
| `src/hooks/useThemeManager.ts` | Vault theme lifecycle (switch, create, apply, live preview). |
|
||||
| `docs/THEMING.md` | Full theme system documentation. |
|
||||
|
||||
### Settings & Config
|
||||
|
||||
@@ -274,7 +268,7 @@ This lives in `useVaultLoader.ts` and `useNoteActions.ts`. Components never call
|
||||
|
||||
### Props-Down, Callbacks-Up
|
||||
|
||||
No global state management (no Redux, no Context). `App.tsx` owns the state and passes it down as props. Child-to-parent communication uses callback props (`onSelectNote`, `onCloseTab`, etc.).
|
||||
No global state management (no Redux, no Context). `App.tsx` owns the state and passes it down as props. Child-to-parent communication uses callback props (`onSelectNote`, etc.).
|
||||
|
||||
### Discriminated Unions for Selection State
|
||||
|
||||
@@ -317,7 +311,7 @@ BASE_URL="http://localhost:5173" npx playwright test tests/smoke/<slug>.spec.ts
|
||||
### Add a new Tauri command
|
||||
|
||||
1. Write the Rust function in the appropriate module (`vault/`, `git/`, etc.)
|
||||
2. Add a command handler in `commands.rs`
|
||||
2. Add a command handler in `commands/`
|
||||
3. Register it in the `generate_handler![]` macro in `lib.rs`
|
||||
4. Call it from the frontend via `invoke()` in the appropriate hook
|
||||
5. Add a mock handler in `mock-tauri.ts`
|
||||
@@ -342,11 +336,10 @@ BASE_URL="http://localhost:5173" npx playwright test tests/smoke/<slug>.spec.ts
|
||||
2. Add a corresponding menu bar item in `menu.rs` for discoverability
|
||||
3. If it has a keyboard shortcut, register it in `useAppKeyboard.ts`
|
||||
|
||||
### Add or modify a theme
|
||||
### Modify styling
|
||||
|
||||
1. **Vault-based** (preferred): Create/edit a markdown note in `theme/` with `type: Theme` frontmatter
|
||||
2. **Programmatic**: Edit defaults in `src-tauri/src/theme/defaults.rs`
|
||||
3. See `docs/THEMING.md` for the full property reference
|
||||
1. **Global CSS variables**: Edit `src/index.css`
|
||||
2. **Editor typography**: Edit `src/theme.json`
|
||||
|
||||
### Work with the AI agent
|
||||
|
||||
|
||||
283
docs/THEMING.md
283
docs/THEMING.md
@@ -1,283 +0,0 @@
|
||||
# Theming
|
||||
|
||||
How the visual theme system works in Laputa.
|
||||
|
||||
## Overview
|
||||
|
||||
Laputa has two layers of theming:
|
||||
|
||||
1. **Global CSS variables** (`src/index.css`): App-wide colors, borders, backgrounds — used by all components via Tailwind and direct CSS.
|
||||
2. **Editor theme** (`src/theme.json`): Editor-specific typography and styling — converted to CSS variables by `useEditorTheme` and applied to the BlockNote container.
|
||||
|
||||
Currently the app is **light mode only** (dark mode was removed for simplicity).
|
||||
|
||||
## Layer 1: Global CSS Variables
|
||||
|
||||
Defined in `src/index.css` under `:root`:
|
||||
|
||||
### Color Palette
|
||||
|
||||
```css
|
||||
/* Primary brand */
|
||||
--primary: #155DFF; /* Blue — links, accents, active states */
|
||||
--primary-foreground: #FFFFFF;
|
||||
|
||||
/* Text hierarchy */
|
||||
--text-primary: #37352F; /* Main text (Notion-like dark gray) */
|
||||
--text-secondary: #787774; /* Secondary text */
|
||||
--text-muted: #B4B4B4; /* Muted/placeholder text */
|
||||
--text-heading: #37352F; /* Headings */
|
||||
|
||||
/* Backgrounds */
|
||||
--bg-primary: #FFFFFF; /* Main content area */
|
||||
--bg-sidebar: #F7F6F3; /* Sidebar background */
|
||||
--bg-hover: #EBEBEA; /* Hover state */
|
||||
--bg-hover-subtle: #F0F0EF; /* Subtle hover (code blocks) */
|
||||
--bg-selected: #E8F4FE; /* Selected state (blue tint) */
|
||||
|
||||
/* Borders */
|
||||
--border-primary: #E9E9E7; /* Standard borders */
|
||||
|
||||
/* Accent colors */
|
||||
--accent-blue: #155DFF;
|
||||
--accent-green: #00B38B;
|
||||
--accent-orange: #D9730D;
|
||||
--accent-red: #E03E3E;
|
||||
--accent-purple: #A932FF;
|
||||
--accent-yellow: #F0B100;
|
||||
|
||||
/* Light accent backgrounds (for badges/pills) */
|
||||
--accent-blue-light: #155DFF14;
|
||||
--accent-green-light: #00B38B14;
|
||||
--accent-purple-light: #A932FF14;
|
||||
--accent-red-light: #E03E3E14;
|
||||
--accent-yellow-light: #F0B10014;
|
||||
```
|
||||
|
||||
### shadcn/ui Variables
|
||||
|
||||
The app uses [shadcn/ui](https://ui.shadcn.com/) components, which require their own CSS variable naming convention:
|
||||
|
||||
```css
|
||||
--background: #FFFFFF;
|
||||
--foreground: #37352F;
|
||||
--card: #FFFFFF;
|
||||
--card-foreground: #37352F;
|
||||
--primary: #155DFF;
|
||||
--secondary: #EBEBEA;
|
||||
--muted: #F0F0EF;
|
||||
--muted-foreground: #787774;
|
||||
--accent: #EBEBEA;
|
||||
--destructive: #E03E3E;
|
||||
--border: #E9E9E7;
|
||||
--ring: #155DFF;
|
||||
--sidebar: #F7F6F3;
|
||||
```
|
||||
|
||||
### Tailwind v4 Integration
|
||||
|
||||
The `@theme inline` block in `index.css` bridges CSS variables to Tailwind's color system:
|
||||
|
||||
```css
|
||||
@theme inline {
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-primary: var(--primary);
|
||||
/* ... etc */
|
||||
}
|
||||
```
|
||||
|
||||
This enables Tailwind classes like `bg-background`, `text-primary`, `border-border`, etc.
|
||||
|
||||
## Layer 2: Editor Theme (theme.json)
|
||||
|
||||
`src/theme.json` controls the BlockNote editor's typography and element styling. It's a nested JSON object with the following structure:
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"editor": {
|
||||
"fontFamily": "'Inter', -apple-system, BlinkMacSystemFont, sans-serif",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 1.5,
|
||||
"maxWidth": 720,
|
||||
"paddingHorizontal": 40,
|
||||
"paddingVertical": 20,
|
||||
"paragraphSpacing": 8
|
||||
},
|
||||
"headings": {
|
||||
"h1": { "fontSize": 32, "fontWeight": 700, "lineHeight": 1.2, "marginTop": 32, "marginBottom": 12, "color": "var(--text-heading)", "letterSpacing": -0.5 },
|
||||
"h2": { "fontSize": 27, "fontWeight": 600, "lineHeight": 1.4, "marginTop": 28, "marginBottom": 10 },
|
||||
"h3": { "fontSize": 20, "fontWeight": 600, "lineHeight": 1.4, "marginTop": 24, "marginBottom": 8 },
|
||||
"h4": { "fontSize": 20, "fontWeight": 600, "lineHeight": 1.4, "marginTop": 20, "marginBottom": 6 }
|
||||
},
|
||||
"lists": {
|
||||
"bulletSymbol": "\u2022",
|
||||
"bulletSize": 28,
|
||||
"bulletColor": "#177bfd",
|
||||
"indentSize": 24,
|
||||
"itemSpacing": 4,
|
||||
"paddingLeft": 8,
|
||||
"nestedBulletSymbols": ["\u2022", "\u25e6", "\u25aa"],
|
||||
"bulletGap": 6
|
||||
},
|
||||
"checkboxes": {
|
||||
"size": 18,
|
||||
"borderRadius": 3,
|
||||
"checkedColor": "var(--accent-blue)",
|
||||
"uncheckedBorderColor": "var(--text-muted)",
|
||||
"gap": 8
|
||||
},
|
||||
"inlineStyles": {
|
||||
"bold": { "fontWeight": 700, "color": "var(--text-primary)" },
|
||||
"italic": { "fontStyle": "italic" },
|
||||
"strikethrough": { "color": "var(--text-tertiary)", "textDecoration": "line-through" },
|
||||
"code": { "fontFamily": "'SF Mono', 'Fira Code', monospace", "fontSize": 14, "backgroundColor": "var(--bg-hover-subtle)", "paddingHorizontal": 4, "paddingVertical": 2, "borderRadius": 3 },
|
||||
"link": { "color": "var(--accent-blue)", "textDecoration": "underline" },
|
||||
"wikilink": { "color": "var(--accent-blue)", "textDecoration": "none", "borderBottom": "1px dotted var(--accent-blue)", "cursor": "pointer" }
|
||||
},
|
||||
"codeBlocks": {
|
||||
"fontFamily": "'SF Mono', 'Fira Code', monospace",
|
||||
"fontSize": 13,
|
||||
"lineHeight": 1.5,
|
||||
"backgroundColor": "var(--bg-card)",
|
||||
"paddingHorizontal": 16,
|
||||
"paddingVertical": 12,
|
||||
"borderRadius": 6,
|
||||
"marginVertical": 12
|
||||
},
|
||||
"blockquote": {
|
||||
"borderLeftWidth": 3,
|
||||
"borderLeftColor": "var(--accent-blue)",
|
||||
"paddingLeft": 16,
|
||||
"marginVertical": 12,
|
||||
"color": "var(--text-secondary)",
|
||||
"fontStyle": "italic"
|
||||
},
|
||||
"table": {
|
||||
"borderColor": "var(--border-primary)",
|
||||
"headerBackground": "var(--bg-card)",
|
||||
"cellPaddingHorizontal": 12,
|
||||
"cellPaddingVertical": 8,
|
||||
"fontSize": 14
|
||||
},
|
||||
"horizontalRule": {
|
||||
"color": "var(--border-primary)",
|
||||
"marginVertical": 24,
|
||||
"thickness": 1
|
||||
},
|
||||
"colors": {
|
||||
"background": "var(--bg-primary)",
|
||||
"text": "var(--text-primary)",
|
||||
"textSecondary": "var(--text-secondary)",
|
||||
"textMuted": "var(--text-muted)",
|
||||
"heading": "var(--text-heading)",
|
||||
"accent": "var(--accent-blue)",
|
||||
"selection": "var(--bg-selected)",
|
||||
"cursor": "var(--text-primary)"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### How theme.json Becomes CSS
|
||||
|
||||
The `useEditorTheme` hook (`src/hooks/useTheme.ts`) flattens the nested structure into CSS custom properties:
|
||||
|
||||
```typescript
|
||||
function flattenTheme(obj, prefix = '--') {
|
||||
// Recursively flattens:
|
||||
// { editor: { fontSize: 16 } } → { '--editor-font-size': '16px' }
|
||||
// { headings: { h1: { fontSize: 32 } } } → { '--headings-h1-font-size': '32px' }
|
||||
}
|
||||
```
|
||||
|
||||
Key transformations:
|
||||
- **camelCase → kebab-case**: `fontSize` → `font-size`
|
||||
- **Numeric values get `px`**: `16` → `16px`
|
||||
- **Unitless exceptions**: `lineHeight`, `fontWeight`, `opacity` stay as plain numbers
|
||||
- **String values pass through**: `"var(--text-heading)"` → `var(--text-heading)`
|
||||
- **Arrays are skipped**: `nestedBulletSymbols` is ignored in CSS flattening
|
||||
|
||||
The resulting flat map is applied as inline styles on the BlockNote container:
|
||||
|
||||
```tsx
|
||||
<div className="editor__blocknote-container" style={cssVars as React.CSSProperties}>
|
||||
<BlockNoteView editor={editor} theme="light" />
|
||||
</div>
|
||||
```
|
||||
|
||||
### EditorTheme.css
|
||||
|
||||
`src/components/EditorTheme.css` contains CSS selectors that consume these variables to style BlockNote's internal elements (headings, lists, code blocks, etc.). This file uses selectors like `.bn-editor [data-content-type="heading"]` to target BlockNote's rendered DOM.
|
||||
|
||||
## How to Modify Styles
|
||||
|
||||
### Change a global color
|
||||
|
||||
Edit `src/index.css` — find the variable under `:root` and change its value:
|
||||
|
||||
```css
|
||||
/* Before */
|
||||
--primary: #155DFF;
|
||||
/* After */
|
||||
--primary: #FF5500;
|
||||
```
|
||||
|
||||
All components using `text-primary`, `bg-primary`, `var(--primary)`, etc. will update automatically.
|
||||
|
||||
### Change editor typography
|
||||
|
||||
Edit `src/theme.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"editor": {
|
||||
"fontSize": 18, // was 16
|
||||
"fontFamily": "'Georgia', serif" // was Inter
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `useEditorTheme` hook will automatically regenerate the CSS variables on next render.
|
||||
|
||||
### Change heading sizes
|
||||
|
||||
Edit the `headings` section in `src/theme.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"headings": {
|
||||
"h1": { "fontSize": 36 }, // was 32
|
||||
"h2": { "fontSize": 28 } // was 27
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Change wikilink appearance
|
||||
|
||||
Edit `inlineStyles.wikilink` in `src/theme.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"inlineStyles": {
|
||||
"wikilink": {
|
||||
"color": "var(--accent-purple)",
|
||||
"borderBottom": "2px solid var(--accent-purple)"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Add a new CSS variable
|
||||
|
||||
1. Add it to `:root` in `src/index.css`
|
||||
2. If it needs to be available in Tailwind, add a mapping in the `@theme inline` block
|
||||
3. Reference it anywhere as `var(--my-variable)` in CSS or `theme.json`
|
||||
|
||||
## Design Decisions
|
||||
|
||||
- **CSS variables over Tailwind config**: Colors are defined as CSS variables rather than Tailwind config values, because they need to be shared with non-Tailwind code (BlockNote, inline styles, theme.json).
|
||||
- **theme.json for editor only**: The editor theme is separate from global styles because it controls BlockNote-specific typography that doesn't apply to the rest of the app (sidebar, dialogs, etc.).
|
||||
- **No dark mode (for now)**: Dark mode was removed to simplify the initial build. The CSS variable architecture supports it — just add a `[data-theme="dark"]` or `@media (prefers-color-scheme: dark)` section in `index.css`.
|
||||
- **Inline styles for editor**: The editor theme is applied via inline styles (not a stylesheet) because the values come from JSON and are computed at runtime by the hook.
|
||||
Reference in New Issue
Block a user