Commit Graph

615 Commits

Author SHA1 Message Date
lucaronin
fe37bb4b27 feat: wire create-open relationship note to all panel contexts 2026-03-11 21:25:20 +01:00
lucaronin
4a973e9ba8 feat: add 'Create & open' option to relationship input dropdowns
When typing a non-existent note title in the relationship target input,
a 'Create & open' option now appears at the bottom of the dropdown.
Selecting it creates the note, adds the wikilink, and opens the new note.

- Added SearchDropdownWithCreate with create option
- Modified InlineAddNote and NoteTargetInput to support create flow
- Added onCreateAndOpenNote prop to DynamicRelationshipsPanel
- Keyboard accessible (arrow keys + Enter)
- 6 new tests covering create-and-open behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 21:25:20 +01:00
lucaronin
b02227a5a6 fix: case-insensitive type entry lookup + Playwright smoke test
buildTypeEntryMap now stores both original title and lowercase key so
isA: 'config' matches type entry titled 'Config'. Adds Playwright smoke
test that blocks the vault API to test against mock data fixtures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 21:24:51 +01:00
lucaronin
aaa5bc2e81 fix: sidebar section header reflects type icon, color, and label
- Add GearSix icon ('gear-six') to icon registry — was missing, causing
  Config type to show FileText fallback instead of its configured icon
- Add 'gray' to ACCENT_COLORS palette with CSS variables — was missing,
  causing Config type color to fall back to muted foreground
- Extract sidebar section logic to utils/sidebarSections.ts for testability
- Add Config type + instance to mock entries for browser dev mode
- Add tests: icon resolution, gray color, sidebar section builder

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 21:24:51 +01:00
lucaronin
5107cd13f4 test: add real filesystem vault integration tests
Replace mock vault approach with real filesystem I/O for integration
tests. Each test copies tests/fixtures/test-vault/ to a temp directory,
overrides mock handlers to point at it, and verifies actual file
operations through the vite dev server middleware.

Tests cover: vault loading, archive/trash filtering, note creation,
rename with filesystem update, wikilink cascade on rename, relationship
display, and real file content loading.

Also extends vite.config.ts vault API middleware with write endpoints
(save, rename, delete, search, entry) and fixes getBool to handle
YAML 1.2 string values like "Yes"/"yes" (js-yaml 4.x compatibility).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 21:24:44 +01:00
lucaronin
1e1eb4facc test: unit tests for rename-wikilink-update feature
- handleRenameNote passes entry title as old_title to Rust
- handleUpdateFrontmatter triggers rename on title key change
- non-title keys don't trigger rename
- null old_title when entry not found

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 19:47:46 +01:00
lucaronin
d28c46123c feat: wire H1 sync and frontmatter title change to full rename flow
handleTitleSync now saves pending content and calls rename_note
(which renames the file and updates wikilinks) instead of only
updating in-memory state. handleUpdateFrontmatter also triggers
rename when the title: key is changed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 19:47:46 +01:00
lucaronin
a90ca8bf3a fix: post-process BlockNote serializer to remove extra blank lines
blocksToMarkdownLossy() inserts blank lines between every block, making
tight lists loose and polluting git history. Add compactMarkdown() that
collapses inter-list-item blanks and excessive blank line runs while
preserving code blocks and intentional paragraph spacing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 19:38:05 +01:00
lucaronin
ee94ecb731 fix: unify wikilink resolution and disambiguate duplicate titles
- Create resolveEntry() in wikilink.ts: single case-insensitive resolution
  function that handles title, alias, filename stem, path suffix, and
  pipe syntax matching
- Replace findEntryByTarget (case-sensitive) and entryMatchesTarget
  (hardcoded /Laputa/ path) with unified resolveEntry
- Fix attachClickHandlers to insert path|title pipe syntax when multiple
  candidates share the same title (disambiguation)
