From 1ba74d4b1d1d9b5b8a84d6a8470e742c4df18ef2 Mon Sep 17 00:00:00 2001 From: Test Date: Sat, 28 Mar 2026 11:03:01 +0100 Subject: [PATCH] =?UTF-8?q?docs:=20audit=20ARCHITECTURE.md,=20ABSTRACTIONS?= =?UTF-8?q?.md,=20GETTING-STARTED.md=20=E2=80=94=20remove=20stale=20theme/?= =?UTF-8?q?tab/favorites=20refs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- docs/ABSTRACTIONS.md | 72 ++-------- docs/ARCHITECTURE.md | 52 +++----- docs/GETTING-STARTED.md | 33 ++--- docs/THEMING.md | 283 ---------------------------------------- 4 files changed, 41 insertions(+), 399 deletions(-) delete mode 100644 docs/THEMING.md diff --git a/docs/ABSTRACTIONS.md b/docs/ABSTRACTIONS.md index 02943e2d..a2f1f282 100644 --- a/docs/ABSTRACTIONS.md +++ b/docs/ABSTRACTIONS.md @@ -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 // 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 - createTheme(name?: string): Promise - reloadThemes(): Promise - updateThemeProperty(key: string, value: string): Promise -} -``` - -- 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 diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index edccf2fb..df65e010 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -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()
(purge trash, seed themes, register MCP) + T->>T: run_startup_tasks()
(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 | diff --git a/docs/GETTING-STARTED.md b/docs/GETTING-STARTED.md index 9cd8777e..148c5d7a 100644 --- a/docs/GETTING-STARTED.md +++ b/docs/GETTING-STARTED.md @@ -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/.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/.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 diff --git a/docs/THEMING.md b/docs/THEMING.md deleted file mode 100644 index 784b7fee..00000000 --- a/docs/THEMING.md +++ /dev/null @@ -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 -
- -
-``` - -### 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.