Remove all vestiges of the abandoned Trash system: trashed/trashedAt fields
from types, frontmatter parsing, sidebar filtering, editor banners, inspector
components, mock data, and all related tests. Delete is already permanent via
useDeleteActions with a confirmation dialog. Notes with trashed:true in
existing vault frontmatter are now treated as normal notes (the flag is
ignored by the parser).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CheckCircle button in breadcrumb bar toggles _organized property
- Green filled icon when organized, gray outline when not
- Command palette: "Mark as Organized" / "Mark as Unorganized"
- _organized hidden from Properties panel
- Handler follows favorite toggle pattern with optimistic updates
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds FAVORITES sidebar section backed by _favorite and _favorite_index
frontmatter properties. Star button in breadcrumb bar toggles favorite
state. Drag-to-reorder updates _favorite_index on all affected notes.
Section auto-hides when empty.
ADR 0038 documents the decision to use frontmatter for portability.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Properties panel now detects frontmatter state (valid/empty/none/invalid):
- No frontmatter: shows "Initialize properties" button that adds type+title
- Invalid YAML: shows warning with "Fix in editor" button (toggles raw mode)
- Valid frontmatter: unchanged behavior
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reverts commits d263e43, ae2260c, 473239b, aaa4691, cd7a570, 3cd097d.
Removes PinnedPropertiesBar, usePinnedProperties hook, Rust backend
for _pinned_properties, and all related tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PinnedPropertiesBar: horizontal bar below title with icon + label +
editable value chips, overflow popover for hidden properties
- PinnedPropertyChip: inline-editable chip with status/relationship colors
- NoteListPinnedValues: compact value-only chips under note titles
- Pin/unpin context menu (right-click) in Properties panel with highlight
- Real-time sync: _pinned_properties changes propagate via frontmatterToEntryPatch
- Default pinned properties (status, belongs_to, related_to) for types without config
- Per-type config stored in type definition frontmatter as _pinned_properties
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the multi-tab model with single-note navigation. One note is
open at a time; clicking any note (sidebar, wikilink, relationship)
replaces the current note in the editor. Back/Forward history still
works via Cmd+[/].
Removed: TabBar component, useClosedTabHistory, tab reorder/close/
reopen logic, Cmd+W/Cmd+Shift+T shortcuts, Close Tab and Reopen
Closed Tab menu items, tabLayout utility, and all related tests.
Simplified: useTabManagement (single-note), useAppNavigation (no tab
lookup), useKeyboardNavigation (note nav only), useNoteCreation (no
close-tab cleanup), useDeleteActions (deselect instead of close tab),
Editor (no TabBar render), Rust menu (removed 2 menu items).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove all theme switching, creation, and management functionality
from the app — backend (Rust theme module, Tauri commands, menu items,
startup tasks, vault seeding), frontend (useThemeManager, ThemePropertyEditor,
themeSchema, command palette Appearance group, settings panel appearance
section, isDarkTheme prop chain), mock data, and all related tests.
Editor styling via theme.json/EditorTheme.css is preserved (not user theming).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When push is rejected due to remote having newer commits, the bottom bar
now shows "Pull required" (orange). Clicking it pulls then auto-pushes.
Conflicted notes show an inline banner with "Keep mine" / "Keep theirs"
buttons. A new "Pull from Remote" command is available in Cmd+K and the
Vault menu. Clicking the sync badge opens a popup showing branch name,
ahead/behind counts, and a Pull button.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
H1 headings are body content, not the title source of truth. Remove
useHeadingTitleSync hook and frontmatter-title-from-H1 logic in the
editor. Rust rename_note no longer modifies H1 in file content — only
updates the frontmatter title: field.
TitleField now uses optimistic UI: the new title displays immediately
after commit while the async file rename runs in background, preventing
UI freezes on slow filesystems or large vaults.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Every note can now have an optional emoji icon (frontmatter `icon` field).
The icon is displayed in the editor header, note list, search results,
and Quick Open. Includes command palette commands "Set Note Icon" and
"Remove Note Icon", plus full test coverage (Vitest + Playwright).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract scan_vault helpers (is_md_file, try_parse_md, scan_root_md_files, scan_protected_folders)
to eliminate deep nesting and reduce cyclomatic complexity. Break up large assertion blocks in tests.
Extract useEditorSetup and useRawModeWithFlush hooks from Editor component to reduce complexity.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Editor mode is now stored as a global preference in VaultConfig instead of
being tracked per-tab. Toggling raw mode persists across tab switches and
app restarts via the editor_mode field in config/ui.config.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a TitleField component between the breadcrumb bar and BlockNote
editor that serves as the primary title editing surface. The H1 block
inside BlockNote is hidden via CSS. The title field:
- Shows the note title in a prominent input field
- Displays the expected filename when it differs from the current one
- Triggers onTitleSync on blur/Enter to rename the file
- Responds to laputa:focus-editor selectTitle events for new notes
- Reverts on Escape or empty input
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When toggling from raw mode back to BlockNote, the editor now correctly
re-parses content from tab.content instead of using stale cached blocks.
Key changes:
- useEditorTabSwap: detect rawMode true→false transition, invalidate
block cache, and re-parse from tab.content. Added rawSwapPendingRef
guard to prevent a second effect run from re-caching stale blocks
before the deferred doSwap microtask runs.
- useRawMode: added onBeforeRawEnd callback to flush debounced raw
editor content synchronously before toggling off.
- Editor.tsx: wired rawLatestContentRef and handleBeforeRawEnd to
ensure the latest raw content reaches tab.content before the swap.
- RawEditorView: exposed latestContentRef so parent can read the
latest keystroke content without waiting for the 500ms debounce.
- EditorContent: threaded rawLatestContentRef through to RawEditorView.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies that searching "Writing" in Quick Open shows the exact title
match first, followed by prefix matches. Also adds Refactoring test
entries to mock data and demo vault.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
allContent is empty ({}) in Tauri mode because loadVaultData never
populates it — note content only enters allContent on explicit save.
mergeTabContent() enriches allContent with open tab content so the AI
context snapshot always includes the active note's body.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
detectFileOperation silently failed when tool input was undefined (timing
issues with NDJSON event ordering). Added onVaultChanged callback as fallback:
when Write/Edit/Bash tools complete but specific file can't be determined,
vault.reloadVault() is triggered. Also added safety net in onDone handler.
Threaded onVaultChanged through AiPanel → EditorRightPanel → Editor → App.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove --tools "" restriction so the agent has native bash/read/write/edit
access. Set vault path as working directory for the subprocess.
Simplify MCP to 4 Laputa-specific tools (search_notes, get_vault_context,
get_note, open_note) — everything else is handled by native tools.
Add file operation detection from Write/Edit tool calls to auto-open
created notes and refresh modified notes in the UI. Enhanced tool call
labels show bash commands, file paths, and note names.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Render [[wikilink]] reference pills inside sent message bubbles
with type-colored badges; clicking a pill opens the note
- Add noteList (filtered note list titles, max 100) and noteListFilter
to the structured context snapshot sent to the AI
- Thread noteList/noteListFilter from App → Editor → EditorRightPanel → AiPanel
- Store references in AiAgentMessage for display in chat history
- Add tests for reference pill rendering and noteList context
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: make trashed notes read-only with visible banner in editor
When a note is in the Trash, the editor now shows a banner below the
breadcrumb ("This note is in the Trash") with Restore and Delete
permanently buttons. The BlockNote editor is set to read-only mode,
preventing accidental edits while still allowing navigation and copy.
- Add TrashedNoteBanner component with restore/delete actions
- Pass editable={false} to BlockNoteView when note is trashed
- Add delete_note Rust command for permanent file deletion
- Wire onDeleteNote through Editor → EditorContent → App
- Extract EditorBody to reduce EditorContent cyclomatic complexity
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: rustfmt fix
---------
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds a third editor mode (alongside WYSIWYG and Diff) that shows the raw
markdown file in a monospaced textarea. Includes:
- useRawMode hook with derived state (no setState-in-effect) and onFlushPending
- RawEditorView: textarea with 500ms debounce, YAML error banner, wikilink
autocomplete via [[ trigger, Cmd+S save
- BreadcrumbBar Code icon toggles raw mode; mutual exclusion with diff mode
- Command palette: "Toggle Raw Editor" (View group, requires active tab)
- useEditorModeExclusion hook extracted to keep Editor.tsx under complexity threshold
- buildViewCommands extracted from useCommandRegistry for same reason
- RawToggleButton and RawModeEditorSection components extracted for clean CC
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three changes make the editor respect the active theme:
1. useThemeManager now derives app-specific CSS variables (--bg-primary,
--text-primary, --border-primary, etc.) from the theme's core colors,
so the editor's EditorTheme.css variables resolve correctly for both
light and dark themes.
2. BlockNoteView theme prop is now dynamic (isDark ? 'dark' : 'light')
instead of hardcoded to 'light', so BlockNote's own UI chrome (menus,
toolbars) matches the active theme.
3. Removed forced light-mode class removal from main.tsx that was
preventing dark mode from being applied.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add claude CLI subprocess backend for AI panels
Add claude_cli.rs module that spawns the local `claude` CLI as a
subprocess instead of calling the Anthropic API directly. This removes
the need for users to configure an API key — the CLI uses the user's
existing Claude authentication.
- find_claude_binary(): discovers claude in PATH or common locations
- check_cli(): returns install/version status for the frontend
- run_chat_stream(): spawns claude -p with stream-json for chat panel
- run_agent_stream(): spawns claude with MCP vault tools for agent panel
- Parses stream-json NDJSON events and emits typed Tauri events
- Remove http:default capability (no longer calling APIs from frontend)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: replace Anthropic API with Claude CLI for AI chat and agent panels
Remove direct Anthropic API calls and the entire tool-use loop from the frontend.
Both AI Chat and AI Agent panels now delegate to the `claude` CLI subprocess via
Tauri commands, streaming NDJSON events back to the UI.
Key changes:
- ai-chat.ts / ai-agent.ts: replace SSE/API streaming with Tauri invoke + listen
- useAIChat / useAiAgent hooks: simplified to use CLI streaming callbacks
- AIChatPanel / AiPanel: remove API key dialogs, model selectors, undo support
- SettingsPanel: remove Anthropic key field (no longer needed)
- claude_cli.rs: add comprehensive tests (37 total, 90% coverage)
- mock-handlers: replace ai_chat with check_claude_cli / stream_claude_* stubs
- Net -432 lines across 17 files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add --verbose flag to claude CLI subprocess args
* chore: exclude search.rs and lib.rs from coverage (qmd integration + Tauri setup)
---------
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace AIChatPanel with AiPanel in EditorRightPanel, pass onOpenNote
for vault navigation. Add partial undo (delete created notes). Add
coverage exclusions for AI agent files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add copy_image_to_vault Rust command for native drag-drop
Adds a new Tauri command that copies an image file from a source path
(provided by Tauri's drag-drop event) directly into vault/attachments/.
More efficient than base64 encoding for filesystem drag-drop.
Also adds core:webview:allow-on-drag-drop-event permission.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: handle Tauri native drag-drop for filesystem images
Listen for Tauri's onDragDropEvent to intercept OS-level file drops
that bypass the webview's HTML5 DnD API. When image files are dropped:
1. Copy to vault/attachments via copy_image_to_vault command
2. Insert image block into BlockNote at cursor position
Also passes vaultPath through Editor → EditorContent → SingleEditorView
so the hook can invoke the Tauri command.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove nonexistent drag-drop permission from capabilities
The onDragDropEvent API works through core:event:default (included
in core:default), not a separate webview permission.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct DragDropEvent type handling for 'over' events
Tauri's DragDropEvent discriminated union only provides `paths` on 'drop'
events, not 'over'. Show drag overlay unconditionally on 'over' since we
can't filter by image paths at that stage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: drag-drop-images wireframes (idle + drag-over overlay)
* style: rustfmt mcp.rs and image.rs
---------
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
When the editor's first block is an H1 heading, the note title
automatically stays in sync. Changes are debounced at 500ms.
Manual rename via tab breaks the sync until the tab is switched.
- Show H1 in editor (stop stripping on load, stop reconstructing on save)
- New useHeadingTitleSync hook tracks sync state and debounces title updates
- handleEditorChange keeps frontmatter title: in sync with H1
- Fast path for H1-only content preserves instant new-note interactivity
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Type labels in autocomplete dropdowns (wiki-link [[, relation add,
Cmd+P quick open) and search panel now use the light/muted variant
of the type color as background instead of grey. This matches the
existing color convention used in wiki-link chips and inspector panels.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve custom type color and icon in all autocomplete contexts
Custom types (e.g., Evergreen, Recipe) appeared grey in wikilink [[,
@-mention, Cmd+P, and search autocompletes because getTypeColor() was
called without the custom color key from the Type document.
Root causes:
- Editor.tsx: getTypeColor(group) missing typeEntryMap[group]?.color
- useNoteSearch.ts: getTypeColor(e.isA) missing custom color lookup
- SearchPanel.tsx: type badge had no color styling at all
Fixes:
- Extract buildTypeEntryMap to shared utility in typeColors.ts
- Pass custom color key in all autocomplete code paths
- Add type icon (Phosphor) to the left of each result in NoteSearchList
- Add TypeIcon to WikilinkSuggestionItem and NoteAutocomplete
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add design file for autocomplete type color fix
Shows the fixed state: type icon on the left, colored type badge on
the right, untyped notes neutral grey with no icon.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: speed up Cmd+N note creation from ~150ms to ~16ms
Root cause: the editor focus used a fixed 150ms setTimeout after note
creation, even when the BlockNote editor was already mounted. The
optimistic UI (state updates, tab opening) was already synchronous,
but the focus delay dominated perceived latency.
Changes:
- Editor focus now uses requestAnimationFrame when editor is mounted
(~16ms on 60Hz), falling back to 80ms timeout for first-mount only
- Rust save_note_content creates parent directories automatically,
preventing optimistic-UI revert when creating notes in new folders
- Added perf timing markers (console.debug) to measure creation→focus
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: retrigger after disk space cleanup
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add useNavigationHistory hook for browser-style back/forward
Pure state management hook that tracks a navigation stack of note paths
with cursor-based back/forward traversal. Handles edge cases: duplicate
pushes (no-op), forward stack cleared on new push, invalid path skipping,
and path removal when tabs close.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: integrate back/forward navigation into UI and keyboard shortcuts
- Add Back/Forward arrow buttons to TabBar (left of tabs)
- Wire useNavigationHistory through App → Editor → TabBar
- Add Cmd+[ and Cmd+] keyboard shortcuts via useAppKeyboard
- Register Go Back/Go Forward in command palette
- History tracks active tab changes, skips closed tabs gracefully
- Navigation from history doesn't re-push to stack (ref guard)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add mouse button 3/4 and trackpad swipe for back/forward
- Mouse buttons 3 (back) and 4 (forward) trigger navigation
- macOS trackpad two-finger horizontal swipe triggers back/forward
using accumulated wheel deltaX with a 120px threshold
- Debounced reset after 300ms of inactivity
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: add back-forward-nav.pen with 3 state frames
Shows: default (both disabled), one note visited (back disabled),
two notes visited (back enabled). Matches tab bar button placement.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: show type label for all notes in wiki-link autocomplete
Notes with the default 'Note' type (isA: null) had noteType set to
undefined, hiding their type label. Now every note always shows its
type in the autocomplete dropdown.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: add wiki-autocomplete-type-fix wireframe
Shows the fixed autocomplete dropdown where every note displays its
type label, including default "Note" types in muted color.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Backlinks previously relied on scanning allContent (raw markdown), which
was empty in Tauri mode until notes were explicitly saved. Now outgoing
wikilink targets are extracted during Rust vault scan and stored on
VaultEntry. The frontend useBacklinks hook uses this indexed data, and
outgoingLinks update in real-time on content change.
- Add extract_outgoing_links() in Rust parsing + outgoing_links field on VaultEntry
- Add extractOutgoingLinks() TypeScript utility for real-time updates
- Rewrite useBacklinks to use outgoingLinks instead of scanning raw content
- Add cache version invalidation to force rescan on format change
- Extract useEditorSaveWithLinks hook to keep App.tsx under code health threshold
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Type @ in editor to trigger person-specific autocomplete
- Filters vault entries to show only Person type notes
- Inserts standard [[Person Name]] wikilink on selection
- Lower query threshold (1 char vs 2) since person list is smaller
- Add 3 more Person entries to mock data for testing
- Reuses existing WikilinkSuggestionMenu component
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch wiki-link autocomplete from BlockNote's default grouped suggestion
menu to a custom flat list sorted by relevance. Each item shows the note
type (with its accent color) discretely on the right side. Reduce
MAX_RESULTS from 20 to 10 for a cleaner, more focused list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BlockNote's SuggestionMenu uses item.title as React key, causing duplicate
rendering and broken arrow-key navigation when multiple notes share the
same title. Fix by:
- Adding path-based deduplication to filter out duplicate entries
- Disambiguating same-title notes with parent folder name (e.g. "Standup (work)")
- Deduplicating aliases within each item (filename vs entry.aliases overlap)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
New notes created in-session show a green dot; existing notes with
uncommitted git changes show an orange dot. Saving a new note clears
the green dot; git commit clears the orange dot.
Introduces NoteStatus type ('new' | 'modified' | 'clean'), extracts
useNewNoteTracker hook and resolveNoteStatus pure function, and adds
onNoteSaved callback to useEditorSave for clearing new-note tracking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The autocomplete menu for wiki-links was opening immediately on typing
'[[', processing all 9000+ vault entries and causing a visible freeze.
- Extract preFilterWikilinks utility with MIN_QUERY_LENGTH=2 gate
- Pre-filter entries with case-insensitive substring match before
creating expensive onItemClick closures
- Cap results at MAX_RESULTS=20 after BlockNote's filterSuggestionItems
- Add comprehensive tests for the utility and Editor integration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The tab-swap useEffect in Editor.tsx watched [activeTabPath, tabs, editor].
When save updated tabs via setTabs, the effect re-ran and re-applied stale
cached blocks from the initial tab load, visually reverting the editor.
Subsequent edits would then save old content, effectively losing changes.
Fix: skip the block swap when activeTabPath hasn't changed (only tab content
was updated). Also refresh the cache with current editor blocks so a later
tab switch doesn't revert to stale content.
Added regression tests for the save → update round-trip (JS + Rust).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The useImageDrop hook's handleDrop was uploading files and inserting
image blocks, but BlockNote already has a native dropFile ProseMirror
plugin that does the same via editor.uploadFile — causing duplicate
uploads and image block insertions on every drop.
Fix: handleDrop now only resets the visual overlay state. BlockNote's
native handler handles the actual upload (which calls uploadImageFile)
and block insertion with proper drop-position support.
Also fix build: exclude test files from tsconfig.app.json (test files
use vi.Mock types from vitest, not available in the app build config).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract uploadImageFile to shared hook, add useImageDrop hook that
handles dragover/dragleave/drop events on the editor container.
Drops image files (jpg, png, gif, webp), uploads them via the existing
save_image flow, and inserts BlockNote image blocks at the drop position.
Visual feedback via drop overlay and dashed border.
Refactored Editor.tsx uploadFile to use the shared function.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>