- Update ai-context.ts resolveTarget to use unified resolution
- Add comprehensive tests for resolveEntry and disambiguation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 19:12:05 +01:00
lucaronin
1486bae4bd fix: image drop overlay no longer triggers on internal drags (tabs, blocks)
Remove setIsDragOver(true) from Tauri onDragDropEvent 'over' handler —
Tauri over events can't distinguish OS file drags from internal drags.
The HTML5 dragover handler already checks hasImageFiles() correctly and
now solely drives the overlay state. Tauri handler only processes drops.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 18:36:16 +01:00
lucaronin
e0bf514f5d feat: rename file on save when title slug doesn't match filename
When Cmd+S is pressed, after saving content, checks if the note's
title slug differs from its current filename. If so, triggers
rename_note to update the file on disk, tabs, breadcrumbs, and
wikilinks. Adds needsRenameOnSave() utility with tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 18:09:55 +01:00
lucaronin
7005f54476 fix: mock move_note_to_type_folder collision handling + Rust collision content test
The mock handler now appends -2, -3, etc. when the target path already
exists, matching the Rust unique_dest_path logic.  Previously it would
silently overwrite the existing note's content in MOCK_CONTENT.

Also adds a Rust test that verifies both the moved note and the
pre-existing note retain their respective content after a collision.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 17:39:18 +01:00
lucaronin
265f7b3eb2 fix: prevent data corruption when changing note type — preserve tab content instead of re-reading from disk
After runFrontmatterOp updates the frontmatter and sets the tab content,
move_note_to_type_folder only changes the file location (not its content).
Re-reading via loadNoteContent(result.new_path) was redundant and dangerous:
if the path collided or a stale cache intervened, it could load a different
note's content into the tab — the root cause of the data-corruption bug.

Also fixes stale-closure issue: replaceEntry no longer spreads the captured
`entries` array (which could be stale after the await), avoiding reverting
the isA field that runFrontmatterOp already updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 17:37:53 +01:00
lucaronin
4c7e64dc0c fix: bump cache version + handle Yes/No in TS frontmatter parser
Root cause: commit d6d35b3 added a custom Rust deserializer for
Archived/Trashed Yes/No strings but did not bump CACHE_VERSION.
Existing vaults had stale cached entries with archived: false from the
old parser, and since the cache version (5) matched, stale values were
served without re-parsing from disk.

- Bump CACHE_VERSION 5 → 6 to force full rescan on next vault load
- Add Yes/No handling to TypeScript parseScalar (Inspector display)
- Add integration tests: cached vault path with Archived/Trashed: Yes
- Add stale cache version invalidation test
- Add frontmatter.test.ts for TS Yes/No boolean parsing
- Add Playwright smoke test for archived note filtering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 17:07:22 +01:00
lucaronin
3084702e5c fix: prevent crash in handleCreateNoteImmediate — slugify fallback + try/catch
- slugify now returns 'untitled' instead of empty string when input has only
  special characters, preventing invalid paths like '/vault//note.md'
- handleCreateNoteImmediate wrapped in try/catch — worst case shows a toast
  error instead of crashing the app
- Added Rust test for deeply nested directory creation in save_note_content
- Regression tests for slugify edge cases and handleCreateNoteImmediate with
  special-character types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 16:50:05 +01:00
lucaronin
517a2af730 fix: live-reload CSS vars when saving active theme note in editor
When user edits a theme note directly in the editor and presses Cmd+S,
the app now immediately re-applies CSS variables — no manual reload
needed. Added notifyThemeSaved(path, content) to ThemeManager; wired
into onNotePersisted callback so saving the active theme updates
cachedThemeContent, triggering useThemeApplier.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 16:31:03 +01:00
lucaronin
a2e65b36c3 test: add unit tests for prefetch cache, optimistic rollback, rapid switching
- Prefetch: content served from cache, cache cleared on vault reload,
  deduplication of concurrent requests
- Optimistic rollback: trash/archive/restore/unarchive roll back
  updateEntry on disk write failure with error toast
