Repository navigation
Hover previews for node references #34
Copy link
Copy link
Closed
Labels
enhancementNew feature or requestNew feature or request
Description
Implement interactive decorations for node references within TextNode content, including hover previews, visual indicators, and contextual actions built on precise character positioning.
Dependencies
- Mock Element Positioning System for Precise Cursor Handling #33 (Mock element positioning system required for accurate decoration placement)
Related Issues
- Hybrid Markdown Rendering System - Live Preview with Syntax Visibility #26 (Hybrid markdown system provides styling foundation)
Self-Contained Scope
- Complete reference parsing and decoration system for multiple reference types
- Interactive hover preview components with contextual information
- Visual decoration overlays with precise positioning integration
- Mock services for node/person/task lookups during independent development
- Comprehensive testing with various reference types and interaction scenarios
- Documentation and usage examples for reference syntax and behaviors
Implementation Approach
- Reference Detection: Parser for
[[nodes]],@people,#tasks, external links - Decoration Overlay: Positioned elements over textarea content using mock element coordinates
- Preview Components: Interactive cards showing contextual information for each reference type
- Mock Services: Independent data layer for reference lookups and preview content
- Testing Strategy: Interaction tests + visual positioning tests + performance benchmarks
- Demo/Documentation: Working examples of all reference types with preview functionality
Requirements
- Reference Types: Support
[[Node Title]],@person-name,#task-name,https://links - Visual Indicators: Distinct styling and icons for each reference type
- Hover Previews: Rich preview cards with contextual information and quick actions
- Precise Positioning: Decorations accurately positioned over reference text
- Interactive Actions: Click behaviors for navigation, editing, and copying references
- Performance: Decoration rendering < 100ms for smooth user experience
Technical Implementation Notes
Step 1: Reference Parser
// ReferenceParser.js
export const REFERENCE_PATTERNS = {
nodeReference: /\[\[([^\]]+)\]\]/g,
personReference: /@(\w+(-\w+)*|\[([^\]]+)\])/g,
taskReference: /#(\w+(-\w+)*|\[([^\]]+)\])/g,
externalLink: /(https?:\/\/[^\s]+)/g,
markdownLink: /\[([^\]]+)\]\(([^)]+)\)/g
};
export function parseReferences(content) {
const references = [];
for (const [type, pattern] of Object.entries(REFERENCE_PATTERNS)) {
let match;
const regex = new RegExp(pattern);
while ((match = regex.exec(content)) \!== null) {
references.push({
type,
text: match[0],
content: match[1] || match[0],
startIndex: match.index,
endIndex: match.index + match[0].length
});
}
}
return references.sort((a, b) => a.startIndex - b.startIndex);
}Step 2: Decoration Overlay System
<\!-- DecorationOverlay.svelte -->
<script>
import { parseReferences } from './ReferenceParser.js';
import NodePreview from './previews/NodePreview.svelte';
import PersonPreview from './previews/PersonPreview.svelte';
import TaskPreview from './previews/TaskPreview.svelte';
export let content = '';
export let mockElement;
export let textareaElement;
let hoveredReference = null;
let previewPosition = null;
let hoverTimeout;
$: references = parseReferences(content);
$: decorations = references.map(ref => ({
...ref,
position: getCharacterPosition(mockElement, ref.startIndex),
endPosition: getCharacterPosition(mockElement, ref.endIndex - 1)
}));
function handleReferenceHover(reference, event) {
clearTimeout(hoverTimeout);
hoverTimeout = setTimeout(() => {
hoveredReference = reference;
previewPosition = { x: event.clientX, y: event.clientY };
}, 500); // 0.5s delay like Logseq
}
function handleMouseLeave() {
clearTimeout(hoverTimeout);
hoveredReference = null;
previewPosition = null;
}
</script>
<div class="decoration-overlay">
{#each decorations as decoration}
<div
class="reference-decoration reference-{decoration.type}"
style="
left: {decoration.position.left}px;
top: {decoration.position.top}px;
width: {decoration.endPosition.left - decoration.position.left}px;
height: {decoration.position.height}px;
"
on:mouseenter={e => handleReferenceHover(decoration, e)}
on:mouseleave={handleMouseLeave}
on:click={e => handleReferenceClick(decoration, e)}
>
<\!-- Visual decoration overlay -->
</div>
{/each}
{#if hoveredReference && previewPosition}
<div class="reference-preview" style="left: {previewPosition.x}px; top: {previewPosition.y}px;">
{#if hoveredReference.type === 'nodeReference'}
<NodePreview nodeTitle={hoveredReference.content} />
{:else if hoveredReference.type === 'personReference'}
<PersonPreview personId={hoveredReference.content} />
{:else if hoveredReference.type === 'taskReference'}
<TaskPreview taskId={hoveredReference.content} />
{/if}
</div>
{/if}
</div>Step 3: Preview Components with Mock Data
<\!-- previews/NodePreview.svelte -->
<script>
import { mockNodeService } from '$lib/services/mockNodeService';
export let nodeTitle;
let nodeData = null;
let loading = true;
onMount(async () => {
try {
nodeData = await mockNodeService.findNodeByTitle(nodeTitle);
loading = false;
} catch (error) {
loading = false;
}
});
function handleOpenNode() {
// Mock navigation - will integrate with real routing later
console.log('Navigate to node:', nodeTitle);
}
function handleCopyReference() {
navigator.clipboard.writeText(`[[${nodeTitle}]]`);
}
</script>
<div class="node-preview-card">
{#if loading}
<div class="preview-loading">Loading...</div>
{:else if nodeData}
<div class="preview-header">
<h4>{nodeData.title}</h4>
<span class="node-type">{nodeData.type}</span>
</div>
<div class="preview-content">
{nodeData.content.substring(0, 150)}{nodeData.content.length > 150 ? '...' : ''}
</div>
<div class="preview-actions">
<button on:click={handleOpenNode}>Open</button>
<button on:click={handleCopyReference}>Copy Ref</button>
</div>
{:else}
<div class="preview-error">Node not found: {nodeTitle}</div>
{/if}
</div>File Modifications Required:
- Create:
ReferenceParser.jsfor syntax detection - Create:
DecorationOverlay.sveltemain component - Create: Preview components (
NodePreview.svelte,PersonPreview.svelte, etc.) - Create:
mockNodeService.jsfor independent development - Modify:
BaseNode.svelteto include decoration overlay - Add: CSS styles for decorations and preview cards
Mock Services Pattern:
// mockNodeService.js - Enable independent development
export const mockNodeService = {
async findNodeByTitle(title) {
// Simulate network delay
await new Promise(resolve => setTimeout(resolve, 100));
// Mock node data
return {
title,
type: 'text',
content: `This is mock content for ${title}. It provides enough detail to show in preview cards during development.`,
createdAt: new Date(),
updatedAt: new Date()
};
}
};Acceptance Criteria
- All reference types properly detected (
[[nodes]],@people,#tasks, links) - Visual decorations positioned accurately over reference text
- Hover previews appear after 0.5s delay with relevant information
- Click actions work correctly (open, copy, edit)
- Visual indicators distinct for each reference type
- No interference with textarea editing or cursor positioning
- Performance impact minimal (< 100ms for decoration rendering)
- Preview cards show useful contextual information
- Mobile-friendly touch interactions
- Accessibility support (keyboard navigation, screen readers)
Resources & References
- Logseq Block References: Inspiration from hover preview research
- Mock Element System: Issue Mock Element Positioning System for Precise Cursor Handling #33 positioning foundation
- Component Patterns: Existing BaseNode and TextNode architecture
- Design System: Current CSS tokens and styling patterns in
/src/lib/design/ - Service Patterns: Existing mock service implementations
Definition of Done
- Reference parsing system implemented and tested
- Decoration overlay component created with precise positioning
- All preview components implemented with mock data integration
- Visual styling follows design system patterns
- All acceptance criteria met and verified
- Unit tests >90% coverage for parsing and positioning logic
- Interaction tests for hover and click behaviors
- Performance benchmarks meet requirements (< 100ms)
- Code follows project standards (ESLint, Prettier, TypeScript)
- Documentation includes reference syntax guide and examples
- Ready for integration with real node/person/task services
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request
Activity
Update architecture documentation with advanced UI/UX capabilities
Update architecture documentation with advanced UI/UX capabilities
Update architecture documentation with advanced UI/UX capabilities
[-]Rich Node Reference Decorations with Interactive Previews[/-][+]Hover previews for node references[/+]malibio commentedon Aug 3, 2026
Retitled and re-scoped: most of this issue is built, and the remaining gap is hover previews specifically.
Already built (the spec predates all of it):
packages/desktop-app/src/lib/components/references/base-node-reference.svelte, registered atlib/plugins/core-plugins.ts:18packages/desktop-app/src/lib/styles/noderef.css:163-239nodeReferenceSelectedevent plumbed through every node type fromdesign/components/base-node.svelte:395-661lib/services/base-node-decoration.ts@and[[—lib/components/autocomplete-modal.svelte:86,183-212,components/ui/node-autocomplete/node-autocomplete.sveltedesign/components/backlinks-panel.svelte:70Still missing: hover previews. No preview component exists; the only hover treatment on references is CSS color/
::afteron deleted refs (noderef.css:177,216).Note the reference model differs from what this spec assumed: references are stored as markdown links (
[text](nodespace://id), built atdesign/components/textarea-controller.svelte.ts:999), not as raw decorated tokens over a textarea. So a preview attaches to a rendered link indesign/components/view-mode-renderer.svelte, and the elaborate decoration-overlay-with-mock-element-positioning approach in the original body is unnecessary — drop it.Also obsolete in the original spec: the
#taskhashtag reference type (only@,/, and[[are triggers —lib/services/content-processor.ts:60), and the mock-service scaffolding, since the real data layer exists.The remaining work is: a hover preview card showing the target node title and a content snippet, with a delay, positioned against the link, dismissed on mouse-out — plus a keyboard-accessible equivalent.
mstomar125 commentedon Aug 11, 2026
Resolved in #2024 (merged to
main).Implemented the remaining scoped work — hover/focus preview cards for node references, showing the target node's title and a content snippet:
app-shellcovers both reference render paths (the markdown-link form and the[[id]]wikilink form), since both resolve to the samea.ns-noderefanchor — mirroring the existing delegated click handler.Escape, or scroll. Cache-first resolution viaSharedNodeStore.aria-describedbylinking the active reference to the card. Deleted/broken references are skipped. Card ispointer-events:noneand viewport-clamped (flips above near the bottom edge).extractNodeIdFromHrefand shared with the click handler.As noted in the re-scope, the original 2025 decoration-overlay / mock-service /
#taskmachinery was intentionally dropped — the real model is rendered markdown links and the data layer already exists.A two-pass adversarial review caught a real positioning bug (card measured at loading size, could clip near the viewport bottom) which was fixed before merge. Full suite green (195 files / 4500 tests); lint + svelte-check clean.