Commit Graph

1055 Commits

Author SHA1 Message Date
lucaronin
4d61774a70 fix: smoke tests skip Theme entries in note list to avoid type selector issues
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 13:50:11 +01:00
lucaronin
164b76d35c fix: resolve demo vault path relative to project root — fix smoke test failures
DEFAULT_VAULTS was hard-coded to the main repo path, causing the Vault API
to read stale/duplicate files when running from worktrees. Now uses Vite's
define to inject the correct path at build time. Also fix theme heading
titles (remove redundant "Theme" suffix) and make smoke tests resilient
to Theme-type notes appearing first in the note list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 13:14:56 +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
92f0f96ad2 test: Playwright smoke test for note filename rename on save
Covers: title change + save renames file, no rename when filename matches,
rapid title edits rename to final title.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 18:09:55 +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
761d6dc3b5 fix: rename_note handles filename-slug mismatch and collisions
When the note content already has the correct title but the filename
doesn't match (e.g. untitled-note-9.md after user changed H1), the
rename was a no-op. Now checks both title AND filename slug before
early-returning. Also uses unique_dest_path for collision handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 18:09:55 +01:00
lucaronin
5f08e8eba2 test: Playwright smoke test for changing-type data corruption regression
Verifies that changing a note's type preserves the editor content —
the bug caused the tab to load a different note's content after the move.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 17:50:22 +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
8a8fa802bb style: rustfmt formatting fix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 17:09:36 +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
e891ff6191 test: Playwright smoke test for create note crash fix
Covers all acceptance criteria:
- Click '+' next to type section → note created, no crash
- Cmd+N → note created, no crash
- Custom type → note created, no crash
- Rapid double-click → both notes created, no crash

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 16:50:05 +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
89ae7e54bb test: Playwright smoke test for theme live reload on save
Verifies that editing a theme note frontmatter in raw mode and pressing
Ctrl+S immediately updates CSS vars on the DOM. Also verifies saving a
non-theme note does not affect the active theme.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 16:36:56 +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
484324ae3f test: add Playwright smoke test for rapid note switching latency
Validates that rapid keyboard navigation and click-based note switching
don't produce stale content or crash the editor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 13:28:46 +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
db66e7c9da fix: scope Playwright selectors to dialog overlays to avoid sidebar matches
The `span.truncate` selector was matching sidebar note titles in addition
to search results, causing false positives in the full-text search test.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:59:57 +01:00
lucaronin
a760594d66 style: apply cargo fmt to is_file_trashed tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:44:37 +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
46f289d229 fix: simplify cache-invalidation smoke test — container visibility only 2026-03-09 12:20:24 +01:00
lucaronin
182f88cd10 chore: rotate Tauri signing keypair — fix CI release builds 2026-03-09 12:20:24 +01:00
lucaronin
d6d35b3057 fix: handle Archived/Trashed Yes/No string values in frontmatter parser
The Rust YAML parser only accepted boolean values (true/false) for the
archived and trashed fields. When the vault writes Archived: Yes or
Trashed: Yes (YAML string, not boolean), serde silently returned None
and the note appeared as non-archived/non-trashed.

Add a custom deserializer that accepts both booleans and string
representations (Yes/yes/YES/true/1 → true, No/no/false/0 → false).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:59:27 +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
656679e21a style: apply cargo fmt to new tests
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
1354e504c3 test: add Playwright smoke test for CodeMirror cursor at non-100% zoom
Covers clicking at 150%, 80%, and double-click word selection at 125%.
Verifies cursor lands near the click point (within first 30 chars of line)
and that word selection produces a non-empty range.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 10:51:57 +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
afe549478e ci: retrigger release — fix Tauri signing key secret 2026-03-09 10:00:59 +01:00
lucaronin
e414d3b86c test: fix flaky command palette smoke test — use reindex instead of settings
Settings command is disabled in mock environment (onOpenSettings not wired),
causing the 'typing filters the command list' test to always fail.
Reindex Vault is always enabled and already tested in indexing-reindex-status.spec.ts.
2026-03-09 09:28:48 +01:00
lucaronin
d3657d86bd docs: consolidate VISION.md into docs/ — remove duplicate root file 2026-03-09 09:25:48 +01:00
lucaronin
664426d598 fix: prune stale cache entries on vault open, not just cache write
Remove the early return in update_same_commit that skipped filesystem
validation when git reported no changes. Add prune_stale_entries to
finalize_and_cache so every vault scan path validates entries exist on
disk and deduplicates by case-folded path. Prevents ghost notes after
deleting files outside the app (e.g., via Finder).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 03:06:55 +01:00
lucaronin
0755c4a8c0 fix: add 1 retry for Playwright smoke tests to handle server startup timing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 00:58:24 +01:00
lucaronin
fdb3fdf6fe style: rustfmt vault/mod.rs test formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 00:52:56 +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
587eb0231e feat: add reload_vault_entry Tauri command
Re-reads a single .md file from disk and returns a fresh VaultEntry.
Used after failed optimistic updates to restore the true filesystem state.

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
dd1595cf68 style: apply rustfmt to rename.rs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 21:52:30 +01:00
lucaronin
ab1cb6260c test: add Playwright smoke test for move-note-to-type-folder
Covers type change → move toast confirmation and type selector visibility
in the properties panel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 21:52:30 +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