feat: add backlink hover previews#768
Conversation
WalkthroughThis PR adds side-effect-free wiki-target resolution with explicit unavailable outcomes, desktop wiki-link hover previews, passive markdown rendering, raster-only preview asset serving, and related tests and documentation. ChangesWiki-link hover previews and target resolution
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
Adds passive, local-only hover previews for [[wiki links]] in pointer-capable desktop note panes, while tightening “existing target” resolution so iCloud placeholders or transient read failures are treated as unavailable (not “missing”) to prevent duplicate-note creation and incorrect navigation.
Changes:
- Introduces
resolveExistingWikiTarget(resolved / ambiguous / unavailable / missing) and reuses it across creation, navigation, and capture-related flows. - Wires Meowdown’s
WikilinkHoverCardinto the desktop editor with a passive preview body renderer backed by generation-pinned reads and frontmatter-stripped markdown. - Restricts hover-preview asset loading to sniffed raster-only responses via
?reflect-preview=rasterenforcement in the Tauri asset protocol, and makes Markdown previews optionally non-interactive.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| packages/core/src/graph/resolve-existing-wiki-target.ts | Adds side-effect-free resolver for existing wiki-link targets with bounded disk fallback and “unavailable” outcome. |
| packages/core/src/graph/resolve-existing-wiki-target.test.ts | Comprehensive unit tests for resolver precedence, disk fallback, and unavailable handling. |
| packages/core/src/graph/create-note.ts | Refactors create-guard logic to delegate to resolveExistingWikiTarget, adding unavailable to outcomes. |
| packages/core/src/graph/create-note.test.ts | Updates/extends tests to cover daily outranking and unavailable blocking note creation. |
| packages/core/src/exports/platform.ts | Exports the new resolver and its result type from @reflect/core. |
| packages/core/src/actions/backlink-target.ts | Treats unavailable resolutions as non-blocking for capture/backlinks (keeps requested spelling). |
| packages/core/src/actions/backlink-target.test.ts | Adds test coverage for unavailable backlink target behavior. |
| docs/porting/README.md | Marks backlink hover previews as ported. |
| docs/porting/backlink-hover-previews.md | Updates doc from planned → implemented; records UX and architecture decisions. |
| docs/contributing/editor-architecture.md | Documents the new hover-preview hook and read helper in editor architecture overview. |
| apps/desktop/src/lib/read-existing-note-source.ts | Adds helper to read from live open-session buffer when safe, else generation-pinned disk read. |
| apps/desktop/src/lib/read-existing-note-source.test.ts | Tests for live-buffer vs disk fallback, including stale-buffer refusal. |
| apps/desktop/src/editor/use-wiki-link-navigation.ts | Uses resolveExistingWikiTarget for ISO date navigation when generation is available; surfaces unavailable distinctly. |
| apps/desktop/src/editor/use-wiki-link-navigation.test.tsx | Updates tests to cover new ISO-date resolution behavior and unavailable messaging. |
| apps/desktop/src/editor/use-wiki-link-hover-preview.tsx | Implements Meowdown hover-card body resolver (passive, frontmatter-free, local raster-only assets). |
| apps/desktop/src/editor/use-wiki-link-hover-preview.test.tsx | Tests hover preview resolver behavior, inert rendering, and raster-only asset URL shaping. |
| apps/desktop/src/editor/use-editor-autocomplete.ts | Adds distinct unavailable handling for background create from [[ autocomplete. |
| apps/desktop/src/editor/use-editor-autocomplete.test.tsx | Tests unavailable reporting path for autocomplete create. |
| apps/desktop/src/editor/note-editor.tsx | Adds optional renderWikilinkHoverCard prop and mounts WikilinkHoverCard when provided. |
| apps/desktop/src/editor/note-editor.test.tsx | Verifies hover card mounts only when a renderer is provided and passes the renderer through. |
| apps/desktop/src/editor/markdown-preview.tsx | Adds interactive flag to support passive/inert markdown rendering. |
| apps/desktop/src/components/wiki-link-hover-preview.tsx | Adds the passive hover preview body component (daily header + inert markdown / empty state). |
| apps/desktop/src/components/route-content.test.tsx | Ensures hover renderer is enabled on pointer surfaces and omitted on touch editor surface. |
| apps/desktop/src/components/note-pane.tsx | Wires hover preview renderer into primary note pane only when non-touch and graph session exists. |
| apps/desktop/src-tauri/src/fs/asset_protocol.rs | Enforces raster-only MIME allowlist when reflect-preview=raster query is present; adds tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
apps/desktop/src/editor/use-editor-autocomplete.ts (1)
58-68: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExtract a shared unavailable-note helper
The unavailable create message duplicates the opening-link wording in
use-wiki-link-navigation.ts; a sharedreportUnavailableNoteTitle(operationLabel, title)helper next toreportAmbiguousNoteTitlewould keep the copy aligned and avoid drift.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/desktop/src/editor/use-editor-autocomplete.ts` around lines 58 - 68, Extract a shared reportUnavailableNoteTitle(operationLabel, title) helper alongside reportAmbiguousNoteTitle, and update resolveOrCreateFromAutocomplete to use it for the unavailable outcome. Reuse the helper in use-wiki-link-navigation.ts so opening-link and autocomplete flows share identical unavailable-note messaging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/core/src/graph/resolve-existing-wiki-target.ts`:
- Around line 174-190: The indexedResolution function returns date, title, and
alias index matches without validating note availability. Route indexed paths
through the existing readNote/placeholder availability check used by
dailyFileResolution and matchTitleOnDisk, preserving null for unreadable or
evicted notes before constructing the resolution.
---
Nitpick comments:
In `@apps/desktop/src/editor/use-editor-autocomplete.ts`:
- Around line 58-68: Extract a shared reportUnavailableNoteTitle(operationLabel,
title) helper alongside reportAmbiguousNoteTitle, and update
resolveOrCreateFromAutocomplete to use it for the unavailable outcome. Reuse the
helper in use-wiki-link-navigation.ts so opening-link and autocomplete flows
share identical unavailable-note messaging.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 069274ba-10e6-4fee-b7dc-e50d1cfa26fc
📒 Files selected for processing (25)
apps/desktop/src-tauri/src/fs/asset_protocol.rsapps/desktop/src/components/note-pane.tsxapps/desktop/src/components/route-content.test.tsxapps/desktop/src/components/wiki-link-hover-preview.tsxapps/desktop/src/editor/markdown-preview.tsxapps/desktop/src/editor/note-editor.test.tsxapps/desktop/src/editor/note-editor.tsxapps/desktop/src/editor/use-editor-autocomplete.test.tsxapps/desktop/src/editor/use-editor-autocomplete.tsapps/desktop/src/editor/use-wiki-link-hover-preview.test.tsxapps/desktop/src/editor/use-wiki-link-hover-preview.tsxapps/desktop/src/editor/use-wiki-link-navigation.test.tsxapps/desktop/src/editor/use-wiki-link-navigation.tsapps/desktop/src/lib/read-existing-note-source.test.tsapps/desktop/src/lib/read-existing-note-source.tsdocs/contributing/editor-architecture.mddocs/porting/README.mddocs/porting/backlink-hover-previews.mdpackages/core/src/actions/backlink-target.test.tspackages/core/src/actions/backlink-target.tspackages/core/src/exports/platform.tspackages/core/src/graph/create-note.test.tspackages/core/src/graph/create-note.tspackages/core/src/graph/resolve-existing-wiki-target.test.tspackages/core/src/graph/resolve-existing-wiki-target.ts
| async function indexedResolution( | ||
| match: ExactWikiTargetMatch, | ||
| date: string | undefined, | ||
| generation: number, | ||
| listNoteFiles: ListNoteFiles, | ||
| ): Promise<ExistingMatchResolution | null> { | ||
| if (match.kind === 'date') { | ||
| return resolutionForPaths(match.paths) | ||
| } | ||
| if (date !== undefined) { | ||
| const daily = await dailyFileResolution(date, generation, listNoteFiles) | ||
| if (daily !== null) { | ||
| return daily | ||
| } | ||
| } | ||
| return match.kind === 'missing' ? null : resolutionForPaths(match.paths) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect how indexed matches are produced, and whether placeholder/evicted
# files can appear as indexed title/alias/date matches.
fd -t f 'queries.ts' packages/core/src/indexing
ast-grep outline packages/core/src/indexing/queries.ts --items all --match 'findExactWikiTargetMatches'
rg -n -B3 -A40 'function findExactWikiTargetMatches' packages/core/src/indexing/queries.ts
rg -n 'placeholder' packages/core/src -A3 -B3Repository: team-reflect/reflect-open
Length of output: 30254
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- resolve-existing-wiki-target.ts ---'
sed -n '1,280p' packages/core/src/graph/resolve-existing-wiki-target.ts
echo
echo '--- indexer.ts (placeholder handling) ---'
sed -n '300,350p' packages/core/src/indexing/indexer.ts
echo
echo '--- resolve-existing-wiki-target.test.ts around the indexed/unavailable cases ---'
sed -n '180,280p' packages/core/src/graph/resolve-existing-wiki-target.test.tsRepository: team-reflect/reflect-open
Length of output: 13165
Check readability before returning indexed matches
indexedResolution returns indexed date/title/alias hits without the readNote/placeholder gate that dailyFileResolution and matchTitleOnDisk apply. A stale index row can therefore report resolved for an evicted or otherwise unreadable note. Route indexed hits through the same availability check before returning.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/core/src/graph/resolve-existing-wiki-target.ts` around lines 174 -
190, The indexedResolution function returns date, title, and alias index matches
without validating note availability. Route indexed paths through the existing
readNote/placeholder availability check used by dailyFileResolution and
matchTitleOnDisk, preserving null for unreadable or evicted notes before
constructing the resolution.
Resting the pointer on a wiki link in a desktop note pane now shows a passive, local-only preview of the existing target, built on meowdown 0.46's async
WikilinkHoverCardand a new side-effect-freeresolveExistingWikiTargetresolver whoseunavailableoutcome also stops navigation, autocomplete, and capture from minting duplicate notes for iCloud placeholders or transient read failures.Supersedes #756.
Summary by CodeRabbit