- StatusDropdown: change anchor element from <div> to <span> to fix
invalid HTML nesting inside <span> parents. Browsers auto-correct
<div> inside <span> by ripping the element out, breaking
anchorRef.parentElement positioning. JSDOM doesn't auto-correct,
so tests passed but the real app's dropdown failed to position.
- StatusValue: revert shrink-0 back to min-w-0 so the status pill
can truncate in narrow panels instead of overflowing.
- PropertyRow: add min-w-0 and gap-2 for proper flex overflow,
wrap property key in truncate span.
- AddPropertyForm: add flex-wrap, reduce input widths, set
min-w-[60px] on value inputs for narrow panel support.
- InfoRow/TypeSelector/ReadOnlyType: add min-w-0 and gap-2.
- Tests: increase timeout for 3 Radix Select interaction tests
that are slow in JSDOM.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract heading-stripping logic into `extractEditorBody` — fixes a bug
where the regex failed to strip the title heading from newly created
notes (because `splitFrontmatter` left a leading newline that the
regex didn't account for).
Add a fast path in `doSwap` for empty body content: when the body is
empty (common case for new notes), skip the potentially-async
`tryParseMarkdownToBlocks` pipeline entirely and apply a single empty
paragraph block synchronously. This eliminates the multi-second delay
caused by BlockNote's async markdown parser initialization.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The positionDropdown callback ref ran during React's commit phase
before anchorRef was assigned (children refs commit before parents),
so the dropdown was never positioned and the invisible backdrop
stole all subsequent clicks. Switch to useLayoutEffect which runs
after all refs are set.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the sidebar is hidden (editor-list view mode), the NoteList becomes
the leftmost panel and its header title overlaps with macOS traffic lights.
Add 80px left padding to the NoteList header when sidebarCollapsed is true,
matching the sidebar title bar's existing traffic light clearance.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use portal-based positioning for property panel dropdowns
StatusDropdown and DisplayModeSelector used absolute positioning
within the Inspector's overflow-hidden container, causing dropdowns
to be clipped when the Properties panel is narrow (200-280px).
Both now render via createPortal into document.body with fixed
positioning calculated from the trigger element's bounding rect.
This matches the pattern used by existing Radix UI components
(Select, Popover) in the codebase.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: property dropdown narrow panel fix — before/after frames
---------
Co-authored-by: Laputa App <laputa@app.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
New notes created via Cmd+N now stay in memory until the user
explicitly saves with Cmd+S. This eliminates the disk-write
blocking delay and lets the cursor appear instantly.
- Add 'unsaved' to NoteStatus; blue dot + italic title in tab bar
- useUnsavedTracker in useVaultLoader manages unsaved path set
- useNoteActions skips persist on create, cleans up on close
- useEditorSave accepts unsavedFallback for first-save scenario
- App.tsx wires tracking via contentChangeRef + clearUnsaved
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the first-line-of-content snippet in search results with
a metadata row showing: relative date, created date (when different
from modified), word count, and outgoing link count.
Uses the existing formatSearchSubtitle() helper. Entry lookup now
stores full VaultEntry for metadata access. Graceful degradation
when fields are missing/zero.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Popover and Select components: add collisionPadding=8 as default
- TypeSelector and AddPropertyForm SelectContent: position=popper, side=left
- DateValue and AddDateInput PopoverContent: side=left
Dropdowns now flip direction when hitting viewport edge instead of clipping.
Bug 1: Replace plain-text Radix Select in AddStatusInput with the same
StatusDropdown component used for existing status properties. Both now
show colored pills, search, and custom status creation.
Bug 2: Add color swatch dot next to each status option in the dropdown.
Clicking it opens an inline palette of 8 accent colors that persist via
localStorage (leveraging existing setStatusColor infrastructure).
Refactor StatusDropdown to extract useStatusFiltering, useStatusKeyboard
hooks and VaultSection/SuggestedSection/CreateSection sub-components to
keep Code Health above 9.2 threshold.
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- selectRange() now includes the currently open note as the anchor
- Added setAnchor() to track which note is the selection start
- Added select-none to NoteList to prevent browser text selection on Shift+click
Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
BlockNote uses z-index: 11000 for its toolbar/popover elements.
The Radix UI Select and Popover portals used z-50 (z-index: 50),
causing all inspector dropdowns (type selector, property type picker,
date pickers) to render behind the editor. Raised to z-[12000] to
match the existing StatusDropdown and DisplayModeSelector z-index.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add color dot indicators to each status option in the dropdown. Clicking
the dot opens an inline palette of 8 accent colors (from the design system).
Color assignments persist in localStorage and override the built-in defaults.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
The metadata subtitle (date + word count) from #94 was only intended for
search results. Reverts NoteItem and PinnedCard to show first 2 lines of
note content with a date line underneath.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
The TypeSelector on existing properties used a colored pill with no
border, while the add-property form used a bordered muted-bg control.
Align both to the add-property style: 26px height, visible border,
muted background, 4px radius.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* design: search subtitle metadata layout with 2 frames
Frame 1: Search result items with metadata subtitle line showing
modified date, word count, and link count below the snippet.
Frame 2: NoteList items with enhanced subtitle including link count.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add metadata subtitle to search results and note list items
- formatSubtitle now shows link count (e.g. "2h ago · 342 words · 5 links")
- New formatSearchSubtitle shows full metadata in search results:
modified date, created date, word count, and link count
- SearchPanel renders metadata line under each search result snippet
- Mock entries updated with realistic outgoingLinks data
- 11 new tests covering formatSearchSubtitle and search result metadata
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract SearchResultItem and SearchResultsList from SearchContent
Reduces cyclomatic complexity from 24 to manageable levels by
splitting the monolithic SearchContent into three focused components.
Code Health: 8.97 -> 9.23.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.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>
* design: add multi-select notelist wireframes
Three frames: selected notes with bulk action bar, Shift+click range
select, and empty selection / hover states.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add multi-select notes with bulk archive and trash actions
Cmd/Ctrl+Click toggles individual note selection, Shift+Click selects
a range, Escape clears. A bulk action bar appears at the bottom with
Archive and Trash buttons. Includes useMultiSelect hook, BulkActionBar
component, Rust batch_archive_notes/batch_trash_notes commands, and
9 new tests covering the multi-select behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: trigger GitHub Actions run
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
The commit hash link in the status bar used an <a href> tag which doesn't
open external URLs in Tauri's webview. Replaced with onClick handler that
calls openExternalUrl (Tauri opener plugin in native, window.open in browser).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add pendingSave status indicator for new note creation
Track disk write state during note creation with a pulsing green dot
on tab and breadcrumb bar. The pending state resolves to 'new' once
the file is written to disk, giving users clear feedback during the
async save.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for pendingSave status lifecycle
Cover resolveNoteStatus priority, createAndPersist callbacks,
TabBar pulsing dot indicator, and BreadcrumbBar "Saving…" text.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add design frames for new note creation pending save state
Two frames in design/new-note-creation.pen:
- Pending save: pulsing green dot, italic title
- Saved: static green dot, normal title
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: add new-note-creation frames (pending save + after first save)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Property value inputs now adapt to their detected or overridden display mode:
- Date properties show a calendar date picker (inline and add form)
- Boolean properties show a yes/no toggle (handles string "true"/"false")
- Status properties show a dropdown with vault + suggested statuses
- Null/empty values are now routed through display mode instead of defaulting to text
Refactored SmartPropertyValueCell and usePropertyPanelState for code health 10.0.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
SelectValue already renders the icon from the selected SelectItem,
so the explicit Icon in SelectTrigger caused the type icon to appear twice.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Rename Calendar import from lucide-react to CalendarIcon2 to avoid
conflict with Calendar from @/components/ui/calendar
- Add tsc --noEmit and pnpm build steps to CI to catch type/bundler
errors before tests run (prevents this class of bug from reaching main)
PR #98 added react-day-picker via shadcn/ui Calendar but the package
was not yet installed in node_modules. This broke 5 test files at import
resolution time:
- src/App.test.tsx
- src/components/Editor.test.tsx
- src/components/Inspector.test.tsx
- src/components/InspectorPanels.test.tsx
- src/components/DynamicPropertiesPanel.test.tsx
Fix: add a vitest mock for react-day-picker in the global test setup
(same pattern as the existing react-virtuoso mock). DayPicker renders
a real calendar widget that requires DOM APIs unavailable in jsdom —
mocking it to null is the correct approach for unit/integration tests
that don't test date-picker behavior specifically.
Before: 5 failed | 48 passed (764 tests)
After: 0 failed | 53 passed (929 tests)
* feat: show metadata subtitle (date + word count) instead of snippet
Replace the note list subtitle with a compact metadata summary showing
relative date and word count (e.g. "2d ago · 342 words") instead of
the first paragraph of note content. Adds word_count to VaultEntry on
the Rust backend, computed during vault scan.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: cargo fmt formatting
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: hide empty backlinks, referenced-by, and relations sections
When a note has no backlinks, referenced-by entries, or relations,
the corresponding inspector sections are now completely hidden instead
of showing "No backlinks" / "No references" / "No relationships" labels.
This keeps the inspector clean and focused on actual content.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: add before/after mockup for hide-backlinks-empty
Shows inspector panel comparison: cluttered empty state labels (before)
vs clean hidden sections (after).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: add before/after wireframes for hide-backlinks-empty
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: show last commit hash in status bar, remove branch name
Replace the hardcoded "main" branch display with a clickable short SHA
linking to the GitHub commit. Add Rust get_last_commit_info command that
returns the last commit hash and constructs a GitHub URL from the remote.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add design/git-status-bar.pen with new status bar layout
Shows the updated status bar with commit hash link, no branch name,
and annotated change callouts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: retrigger — runner 3 pnpm not found (env issue)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* revert: remove titlebar color darkening — superseded by custom drag region
The --bg-titlebar CSS variable and background overrides on the four
header bars (TabBar, SidebarTitleBar, NoteList, Inspector) were added
before the native macOS titlebar was replaced with a custom frameless
drag region. These color changes now serve no purpose and add dead code.
Removes:
- --bg-titlebar CSS variable from index.css
- background: var(--bg-titlebar) from all four header components
- Redundant data-tauri-drag-region attrs (useDragRegion hook handles drag)
- design/titlebar-darker.pen design file
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: retrigger CI for Rust coverage check
* ci: retrigger — runner 3 pnpm path issue (flaky env)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace the native HTML5 date input (hidden <input type="date"> with
showPicker()) with a proper shadcn/ui date picker using Calendar and
Popover components. The new implementation provides:
- Calendar popup with month navigation and keyboard support
- Clear date button in the popover footer
- Calendar icon in the trigger button
- Proper date parsing via parseDateValue helper
Also adds the design file design/date-picker-shadcn.pen with closed
and open state frames.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Type labels in autocomplete dropdowns (wiki-link [[, relation add,
Cmd+P quick open) and search panel now use the light/muted variant
of the type color as background instead of grey. This matches the
existing color convention used in wiki-link chips and inspector panels.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace the vertical grey popup with an inline horizontal form that matches
existing property row styling. Fields: [name] [type dropdown] [value] [✓] [×].
Type dropdown uses icon-left design consistent with the app's Select component.
Includes design file with empty and filled state frames.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve search panel freeze by making search_vault async
The search_vault Tauri command was synchronous (fn, not async fn),
blocking the main thread for 30+ seconds during hybrid/semantic
search on large vaults (9200+ files). This caused the macOS beachball.
Changes:
- Make search_vault async with tokio::spawn_blocking (runs qmd off main thread)
- Cache collection name per vault path (avoid repeated qmd collection list calls)
- Cancel inflight searches and debounce timers when panel closes
- Add regression test for stale result cancellation on panel close
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: add status property dropdown wireframes
Three frames: closed pill state, open dropdown with suggested/vault
statuses, and custom status creation flow.
Also bump flaky NoteList 9000-entry test timeout from 15s to 30s.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: replace status text edit with Notion-style dropdown
- Extract STATUS_STYLES to shared statusStyles.ts utility
- New StatusDropdown component: filterable popover with colored pills
- StatusPill reusable component for consistent status chip rendering
- Vault-wide status aggregation from entries prop
- Dropdown shows "From vault" and "Suggested" sections
- Custom status creation via type-and-Enter
- Escape/backdrop click cancels without saving
- Keyboard navigation (ArrowUp/Down + Enter)
- 22 new tests covering dropdown behavior + integration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: cargo fmt
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
On a 9000+ entry vault, creating a new note (Cmd+N) was slow because
the entries state update triggered expensive re-computations in NoteList
(filter + sort), Sidebar (type counts), and Editor (wikilink suggestions)
— all blocking the tab from appearing.
Fix: wrap setEntries/setAllContent/trackNew in React's startTransition so
they run as low-priority updates. The tab creation (setTabs/setActiveTabPath)
remains high-priority and renders in <50ms. The entries update is deferred
to idle time without blocking the UI.
Also reorder createAndPersist to call openTab before addEntry, making the
intent explicit: tab appears first, vault index updates second.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The InspectorHeader was missing shrink-0, allowing flex compression to
shrink it below the intended 52px. Also moved overflow-y-auto from the
aside to the content div so the header stays pinned while content
scrolls. Extracted refsMatchTargets() helper to reduce nesting depth
(Code Health 8.92 → 9.5).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve custom type color and icon in all autocomplete contexts
Custom types (e.g., Evergreen, Recipe) appeared grey in wikilink [[,
@-mention, Cmd+P, and search autocompletes because getTypeColor() was
called without the custom color key from the Type document.
Root causes:
- Editor.tsx: getTypeColor(group) missing typeEntryMap[group]?.color
- useNoteSearch.ts: getTypeColor(e.isA) missing custom color lookup
- SearchPanel.tsx: type badge had no color styling at all
Fixes:
- Extract buildTypeEntryMap to shared utility in typeColors.ts
- Pass custom color key in all autocomplete code paths
- Add type icon (Phosphor) to the left of each result in NoteSearchList
- Add TypeIcon to WikilinkSuggestionItem and NoteAutocomplete
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add design file for autocomplete type color fix
Shows the fixed state: type icon on the left, colored type badge on
the right, untyped notes neutral grey with no icon.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: deduplicate inspector panels by excluding frontmatter from outgoing links
The extract_outgoing_links function was scanning the entire file content
including YAML frontmatter, causing wikilinks in frontmatter fields
(e.g. belongsTo, relatedTo) to appear in both the relationships map AND
outgoingLinks. This made the same note show up in both "Referenced By"
and "Backlinks" panels.
Backend: pass gray_matter's parsed body (no frontmatter) to
extract_outgoing_links instead of raw file content.
Frontend: filter useBacklinks to exclude entries already shown in
Referenced By, ensuring strictly non-overlapping panels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: retrigger after disk space cleanup [skip precommit]
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: darken title bar headers for visual separation from content
Add --bg-titlebar (#EDECE9) CSS variable and apply it to all four
header bars (TabBar, SidebarTitleBar, NoteList header, Inspector
header) so the top drag region is subtly darker than the app content.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add design file for titlebar-darker task
Single frame showing the three-tier color hierarchy:
title bar (#EDECE9) → sidebar (#F7F6F3) → content (#FFFFFF).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add auto-pull vault sync with conflict handling
- Add git_pull, has_remote, get_conflict_files to Rust backend
- Add GitPullResult type and auto_pull_interval_minutes to Settings
- Create useAutoSync hook (pull on launch, focus, periodic interval)
- Update StatusBar with real sync status indicator
- Add pull interval setting to SettingsPanel
- Add mock handler for git_pull
- Update existing tests for new Settings field
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add tests for git pull, settings, and useAutoSync hook
- Add Rust tests: has_remote, git_pull (no_remote, up_to_date, updated),
get_conflict_files, parse_updated_files, GitPullResult serialization
- Add frontend tests: useAutoSync (mount pull, focus pull, conflict,
error, manual trigger, concurrent prevention, no_remote)
- Fix existing settings tests for new auto_pull_interval_minutes field
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: add auto-pull-vault wireframes
Frames showing: sync idle, syncing, conflict indicator,
settings sync section, and conflict toast notification.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add auto_pull_interval_minutes to all Settings literals
Fix build error and test fixtures missing the new field.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: cargo fmt
* ci: re-trigger CI after flaky test
* ci: retrigger after disk space cleanup
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* design: add smart property display wireframes
Frames: date picker, boolean toggle, status badge, display mode override dropdown.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: smart property display — type-aware rendering with display mode override
- Add property type auto-detection (date, boolean, status, url, text) based on value content and property name
- Date properties (ISO strings) show as friendly format (e.g. "Mar 31, 2026") with native date picker on click
- Boolean properties show as toggle buttons
- Status properties auto-detected from key name or known status values, rendered as colored badges
- Each property gets a display mode override dropdown (visible on hover) to force a specific display type
- Display mode overrides persist across sessions via localStorage
- Add mock data with date/boolean fields for testing
- 36 new tests covering type detection, date formatting, localStorage persistence, and UI rendering
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: speed up Cmd+N note creation from ~150ms to ~16ms
Root cause: the editor focus used a fixed 150ms setTimeout after note
creation, even when the BlockNote editor was already mounted. The
optimistic UI (state updates, tab opening) was already synchronous,
but the focus delay dominated perceived latency.
Changes:
- Editor focus now uses requestAnimationFrame when editor is mounted
(~16ms on 60Hz), falling back to 80ms timeout for first-mount only
- Rust save_note_content creates parent directories automatically,
preventing optimistic-UI revert when creating notes in new folders
- Added perf timing markers (console.debug) to measure creation→focus
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: retrigger after disk space cleanup
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>