* fix: show type label for all notes in wiki-link autocomplete
Notes with the default 'Note' type (isA: null) had noteType set to
undefined, hiding their type label. Now every note always shows its
type in the autocomplete dropdown.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: add wiki-autocomplete-type-fix wireframe
Shows the fixed autocomplete dropdown where every note displays its
type label, including default "Note" types in muted color.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: show note type in relations autocomplete dropdown
Replace HTML datalist in InlineAddNote and AddRelationshipForm with
custom NoteAutocomplete component that displays note types as colored
badges, matching the wiki-link autocomplete UX. Supports keyboard
navigation, scrollable results, and graceful truncation.
Also extract ref-group helpers to improve InspectorPanels code health
(8.99 → 9.38).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: add relations autocomplete dropdown wireframe
Shows the autocomplete dropdown with note type badges (Project, Topic,
Procedure, Person) matching the wiki-link autocomplete UX. Includes
edge case of note with no type badge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Backlinks previously relied on scanning allContent (raw markdown), which
was empty in Tauri mode until notes were explicitly saved. Now outgoing
wikilink targets are extracted during Rust vault scan and stored on
VaultEntry. The frontend useBacklinks hook uses this indexed data, and
outgoingLinks update in real-time on content change.
- Add extract_outgoing_links() in Rust parsing + outgoing_links field on VaultEntry
- Add extractOutgoingLinks() TypeScript utility for real-time updates
- Rewrite useBacklinks to use outgoingLinks instead of scanning raw content
- Add cache version invalidation to force rescan on format change
- Extract useEditorSaveWithLinks hook to keep App.tsx under code health threshold
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add search backend (qmd CLI) and design file
- Add search.rs: qmd integration for keyword (BM25), semantic (vsearch),
and hybrid search modes via CLI JSON output
- Register search_vault Tauri command in lib.rs
- Design file with 3 frames: empty, results, no-results states
- Fix pre-existing test failures: install @tauri-apps/plugin-opener,
add mock in test setup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add SearchPanel UI with Cmd+Shift+F shortcut
- SearchPanel component: full-text search overlay with keyword/semantic
mode toggle, debounced search (200ms), arrow key navigation, result
count + elapsed time display, note type badges from vault entries
- Cmd+Shift+F shortcut registered in useAppKeyboard
- Mock search_vault handler in mock-tauri for browser dev mode
- SearchResult/SearchResponse types in types.ts
- Tests: 15 new tests for SearchPanel + 2 for keyboard shortcut
- scrollIntoView mock in test setup for jsdom compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct SearchPanel imports for Tauri/browser dual-mode
Use invoke from @tauri-apps/api/core and mockInvoke from mock-tauri
with a searchCall wrapper, fixing the TypeScript build error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: make test_detect_collection_fallback robust when qmd not installed
* fix: reset localStorage between App tests to prevent view mode state leak
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- 8 unit tests for filterPersonMentions utility
- 6 integration tests for @ trigger in Editor component
- Verifies Person-only filtering, wikilink insertion, type badges
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Type @ in editor to trigger person-specific autocomplete
- Filters vault entries to show only Person type notes
- Inserts standard [[Person Name]] wikilink on selection
- Lower query threshold (1 char vs 2) since person list is smaller
- Add 3 more Person entries to mock data for testing
- Reuses existing WikilinkSuggestionMenu component
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: relation chips now display correct type color instead of default blue
Root cause: resolveRef matched entries by path/filename only, while
wiki-links used findEntryByTarget (title, filename, aliases). When a
wikilink target used the note title (differing from filename), resolveRef
failed → null type → default blue.
Changes:
- resolveRef now calls findEntryByTarget first (title/alias match)
- Default color for typeless notes changed from blue to neutral grey
- TypeSelector passes custom color key from Type entries
- DynamicPropertiesPanel refactored to reduce cyclomatic complexity
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* design: add before/after visual for relations type color fix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add task completion summary
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
countWords() was including the title line (# Heading) in the count,
inflating it by ~2-3 words. Now strips the first H1 heading and
[[wikilinks]] before counting, so only body prose is tallied.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
App.tsx passed getNoteStatus but not modifiedFiles to NoteList.
The Changes view uses modifiedPathSet (derived from modifiedFiles) to
filter entries, so it was always empty — showing "no pending changes"
even with modifications present.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace window.open() with Tauri's opener plugin (openUrl) so that
clicking a URL property in the Properties panel opens the system
browser instead of failing silently or triggering edit mode.
- Install @tauri-apps/plugin-opener (npm + Cargo + capabilities)
- Add openExternalUrl() utility with Tauri/browser fallback
- Add stopPropagation to prevent accidental edit mode activation
- Add double-click guard (500ms debounce via ref)
- Update tests to mock openExternalUrl instead of window.open
- Add explicit test that URL click does not trigger onStartEdit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ReadOnlyType and TypeSelector containers were missing px-1.5, causing the
Type label to sit flush-left while all other property/info rows were
indented 6px.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The PR #43 text-size reduction missed the UrlValue component — both its
display span and edit input kept the old larger size. Align them with
the 12px used by EditableValue.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch wiki-link autocomplete from BlockNote's default grouped suggestion
menu to a custom flat list sorted by relevance. Each item shows the note
type (with its accent color) discretely on the right side. Reduce
MAX_RESULTS from 20 to 10 for a cleaner, more focused list.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Raycast-style command palette with fuzzy search across all app actions.
Groups commands by category (Navigation, Note, Git, View, Settings)
with keyboard shortcuts displayed inline. Contextual actions (trash,
archive, save) are only available when a note is open.
Extracts fuzzyMatch into shared utility for reuse.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The green dot (new note indicator) was disappearing after Cmd+S because
markSaved cleared it from the in-memory newPaths set. Now resolveNoteStatus
derives "new" status from git status (untracked/added files) so the green
dot persists until the note is committed. The in-memory tracker is only
used for the brief window between note creation and first save to disk.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BlockNote's SuggestionMenu uses item.title as React key, causing duplicate
rendering and broken arrow-key navigation when multiple notes share the
same title. Fix by:
- Adding path-based deduplication to filter out duplicate entries
- Disambiguating same-title notes with parent folder name (e.g. "Standup (work)")
- Deduplicating aliases within each item (filename vs entry.aliases overlap)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The inspector panels were calling getTypeColor/getTypeLightColor/getTypeIcon
without passing the custom color/icon overrides from the Type entry, causing
all notes to render with the default blue color regardless of their type.
Now builds a typeEntryMap in Inspector and threads it through to all panels,
matching how NoteList correctly resolves type colors and icons.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The previous commit replaced modifiedFiles with getNoteStatus, but main
had tests for the 'changes filter' view that depend on modifiedFiles.
This commit restores full backward compat:
- Both modifiedFiles and getNoteStatus props are accepted
- getNoteStatus takes precedence when provided (used by App.tsx)
- modifiedFiles automatically derives status='modified' when getNoteStatus
is not provided (used by tests and legacy callers)
- isChangesView / 'Changes' header / changes filter all restored
NoteItem continues to use noteStatus prop (new | modified | clean),
so both green (new) and orange (modified) dots work correctly.
New notes created in-session show a green dot; existing notes with
uncommitted git changes show an orange dot. Saving a new note clears
the green dot; git commit clears the orange dot.
Introduces NoteStatus type ('new' | 'modified' | 'clean'), extracts
useNewNoteTracker hook and resolveNoteStatus pure function, and adds
onNoteSaved callback to useEditorSave for clearing new-note tracking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extract icon registry to src/utils/iconRegistry.ts with 290 curated Phosphor icons
- Add real-time search field that filters icons by name substring
- Show "No icons found" empty state when search yields no results
- Scrollable icon grid (240px max height) for browsing all icons
- Add teal and pink accent colors to the palette (8 total)
- Widen popover from 264px to 280px for better search field fit
- Update tests: search filtering, empty state, icon count validation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Clicking "N pending" in the status bar or the "Changes" nav item in
the sidebar shows a filtered list of notes with uncommitted changes.
The view updates in real-time as files are saved or committed.
- Add 'changes' filter to SidebarSelection type
- Make StatusBar "N pending" indicator clickable
- Add "Changes" NavItem in sidebar (visible when modifiedCount > 0)
- Filter NoteList by modifiedFiles paths for changes view
- Show "No pending changes" empty state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Property values (dates, URLs, text) were inheriting the 14px root font
size instead of using the 12px size already applied in InfoRow and other
inspector sections. Standardize all property value and editing input
text to 12px for visual consistency.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add UrlValue component: click opens in browser, pencil icon for edit
- URL detection via isUrlValue() (http/https URLs + bare domains)
- URL normalization: prepends https:// to bare domains
- Malformed URLs silently ignored (no open attempt)
- Long URLs truncated visually but full URL opens on click
- Edit mode via pencil icon or keyboard (Enter/Escape/blur)
- 22 new tests covering detection, normalization, and component behavior
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Type field in the Properties panel was read-only. Now it uses a
Radix Select dropdown that lists all vault types (entries where
isA === "Type"), plus a "None" option to clear the type. Wired to
the existing onUpdateProperty('type', ...) flow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add remove (X) button on hover for each related note in a relationship
group, and inline add control with autocomplete to add new notes to
existing relations. Changes update frontmatter via the existing
update/delete property pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The DynamicPropertiesPanel had a local countWords that used a regex
(/^---[\s\S]*?---/) which could match dashes inside frontmatter values,
leaving part of the frontmatter in the body and inflating the count.
Replace with the canonical countWords from utils/wikilinks.ts which uses
splitFrontmatter (line-based indexOf) and correctly finds the closing
delimiter on its own line.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The autocomplete menu for wiki-links was opening immediately on typing
'[[', processing all 9000+ vault entries and causing a visible freeze.
- Extract preFilterWikilinks utility with MIN_QUERY_LENGTH=2 gate
- Pre-filter entries with case-insensitive substring match before
creating expensive onItemClick closures
- Cap results at MAX_RESULTS=20 after BlockNote's filterSuggestionItems
- Add comprehensive tests for the utility and Editor integration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tauri invoke errors are strings (not Error instances). The new test
verifies the frontend displays the actual backend error message.
Also tests that the login button is disabled during the OAuth flow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The frontend was swallowing Tauri backend errors (which are strings,
not Error instances) and always showing generic "Failed to start login."
Now the actual error message is displayed. Also adds User-Agent header
to device flow requests, a clear error message for 404 responses, and
a test for the 404 case.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Drag cancel (dragEnd without drop) does not trigger reorder
- Drag from last tab toward first produces correct reorder
- Active tab path is preserved after reorder
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handleDrop and handleDragOver closed over dragIndex/dropIndex state
values. When the last dragover and drop events fire in the same React
render cycle, the drop handler reads stale state (often null), causing
computeDropTarget to return null and skip the reorder.
Mirror dragIndex/dropIndex into refs that are updated synchronously
alongside setState. Event handlers now read from refs, ensuring they
always see the latest values regardless of React's batching schedule.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The tab-swap useEffect in Editor.tsx watched [activeTabPath, tabs, editor].
When save updated tabs via setTabs, the effect re-ran and re-applied stale
cached blocks from the initial tab load, visually reverting the editor.
Subsequent edits would then save old content, effectively losing changes.
Fix: skip the block swap when activeTabPath hasn't changed (only tab content
was updated). Also refresh the cache with current editor blocks so a later
tab switch doesn't revert to stale content.
Added regression tests for the save → update round-trip (JS + Rust).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The useImageDrop hook's handleDrop was uploading files and inserting
image blocks, but BlockNote already has a native dropFile ProseMirror
plugin that does the same via editor.uploadFile — causing duplicate
uploads and image block insertions on every drop.
Fix: handleDrop now only resets the visual overlay state. BlockNote's
native handler handles the actual upload (which calls uploadImageFile)
and block insertion with proper drop-position support.
Also fix build: exclude test files from tsconfig.app.json (test files
use vi.Mock types from vitest, not available in the app build config).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>