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>
Add multi-window support: notes can be opened in dedicated secondary
Tauri windows with editor-only layout (no sidebar, no note list).
Triggers: Cmd+Shift+Click on notes, Cmd+K → "Open in New Window",
Cmd+Shift+O shortcut, Note → "Open in New Window" menu bar item.
Secondary windows have their own auto-save, theme, and wikilink
navigation. Closing a secondary window does not affect the main window.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Inbox shows notes without valid outgoing relationships (body wikilinks
or frontmatter refs), helping users find captured but unorganized notes.
Includes time-period filter pills (This week/month/quarter/All time),
Cmd+K command, and macOS menu bar entry. Broken wikilinks (targeting
non-existent notes) are not counted as relationships.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Content is automatically persisted 500ms after the last edit in both
BlockNote and raw editor modes. Cmd+S still works as immediate flush.
Tab close flushes any pending auto-save to prevent data loss.
Updated the "unsaved" tab indicator to show "Auto-saving…" with pulse.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The theme live-reload mechanism was already working correctly — the
previous QA failures were caused by flawed test methodology (modifying
files on disk while the editor had them open, so Cmd+S overwrote with
stale content). Added unit and Playwright tests that verify ALL theme
properties (including bullet-size/color) update when editing the raw
editor buffer and saving. Removed old skipped test file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These properties have dedicated UI elsewhere (trash/archive banner, emoji picker)
and should not appear as generic editable properties.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire notifyThemeSaved through the frontmatter update chain so CSS
variables update immediately when:
- The user edits a theme note in raw mode and presses Cmd+S
- A frontmatter property is changed via the inspector panel
Also expose CodeMirror EditorView on the DOM for Playwright test access,
add unit tests for onNotePersisted, and a new Playwright smoke test
that verifies the full raw-editor → save → CSS-vars-update flow.
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>
Previously restore/unarchive set `Trashed: false` / `archived: false` in
frontmatter. Now uses handleDeleteProperty to remove the fields entirely,
keeping frontmatter clean as if the note was never trashed/archived.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two bugs fixed:
1. CodeMirror raw editor didn't reflect frontmatter changes (e.g. Trashed: true)
after trash/archive because useCodeMirror only used content as initial state.
Added content-sync effect with external-sync guard to prevent infinite loops.
2. Entry actions (trash/archive/restore/unarchive) showed "Property updated" toast
instead of contextual message because runFrontmatterAndApply overwrote it.
Added silent option to suppress toast when caller manages its own feedback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When viewing a type in the sidebar, the note list now shows filter pills
below the header to switch between Open, Archived, and Trashed notes.
Each pill shows a count badge. Bulk actions are context-aware: Trashed
filter offers Restore/Archive/Delete permanently, Archived filter offers
Unarchive/Trash. Cmd+K commands added for switching filters.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
handleSelectNote skipped syncNoteTitle for tabs already open (early
return). Moved sync call into handleSelectNoteWithSync so it always
runs, even when switching to an existing tab via Cmd+P. Also removes
H1 fallback from extract_title — title now comes from frontmatter or
filename only, per the title/filename contract.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove owner, cadence, created_at, and created_time as hardcoded fields from
the Frontmatter struct — they don't drive app logic and belong in generic
properties. Owner and cadence values now flow through to the properties map
(including single-element array unwrapping). Creation date is sourced from
filesystem metadata (birthtime on macOS) instead of frontmatter.
Also fixes title extraction: add H1 heading extraction to extract_title()
(priority: frontmatter title → H1 → filename slug). Previously titles fell
back directly to filename when no frontmatter title was set.
StringOrList is retained for scalar Frontmatter fields as a defensive measure:
YAML values can arrive as single-element arrays (e.g. Status: [Active]) which
would cause entire deserialization to fail without it.
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>
- handleSelectNote syncs title frontmatter before loading content
- handleSelectNoteWithSync reloads entry after open to update display title
- Added sync_note_title to mock handlers
- Fixed rapid-switching test to flush sync microtask
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Repair Vault command now runs flatten_vault() and migrate_is_a_to_type()
before restoring themes and config, ensuring vaults adopt flat structure.
Also fixes config.md type definition to use `type: Type` instead of
legacy `Is A: Type`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
Extract delete/trash management logic (deleteNoteFromDisk, handleDeleteNote,
handleBulkDeletePermanently, handleEmptyTrash, trashedCount, confirmDelete state)
into a focused useDeleteActions hook. Reduces App.tsx from 733 to 672 lines.
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
useBulkActions was embedded in App.tsx with zero test coverage. It handles
bulk archive, trash, and restore operations — all with partial-failure
semantics and toast messaging.
Extract to src/hooks/useBulkActions.ts and add 15 unit tests covering:
- Plural/singular toast messages ("2 notes archived" vs "1 note archived")
- Partial failures: only successful operations counted in toast
- All-failure case: no toast shown
- Empty array: no operations called, no toast
Risk mitigated: silent bugs in batch operations (wrong count in toast,
toast shown when nothing succeeded, partial failure not handled).
Co-authored-by: Test <test@test.com>
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>
- Refactor useClosedTabHistory to store full VaultEntry (not stub) for reliable reopening
- Add data-tab-path attribute to TabItem for precise Playwright selectors
- Add 2 Playwright smoke tests: single close/reopen and empty-history no-op
- Update ARCHITECTURE.md and ABSTRACTIONS.md with closed tab history docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add "Reopen Closed Tab" to File menu with CmdOrCtrl+Shift+T accelerator.
Wire onReopenClosedTab through useAppKeyboard, useMenuEvents,
useAppCommands, and App.tsx.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce useClosedTabHistory hook (LIFO stack, 20-entry cap, dedup)
and integrate it into useTabManagement so handleCloseTab records entries
and handleReopenClosedTab pops them to reopen at original position.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add "Empty Trash…" to the Note menu for discoverability and wire
the menu event through useMenuEvents. Add comprehensive Playwright
smoke test covering trash view navigation, Empty Trash button and
command, confirmation dialog, bulk selection context, and trashed
note banner.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The snippet was extracted once at vault load time (Rust backend) and
never updated when content was saved. Notes created or edited during
a session showed stale/empty snippets until the next app restart.
Added extractSnippet() to the frontend (mirroring Rust logic) and
wired it into useEditorSaveWithLinks so snippet + wordCount are
updated alongside outgoingLinks on every save.
Co-Authored-By: Claude Opus 4.6 <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>
- 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>
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>
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>
- 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>