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>
The previous approach embedded conversation history as formatted text
in the prompt (<conversation_history> tags). The claude CLI's -p flag
treats this as a single user turn, losing turn boundaries — so the
model had no real multi-turn context.
Switch to using --resume with the session_id returned by the CLI's
init event. This lets the CLI manage conversation state natively:
- First message starts a new session (with system prompt)
- Subsequent messages resume via --resume (no system prompt needed)
- Clear and retry reset the session_id for a fresh start
Extract makeStreamCallbacks() to keep useAIChat complexity below
CodeScene threshold.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add "Repair Vault" to command palette (Cmd+K → "Repair Vault")
- Add "Repair Vault" to macOS Vault menu bar
- Wire repair_vault Tauri command through App → useAppCommands → registry
- Add menu event handler for vault-repair
- Update MCP get_vault_context to include configFiles.agents content
- Add repair_vault mock handler for browser testing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The command was gated on `mcpStatus !== 'checking'` which meant it was
hidden during the initial async status check. Changed enabled to always
be true so users can find and run the command immediately on app start.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The "Install MCP Server" command was only enabled when status was
"not_installed", preventing users from re-registering when MCP got
removed or broken. Now the command is always available:
- Shows "Install MCP Server" when not installed
- Shows "Restore MCP Server" when already installed
- Added restore/fix/repair keywords for discoverability
- Context-aware toast: "installed" vs "restored"
- Menu bar label updated to "Restore MCP Server"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Structural changes (sidebar visibility, label, order, template, icon/color)
now auto-create missing Type entries and call onFrontmatterPersisted to
refresh git status, so changes appear in git diff without user intervention.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root causes:
- toolInputMapRef in useAiAgent was overwritten by tool_progress events
(which arrive with input=undefined AFTER the assistant message set
the full input), causing detectFileOperation to receive undefined
and skip file creation detection entirely.
- MCP open_note only broadcast open_tab without vault_changed, so
the note list didn't refresh when Claude Code called open_note.
- detectFileOperation only handled Write/Edit but not Bash commands
that create .md files via redirects.
Fixes:
- Preserve accumulated input in toolInputMapRef (input ?? prev?.input)
- MCP open_note now broadcasts vault_changed before open_tab
- detectFileOperation now detects Bash redirect patterns (>, >>, tee)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The AI chat was using both --resume (CLI session resumption) AND formatted
conversation history in the prompt simultaneously. This dual-context approach
confused the model — it saw the conversation twice (from session + from prompt
markup), leading to "I don't have context" responses on follow-ups.
Fix: remove --resume entirely from chat mode. Each CLI call is now independent,
with full conversation history formatted into the prompt via
<conversation_history> markup. trimHistory handles graceful truncation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add "Reindex Vault" command to command palette and menu bar
- Show "Indexed Xm ago" in status bar when idle, clickable to reindex
- After git pull with updates, auto-trigger incremental reindex
- Add lastIndexedTime state to useIndexing, populated from backend metadata
- Add triggerFullReindex to useIndexing (retryIndexing is now an alias)
- Add onSyncUpdated callback to useAutoSync
- Extract formatIndexedElapsed to utils/indexingHelpers.ts
- Tests: 20 new unit tests across 5 files, 2 Playwright smoke tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update commitWithPush to parse GitPushResult from backend and show
specific messages (rejected, auth, network) instead of generic
"push failed". Tests verify rejected + network error scenarios.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The ENTRY_DELETE_MAP and update map in frontmatterToEntryPatch were
missing the 'visible' key. When handleDeleteProperty or
handleUpdateFrontmatter was called for 'visible', the in-memory
VaultEntry was not updated, causing the sidebar to stay stale even
after the file on disk was correctly modified.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add trimHistory() to keep most recent messages within 100k token budget
- Add formatMessageWithHistory() to prepend conversation context to each message
- Wire up in useAIChat.ts: sendMessage now includes full history
- Keep --resume as belt-and-suspenders for same-session continuity
- 14 new tests in ai-chat.test.ts and useAIChat.test.ts
Also track mock frontmatter writes in mockSavedSinceCommit so the
Changes panel updates correctly in browser dev mode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Trash, archive, restore, and unarchive wrote frontmatter to disk but
never called loadModifiedFiles, so the change didn't appear in the
Changes panel until the next manual refresh.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sidebar section visibility is now controlled entirely by the `visible`
property on Type notes. Removes all hidden_sections references from
Rust struct, TypeScript interface, config migration, mock data, and tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Toggle visible property on Type note frontmatter: sets visible:false
to hide, deletes visible property to show (defaults to visible).
Wire up in App.tsx via onToggleTypeVisibility prop.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Apple notarization rejected qmd/qmd and qmd/vec0.dylib due to:
- Not signed with valid Developer ID certificate
- Hardened runtime not enabled
Changes:
- bundle-qmd.sh: use APPLE_SIGNING_IDENTITY + --options runtime --timestamp in CI
(falls back to ad-hoc signing in dev when no identity is set)
- useThemeManager.test.ts: add ensure_vault_themes to mock (added by theme
editor feature, missing from stale theme ID test mock)
On fresh MacBook installs, the bundled qmd binary fails to run due to:
missing execute permissions, macOS quarantine attributes, and no fallback
when qmd is completely absent. This fix addresses all three issues:
- Runtime: ensure +x permissions and remove quarantine on bundled qmd
- Runtime: auto-install qmd via bun when binary not found anywhere
- Build: ad-hoc code-sign qmd and .dylib files in bundle-qmd.sh
- Build: create placeholder resource dirs so fresh clones build cleanly
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a new Pulse sidebar section that shows chronological git commit
history for the vault. Commits are grouped by day with message, time,
short hash (clickable GitHub link when remote configured), file list
with add/modify/delete status icons, and summary badges. Clicking a
file opens the note in the editor. Disabled with tooltip for non-git
vaults. Accessible via sidebar click or "Go to Pulse" command palette.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add VaultConfig infrastructure (store, hook, migration) that persists
zoom, view mode, section visibility, tag/status colors, and property
display modes to config/ui.config.md in the vault instead of localStorage.
- New vaultConfigStore module with subscribe/notify pattern
- useVaultConfig hook loads config via Tauri, binds store, runs migration
- One-time silent migration from localStorage on first load
- Config type excluded from note search and unified search
- All hooks/utils updated to read/write through vault config store
- Tests updated to use vault config store instead of localStorage mocks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ThemePropertyEditor component that surfaces all customizable
properties from theme.json — typography, headings, lists, code blocks,
blockquote, table, and horizontal rule — organized into collapsible
sections with appropriate input types (number, color, select, text).
- themeSchema.ts: derives flat property list from theme.json with
auto-detected input types, units, and select options
- ThemePropertyEditor.tsx: sectioned editor with collapsible sections,
keyboard-accessible toggles, debounced live updates
- ThemeManager: add updateThemeProperty() and activeThemeContent
- SettingsPanel: show property editor below theme list when active
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add missing command palette commands to menu bar and reorganize into
logical groups: File, Edit, View, Go, Note, Vault, Window. New menus
expose navigation filters, note actions, vault management, themes, and
git operations. Context-sensitive items (archive, trash, diff, raw
editor, commit, conflicts) are greyed out when not applicable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Long lines now wrap to the next visual line instead of scrolling
horizontally off-screen, matching expected behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The enabled guard `!!onRestoreDefaultThemes` could evaluate to false at
runtime, hiding the command from the palette. Since the handler is always
defined via useCallback, the guard is unnecessary. Changed to enabled: true,
matching the pattern used by other always-available commands.
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>
- New restore_default_themes() Rust fn: seeds both _themes/ and theme/ dirs
- Per-file idempotent: never overwrites existing files with content
- Fixed ensure_vault_themes() to include minimal.md (was missing)
- New 'Restore Default Themes' command in Cmd+K Appearance group
- 3 new Rust tests + 4 new frontend tests
- 1676 frontend tests passed
The previous conflict detection only worked for merge-based pulls
(--no-rebase) but failed to detect pre-existing conflicts from
interrupted rebases or prior sessions. This fixes three root causes:
1. Rust: add is_rebase_in_progress/is_merge_in_progress/get_conflict_mode
helpers, and dispatch git_commit_conflict_resolution between
`git commit` (merge) and `git rebase --continue` (rebase)
2. Frontend: add startup conflict check via get_conflict_files before
pulling, so pre-existing conflicts are detected on app launch
3. App.tsx: handleOpenConflictResolver now fetches conflicts directly
when the cached list is empty, preventing the silent early-return
Also exposes get_conflict_files and get_conflict_mode as Tauri commands
so the frontend can independently check conflict state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Frontend wrote `trashed` (lowercase) but Rust parser expected `Trashed`
(title-case via serde rename). On restart, the lowercase key didn't match,
defaulted to false, and trashed notes reappeared.
- Frontend: use title-case keys (Trashed, Trashed at) matching vault convention
- Rust: add serde aliases for lowercase keys (backward compat with already-written files)
- Add regression tests for both title-case and lowercase parsing
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>
useMcpRegistration is fully replaced by useMcpStatus which combines
detection + registration. Added design placeholder for MCP status bar.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add check_mcp_status Tauri command that detects whether the MCP server
is installed, Claude CLI is missing, or config needs setup. The status
bar shows a warning badge (MCP ⚠) when not installed, clickable to
trigger install. Also available via command palette "Install MCP Server".
Replaces useMcpRegistration with useMcpStatus which combines detection
and registration in a single hook.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rust: add ThinkingDelta event to ClaudeStreamEvent for reasoning chunks
- ai-agent.ts: forward ThinkingDelta events via onThinking callback
- useAiAgent: stream reasoning live, accumulate response internally,
reveal as complete block on done
- AiMessage: auto-collapse reasoning when done, use MarkdownContent
for response rendering, update tests for new behavior
- ai-context: add buildContextSnapshot() for structured JSON context
with activeNote, openTabs, noteListFilter, vault summary
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root causes:
- index.js tried to start its own UI bridge server on port 9711, but
ws-bridge.js (spawned by Tauri) already owns it → broadcastUiAction
was a no-op. Fixed by connecting index.js as a WebSocket CLIENT that
sends messages through the existing bridge.
- ws-bridge.js UI bridge had no relay — client messages weren't forwarded.
Added relay so messages from the MCP server reach the React frontend.
- useAiActivity hook existed but was never imported in App.tsx.
- useAiActivity only handled highlight, not open_note/open_tab/set_filter.
- No vault_changed events after write operations.
- set_filter payload used `type` key which overwrote `type: 'ui_action'`.
Changes:
- mcp-server/index.js: connect as WS client instead of starting server;
broadcast vault_changed after all write operations
- mcp-server/ws-bridge.js: add message relay in UI bridge; broadcast
vault_changed after write operations; fix set_filter payload key
- useAiActivity: handle all UI actions (highlight, open_note, open_tab,
set_filter, vault_changed); accept callbacks; auto-reconnect on close
- App.tsx: wire useAiActivity into vault/notes/selection actions; apply
ai-highlight CSS class to editor and note list panels
- App.css: add ai-highlight-glow keyframe animation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
seed_vault_themes now writes individual missing/empty files instead of
skipping when the theme/ directory already exists. A new
ensure_vault_themes Tauri command is called by useThemeManager on every
vault open so vaults without a theme/ folder get default.md and dark.md
seeded automatically.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On fresh installs without qmd, show the accurate "Installing search..."
phase instead of briefly flashing "Indexing..." before switching to
unavailable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Separate 'unavailable' (qmd not installed) from 'error' (indexing failed)
phases. Unavailable state is hidden from the status bar instead of showing
a persistent orange error. Actual errors show "Index failed — retry" with
click-to-retry. Both phases auto-dismiss after a timeout.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tool call blocks in AI Chat are now clickable and expandable to show
tool name, input parameters (pretty-printed JSON), and output/result.
Collapsed by default, keyboard accessible (Tab/Enter/Space/Esc).
Backend: Rust stream events now carry tool input (accumulated from
input_json_delta chunks) and tool output (from tool_result events).
Frontend: AiActionCard is a disclosure widget with aria-expanded,
error output shown in red, long content truncated.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tabs stored a snapshot of VaultEntry at open time. When updateEntry()
changed vault.entries (e.g. trashing a note), the active tab's entry
stayed stale, so the banner never appeared until the note was reopened.
Add a useEffect that syncs tab entries with vault.entries whenever the
vault state changes, using reference equality to skip unchanged entries.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds line numbers, current line highlight, and syntax highlighting
(YAML frontmatter keys/values, --- delimiters, markdown headings).
Extracted useCodeMirror hook and frontmatterHighlight extension.
Preserves wikilink autocomplete, Cmd+S save, and Escape dismiss.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After createTheme(), the theme file was created and the sidebar navigated
to the Theme section, but the note was never opened in the editor.
Now captures the returned path and opens it via handleSelectNote.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add APP_CHECK_FOR_UPDATES constant and menu item in menu.rs (between About and Settings)
- Wire onCheckForUpdates handler through useMenuEvents and useAppCommands
- Add test for app-check-for-updates dispatch
Sort preferences for each type's note list are now stored in the type
file's frontmatter (e.g. `sort: modified:desc` in `type/person.md`)
instead of localStorage. This makes preferences portable with the vault
and versionable in git.
- Add `sort` field to Rust Frontmatter/VaultEntry and TS VaultEntry
- NoteList reads sort from type entry's frontmatter when viewing a type
- Sort changes write to type file via update_frontmatter
- Silent migration from localStorage on first access per type
- Relationship group sorts still use localStorage (no type file)
- Fallback to `modified:desc` when no sort preference exists
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The merge of the dark-theme-editor feature (13ee62c) into the
themes-editable rewrite (7169634) broke the entire theming UI:
themes weren't listed, switching didn't work, and new theme creation
was broken.
Root causes and fixes:
- Stale theme ID from old JSON system ("untitled-2") never cleared:
added detection that clears IDs not matching any known vault theme,
with a ref to skip IDs just set by switchTheme/createTheme
- set_active_theme Rust command only accepted String, not Option:
now accepts Option<String> so null can clear the setting
- Theme colors empty in UI: entryToThemeFile now extracts colors
from frontmatter content via extractColorsFromContent
- color-scheme/data-theme-mode not set: added updateColorScheme
and clearColorScheme to sync DOM attributes on theme apply/clear
- isDark broken in Tauri (allContent is {}): moved isDark tracking
into useThemeApplier as state, updated when vars are applied
- SettingsPanel passed activeThemeId as name to createTheme: fixed
to call createTheme() with no arguments
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>