Covers note creation via Cmd+N, unique naming, note selection, and
inspector rendering. Also documents frontmatterOps in ARCHITECTURE.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
39 tests for useNoteCreation covering creation, daily notes, templates,
optimistic revert, and unsaved cleanup. 12 tests for useNoteRename
covering rename operations, toast messages, and error handling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
useNoteActions.ts reduced from 213 to 125 lines by extracting frontmatter
helpers into frontmatterOps.ts and removing re-exports. Consumers now
import directly from the extracted modules.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The vault module split left the mod.rs VaultEntry struct missing fields
that were added in entry.rs but never wired in. Adds the missing fields,
populates them from frontmatter, adds status/owner/cadence to SKIP_KEYS,
and fixes conflicting test expectations from the incomplete merge.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove unused HashMap import from mod.rs, use contains() instead of
iter().any() in frontmatter.rs, add HashMap import to test module.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract VaultEntry struct to entry.rs (64 lines), YAML parsing to
frontmatter.rs (323 lines), and file I/O helpers to file.rs (59 lines).
Tests moved to mod_tests.rs. mod.rs reduced from 1679 to 189 lines,
now purely orchestration (parse_md_file, reload_entry, scan_vault).
All 612 tests pass, public API unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract note creation (CRUD, daily notes, types, optimistic persistence) into
useNoteCreation and rename operations into useNoteRename. useNoteActions now
composes both hooks plus frontmatter/navigation logic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Frontmatter struct deserialization failed completely when any unknown
field had a list value (e.g. Owner: [Luca], Cadence: [Weekly]) because
serde expected a string but got an array. This caused unwrap_or_default()
to return all-None, losing type/status/archived for ~7000+ notes.
Two fixes:
1. Filter parse_frontmatter input to only known keys, preventing unknown
fields from causing deserialization failures
2. Change Owner and Cadence to StringOrList to handle both formats
Extract PropertyValueCells, TypeSelector, and AddPropertyForm into dedicated
files. Move shared display-mode constants to utils/propertyTypes to satisfy
react-refresh lint rule. All tests pass, no behavior change.
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract ensure_gitignore from init_repo so it can be reused by
clone_repo (GitHub "Create New" flow) and repair_vault. New vaults
created via any path now get .DS_Store excluded by default.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Accept Promise<unknown> instead of Promise<void> to match the actual
reloadVault return type (Promise<VaultEntry[]>).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers: TitleField visibility, filename indicator on focus, no
migration banner when vault is flat, CSS rule hiding H1 in editor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- scan_vault now scans root + protected folders only (no deep recursion)
- vault_health_check command detects stray files and title mismatches
- Wikilink resolution: multi-pass with filename stem priority
- TitleField: dedicated title UI above editor, H1 hidden via CSS
- Migration banner: detects subfolders on vault open, offers flatten
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On vault load, detects files in non-protected subfolders via
vault_health_check. Shows an amber banner offering to flatten them
to the vault root. After migration, reloads the vault automatically.
The banner can be dismissed without migrating.
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>
Multi-pass resolution now prioritizes filename stem (strongest) over
alias over title. Removes path-based matching (e.path.endsWith). Legacy
path-style targets like "person/alice" still work by extracting the
last segment "alice" and matching by filename/title.
This matches the flat vault convention where filename IS the note's
identity — filename stem is always slugify(title).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Detects two classes of vault issues:
1. Stray files in non-protected subfolders (won't be scanned)
2. Filename-title mismatches (filename ≠ slugify(title))
Returns a VaultHealthReport with stray_files and title_mismatches.
Registered as a Tauri command for frontend use.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Flat vault enforcement: scan_vault now only picks up .md files at the
vault root and inside protected folders (type/, config/, attachments/,
_themes/, theme/). Files in arbitrary subfolders are no longer indexed.
This prevents stray files from being included and enforces the flat
vault convention where all notes live at the root.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Notes with only headings/rules after H1 (e.g. project templates, daily
notes) previously showed empty snippets. Now extract_snippet collects
sub-heading text as fallback. Also hides the snippet div when empty to
avoid blank gaps in the note list. Bumps cache version to 8 for rescan.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The banner is now rendered as a flex sibling below the list container,
ensuring it displays correctly in both unit tests (JSDOM) and real
browsers (Playwright/Chromium).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When notes are permanently deleted, the Changes note list now shows a
"N notes deleted" banner so the counter matches the visible list items.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use broader .text-muted-foreground selector instead of .text-[12px]
which may not match in all contexts. Check for text length > 15 to
distinguish snippets from short date strings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Snippet extraction was including raw list markers (* , - , + , 1. ) in
the preview text, producing ugly leading spaces. Notes whose snippets
were cached before this fix showed stale/incorrect previews.
- Add strip_list_marker() in both Rust and TS to remove bullet/ordered
list prefixes before snippet assembly
- Trim final snippet to remove leading/trailing whitespace
- Bump CACHE_VERSION 6 → 7 to force full rescan on next vault load,
ensuring all entries get clean snippets
- Add Rust + Vitest tests for list marker stripping
- Update Playwright smoke test with stricter snippet assertions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: handleCreateNoteForRelationship used `await persistNewNote()`
which forced an early React flush. The subsequent frontmatter update
(onAdd/onAddProperty) triggered setTabs in a microtask that collided with
the render batch, causing a radix-ui infinite setState loop
("Maximum update depth exceeded") and a blank white screen.
Two fixes applied:
1. Make handleCreateNoteForRelationship synchronous (fire-and-forget
persistence) to keep all state updates batched — mirrors the working
handleCreateNoteImmediate pattern.
2. Defer onAdd/onAddProperty via setTimeout(0) so the frontmatter update
runs after the tab-switch render completes, avoiding the radix-ui
ref composition loop.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use .first() when selecting type option to handle demo vaults with
duplicate type names in the dropdown.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WKWebView doesn't auto-invalidate ::before/::after styles when CSS custom
properties change on document.documentElement. Add `void root.offsetHeight`
to force reflow. Also add a version counter in useThemeApplier to prevent
stale async fetches from overwriting live-reload CSS vars.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Simplify flatten_vault API to return usize instead of MigrationResult struct
- Add KEEP_FOLDERS: attachments/ and _themes/ alongside type/, config/, theme/
- Use HashSet for collision tracking in unique_filename
- Update wikilinks from path-based [[folder/slug]] to title-based [[slug]]
- Clean up empty directories after flattening
- Flatten demo-vault-v2: move all notes from type-based subfolders to root
- Update smoke tests for flat vault structure
- Remove migrate_to_flat_vault from repair_vault (one-time migration only)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: Tauri's dragDropEnabled (default: true) intercepts drag events
at the webview level, preventing HTML5 DnD from working for tab reorder
and BlockNote block handle drag. Setting dragDropEnabled: false lets all
drag events flow through the standard DOM API.
Image drops now use the HTML5 drop handler + uploadImageFile (same as
paste-upload) instead of Tauri's onDragDropEvent + copyImageToVault.
Removed the useInternalDragFlag workaround and padding hack.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The demo vault doesn't contain a note with 'Refactoring' in the title,
causing consistent timeout failures in the pre-push smoke tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes for the image drop overlay interference:
1. Block handle clipping: Add padding (0 4px) to editor container so
BlockNote's side menu (42px) fits within the overflow clip edge.
overflow-y:auto forces overflow-x:auto (CSS spec), which was clipping
the menu 2px past the container's left boundary.
2. Block handle click interference: Extract isInteractiveTarget() to
exclude .bn-side-menu from handleContainerClick — prevents the
container from stealing focus when clicking drag handle or add button.
3. Internal drag isolation: Track document-level dragstart/dragend to
flag internal HTML5 drags (tabs, blocks). Tauri onDragDropEvent
handler skips entirely during internal drags to prevent interference.
Extract useInternalDragFlag() and handleTauriDrop() to keep
useImageDrop under CodeScene complexity threshold.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove move_note_to_type_folder function and all its tests
- Remove MoveResult, type_to_folder_slug from rename.rs
- Remove infer_type_from_folder, capitalize_first, title_case_folder
- These were all made dead by the flat vault migration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verifies AI panel (3-layer structure, blue glow, context bar, Escape close),
search UI accessibility, Repair Vault command, and no /api/ai/agent fetch calls.
All 7 audited tasks pass: qmd bundling, MCP foundation, AGENTS.md bootstrap,
AI panel rendering, Claude API wiring, AI panel UI, endpoint fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>