- Optimistic ordering: updateEntry called before frontmatter writes
- Rapid switching: sequence counter prevents stale active tab when
  notes are opened faster than IPC resolves

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 13:28:46 +01:00
lucaronin
d8b61dd62f feat: optimize note open and trash/archive latency
Latency root causes:
1. handleSelectNote/handleReplaceActiveTab awaited IPC before updating
   activeTabPath — zero visual feedback for 50-200ms file I/O
2. Trash/archive called updateEntry AFTER two sequential IPC calls —
   note stayed visible in list for 100-400ms

Optimizations:
- Content prefetch cache: hover on NoteItem and keyboard arrow
  navigation pre-load note content via IPC. When user clicks, content is
  already in memory — eliminates the IPC round-trip entirely.
- Optimistic trash/archive/restore/unarchive: updateEntry runs
  immediately, frontmatter writes happen async. On failure, UI rolls
  back and shows error toast.
- Rapid-switch safety: sequence counter (navSeqRef) ensures only the
  latest navigation sets activeTabPath — prevents stale content flash
  when user clicks multiple notes in quick succession.
- Prefetch cache cleared on vault reload to prevent stale content after
  external edits.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 13:28:46 +01:00
lucaronin
73060f3d00 fix: exclude trashed notes from search results and autocomplete
Trashed notes were appearing in search (Cmd+F), Quick Open (Ctrl+P),
wikilink autocomplete ([[), and person mention autocomplete (@).

Rust: add is_file_trashed() to check frontmatter, filter search_vault results.
Frontend: filter trashed entries from useNoteSearch, baseItems in both
editor views, and mock search_vault handler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:42:56 +01:00
lucaronin
a73f18142b fix: add reload_vault to vault API proxy for browser mock
The vault-api proxy maps Tauri commands to HTTP endpoints when a vault
API server is running. Without this, reload_vault bypassed the proxy
and Playwright route interceptors couldn't catch vault reload calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:14:34 +01:00
lucaronin
ba5ba58c6b fix: reload vault invalidates cache and rescans from filesystem
Reload Vault (Cmd+K) now calls the new `reload_vault` Tauri command which
deletes the cache file before scanning, guaranteeing a full filesystem
rescan. Previously it called `list_vault` which used incremental git-based
cache updates that could miss recent changes (e.g. trashing a note then
reloading showed stale data).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:14:34 +01:00
lucaronin
bc2d54d062 fix: resolve TypeScript overload errors in zoomCursorFix
Use untyped function references to avoid conflicts with
posAtCoords overloaded type signatures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 10:54:42 +01:00
lucaronin
0082c02eb4 fix: bypass CodeMirror posAtCoords for accurate cursor at non-100% CSS zoom
CSS zoom on document.documentElement causes a coordinate space mismatch
between mouse event clientX/Y (viewport space) and Range.getClientRects()
(CSS space), breaking CodeMirror's click-to-position mapping. The previous
requestMeasure() fix only recalibrated cached geometry, not this mismatch.

New approach: zoomCursorFix extension patches posAtCoords/posAndSideAtCoords
on the EditorView instance to use document.caretRangeFromPoint() — the
browser's native, zoom-aware API — with coord-adjustment fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 10:43:46 +01:00
lucaronin
e3d616cd26 fix: embed conversation history in AI agent chat messages
The AI chat panel (AiPanel → useAiAgent) was sending each message as a
standalone request with no prior context. Root cause: useAiAgent.sendMessage
called streamClaudeAgent with raw text, never embedding history.

- Add agentMessagesToChatHistory() to convert AiAgentMessage[] to ChatMessage[]
- Embed trimmed history in each agent request via formatMessageWithHistory
- Use messagesRef/statusRef to avoid stale closures in async callbacks
- Also fix useAIChat (dead code path) with same ref pattern
- Update mock layers to detect history presence for testability
- Add Playwright smoke tests verifying history accumulates and resets

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 10:21:49 +01:00
lucaronin
010d2c3949 fix: disk-first writes in useEntryActions, document three-layer model
Move updateEntry() calls after handleUpdateFrontmatter/handleDeleteProperty
in handleCustomizeType, handleRenameSection, and handleToggleTypeVisibility
so React state only updates after the disk write succeeds. This prevents
state-disk divergence when writes fail.

Expand ARCHITECTURE.md "Three representations, one authority" section with
ownership rules, invariants table, and recovery mechanisms. Add
reload_vault_entry to the commands table (62 total).

Add Playwright smoke test for Reload Vault in Cmd+K palette.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 00:52:56 +01:00
lucaronin
dedb5b2721 feat: wire Reload Vault into Cmd+K palette and menu bar
Adds a "Reload Vault" command that forces a full rescan from filesystem,
bypassing cache. Available via Cmd+K and Vault menu. Wired through
useAppCommands → useCommandRegistry and useMenuEvents.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 00:52:56 +01:00
lucaronin
9cc9ff80d8 refactor: remove hardcoded RELATIONSHIP_KEYS — detect wikilink fields dynamically
Any frontmatter field whose value contains [[wikilinks]] now renders as a
relationship chip automatically. Fields with plain-text values always render
as editable properties, even if they were formerly hardcoded relationship keys.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 00:33:57 +01:00
lucaronin
3e05a9425a test: add Playwright smoke test for exact-match search ranking
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>
2026-03-08 23:53:00 +01:00
lucaronin
55e479be13 feat: flip canonical type field — make type: primary, Is A: the alias
The Rust parser now treats `type:` as the canonical frontmatter field
for entity type, with `Is A:` and `is_a:` accepted as legacy aliases.
Previously it was the other way around, creating an asymmetric
read/write cycle since the frontend and all 8800+ vault notes already
use `type:`.

- Flip serde attribute: rename="type", alias="Is A", alias="is_a"
- Update theme defaults, getting-started vault, and type definitions
- Add round-trip tests for both type: and Is A: parsing
- Update mock data and TypeScript tests to use canonical form

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 23:38:17 +01:00
lucaronin
02611f6d41 feat: move vault cache to ~/.laputa/cache/ and make writes atomic
Cache files are now stored outside the vault directory at
~/.laputa/cache/<vault-hash>.json, preventing them from polluting
the user's git repo. Writes use atomic tmp+rename to avoid corruption.
Legacy .laputa-cache.json files are auto-migrated and cleaned up on
first run.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 23:04:14 +01:00
lucaronin
b0252118a8 test: add Playwright smoke test for exact-match search ranking
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>
2026-03-08 22:15:08 +01:00
lucaronin
4649662f3e fix: ensure exact title match always ranks first in search
Title exact match gets exclusive tier 0 — alias exact match is capped
at tier 1, so a note titled "Refactoring" always appears above notes
with "Refactoring" as an alias or prefix. The 5-tier ranking is:
0=title exact, 1=alias exact, 2=title prefix, 3=alias prefix, 4=fuzzy.

Also adds ranking to editor wikilink autocomplete (enrichSuggestionItems)
and trims whitespace in searchRank comparisons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:03:54 +01:00
lucaronin
873f85afc9 feat: move note to type folder when Is A changes
When the user changes a note's type via the Properties panel,
the note file is automatically moved to the corresponding type folder.
Shows a toast confirming the move. No move if already in correct folder.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 21:52:30 +01:00
lucaronin
2d2c162c3c feat: add move_note_to_type_folder backend command
Adds a new Tauri command that moves a note file to the folder
corresponding to its new type when Is A is changed. Handles:
- folder creation, filename collision (-2 suffix), wikilink updates,
  and no-op when already in the correct folder.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 21:52:30 +01:00
lucaronin
4ce9167e09 feat: exact-match-first ranking in search and wikilink autocomplete
Add searchRank/bestSearchRank utilities that compute a tier (0=exact,
1=prefix, 2=fuzzy-only). Both useNoteSearch and WikilinkChatInput now
sort by rank tier first, then by fuzzy score, ensuring notes with exact
title or alias matches always surface above partial/fuzzy matches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 21:00:58 +01:00
lucaronin
749647bf64 feat: fast note open — use allContent cache to skip IPC disk reads
handleSelectNote and handleReplaceActiveTab now check the in-memory
allContent cache before issuing a Tauri IPC call. Cache hits open the
tab synchronously (zero latency). Cache misses fall back to the disk
read and populate allContent via onContentLoaded so subsequent opens
of the same note are instant.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 20:33:57 +01:00
lucaronin
ea26e0a2ee fix: use vault path for resolveNewNote, resolveNewType, resolveDailyNote
Remove hardcoded /Users/luca/Laputa/ paths from resolveNewNote,
resolveNewType, and resolveDailyNote. All three now accept a vaultPath
parameter and build paths relative to the active vault. Added vaultPath
to NoteActionsConfig so the hook passes it through to all callers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 20:00:20 +01:00
lucaronin
8eac9f9e3f fix: show all scalar properties in Properties panel — remove Owner from RELATIONSHIP_KEYS, remove notion_id from SKIP_KEYS 2026-03-08 19:46:18 +01:00
lucaronin
71810a7a90 fix: resolve AI chat empty body race — read contextPrompt from closure, not stale ref
contextRef (useRef) was initialized at mount time and synced via useEffect,
which runs after paint. If contextPrompt was empty at mount (tab content
not yet loaded), sendMessage could read stale/empty context during the
window between paint and effect. Removing the ref and reading contextPrompt
directly in the useCallback closure eliminates the race entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:02:21 +01:00
lucaronin
87090cebc3 feat: show type instances in inspector Properties panel
When viewing a Type note (e.g. "Project"), the Properties panel now shows
an Instances section listing all notes of that type, sorted by modified_at
descending. Trashed instances are excluded, archived instances are dimmed.
Display capped at 50 with count badge for large collections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 18:14:14 +01:00
lucaronin
f2a9f02d2a fix: resolve AI chat empty body via || fallback + defensive body + Rust strip_frontmatter
Three root causes identified and fixed:

1. JS semantics bug: `activeNoteContent ?? allContent[path]` used nullish
   coalescing (`??`) which does NOT fall through on empty string ''. When
   handleEditorChange temporarily overwrites tab.content with frontmatter-
   only content during async content swaps, activeNoteContent becomes ''
   and the fallback to allContent never triggers. Fix: change `??` to `||`.

2. Defence-in-depth: when body is still empty after fallback (Tauri mode
   where allContent is {}), but wordCount > 0, the body field now includes
   an explicit get_note instruction instead of being empty. This is more
   reliable than the preamble instruction that Claude may skip.

3. Rust strip_frontmatter used `rest.find("---")` which matches `---`
   anywhere in text (including inside frontmatter values like `title:
   foo---bar`). Fixed to use `rest.find("\n---")` for line-boundary
   matching. This ensures accurate wordCount for the fallback heuristic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 17:58:47 +01:00
lucaronin
12fe4978ab fix: auto-save unsaved notes before trash/archive
Flush unsaved editor content to disk before any trash or archive
operation (both single-note and bulk) so body edits are never silently
dropped when only frontmatter is updated.

- Add flushEditorContent utility that checks pending content ref, then
  falls back to comparing tab content with last-saved state
- Add onBeforeAction callback to useEntryActions, called before
  handleTrashNote and handleArchiveNote
- Wire flushBeforeAction in App.tsx using refs for stable closures
- Add error handling in useBulkActions so one failed save doesn't
  block remaining notes
- Extract findOrCreateType helper to reduce useEntryActions complexity
- Export persistContent from useSaveNote for reuse

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 16:52:08 +01:00
lucaronin
48b6434dd4 fix: strip frontmatter from AI context body field — fixes empty body bug
The body field in buildContextSnapshot was passing the full raw file
content (including YAML frontmatter delimiters) instead of just the
body text. When handleEditorChange reconstructed tab content with empty
blocksToMarkdownLossy output, the body became frontmatter-only — causing
the AI to report "has frontmatter but no body content."

Three changes:
1. Strip frontmatter from body using splitFrontmatter before setting the
   body field (frontmatter is already a separate parsed field)
2. Add wordCount to the context snapshot so the AI can detect when body
   is stale vs genuinely empty
3. Instruct the AI to call get_note MCP tool when body is empty but
   wordCount > 0, providing a safety net for any content staleness

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 16:15:38 +01:00
lucaronin
d41fe306fe fix: AI chat receives live editor content instead of stale disk content
handleContentChange now syncs content to tab state on every editor
change (not just on Cmd+S save). This ensures the AI panel's context
snapshot always contains the current editor body, fixing the bug where
the AI reported empty note bodies for unsaved edits.

Root cause: pendingContentRef buffered content for save but never
updated notes.tabs[].content, so activeTab?.content (used by the AI
context builder) always reflected the last-saved disk content.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:07:45 +01:00
lucaronin
5290e1847d fix: CodeMirror cursor placement at non-100% zoom levels
Root cause: CSS zoom on document.documentElement caused stale CodeMirror
measurements. Two issues:

1. Race condition: zoom was applied in useEffect (parent), but CodeMirror
   was created in useEffect (child) — child effects run first, so CM
   measured at zoom=1 before zoom was actually applied.

2. No re-measure on zoom change: CSS zoom changes don't trigger
   ResizeObserver on descendant elements, so CodeMirror never updated
   its cached scaleX/scaleY, line heights, or character widths.

Fix:
- Apply zoom synchronously during useState init (before child effects)
- Dispatch 'laputa-zoom-change' event when zoom changes
- Listen for this event in useCodeMirror and call view.requestMeasure()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 14:54:50 +01:00
lucaronin
d37c75296c fix: pass active note content directly to AI context builder
In Tauri mode, allContent is {} (empty) until a note is explicitly
saved. The previous mergeTabContent fix enriched allContent with tab
content, but the indirection was fragile. This fix passes the active
tab's content directly to buildContextSnapshot as activeNoteContent,
which takes priority over allContent[path]. This ensures the AI always
receives the note body regardless of allContent state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:42:09 +01:00
lucaronin
9c7f05fb7d fix: embed conversation history in prompt instead of broken --resume
Each CLI invocation is a fresh subprocess — --resume depends on session
persistence that doesn't reliably work with -p mode. Switch to prompt-
embedded history: prior exchanges are formatted into each request using
formatMessageWithHistory + trimHistory (already existed as dead code).

- Remove sessionIdRef and --resume session tracking
- Always send system prompt (each request is stateless)
- Split sendMessage into doSend(text, history) to handle retry correctly
- Retry now passes correct history (excludes the retried exchange)
- Tests verify history accumulation, clear, retry, and system prompt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 13:06:58 +01:00
lucaronin
c47a67168c fix: AI chat receives note body from open tabs instead of empty allContent
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>
2026-03-08 12:32:52 +01:00
lucaronin
43629ca62c fix: AI-created notes now visible — onVaultChanged fallback for missed file ops
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>
2026-03-07 14:19:41 +01:00
lucaronin
35afd09364 fix: AI chat wikilinks — system prompt + integration tests
Root cause: buildContextSnapshot (the system prompt used when a note is
active — the common case) did NOT instruct the AI to use [[wikilinks]].
Only buildAgentSystemPrompt (the fallback when no context) had it.
So the AI almost never produced clickable wikilinks.

Fix: Add the [[Note Title]] wikilink instruction to
buildContextSnapshot's preamble.

The click handler chain was already correct (verified with new
integration tests): MarkdownContent → AiMessage → AiPanel →
notes.handleNavigateWikilink → findWikilinkTarget → handleSelectNote.

Tests added:
- Unit: buildContextSnapshot includes wikilink instruction
- Integration: clicking wikilinks in AiPanel calls onOpenNote
- Playwright: wikilink renders, click opens note in tab

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 13:42:32 +01:00