feat(graph): interactive drag, wheel zoom, pan, hover highlight (G2-G5)#171
Merged
Conversation
Makes the noteser-graph fullscreen view feel like Obsidian by consuming
the v1.3 interaction surface (L1-L4). All changes are in the plugin;
src/plugins is untouched.
- G2 wheel zoom + drag pan: the svg declares panZoom:'host', so the host
owns the viewBox (instant, no worker round-trip). The viewport is
reconstructed from the surface.transform settle event and persisted
under g2.viewport so it survives reload. The nine manual zoom/pan
buttons are deleted; a single "Reset view" remains.
- G3 node drag (pin/unpin): onPointerDown pins a node (host auto-captures
the pointer), onPointerMove drags it to payload.{x,y}; the simulation
reheats around pinned nodes (simulationStep skips integration for fixed
nodes) and streams only the moved coordinates via
ctx.patchSvgPositions each frame, so a 500-node drag holds 60fps with
no full-tree re-emit. Click a pinned node to unpin, or "Release pinned".
- G4 hover highlight: onPointerEnter/Leave dims non-neighbour circles and
brightens the hovered node's edges (recolor only, no re-layout).
- G5 click vs drag: a press within ~4px and ~250ms is a click (selects a
note and surfaces the open link, or unpins a pinned node); a real drag
never opens. Open still flows through the existing wikilink:// intercept
(PluginCtx exposes no ctx.openNote, so a true one-click programmatic
open is not possible without a new platform hook).
All G1 features (local graph, color groups, filters, force tuning,
tags-as-nodes) and the sidebar backlinks panel are preserved.
Manifest bumped to v0.3.0 with interaction:{pointer,wheel,hover} on the
fullscreen view. New pure helpers (isTapGesture, simulationStep,
hoverNeighbours, viewportFromTransform) are unit-tested in
noteserGraphPluginG2G5.test.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes the
noteser-graphfullscreen view feel like Obsidian by consuming the v1.3 interaction surface (L1-L4). Onlypublic/plugins/noteser-graph/and a new test undersrc/__tests__/are touched;src/plugins(the platform) is untouched.Behaviours
G2 — wheel zoom + drag pan. The svg declares
panZoom: 'host', so the host owns the viewBox: wheel-zoom and dragging the background pan instantly with no worker round-trip. On settle the host emits onesurface.transform{x,y,scale}event; the plugin reconstructs the viewport box (viewportFromTransform) and persists it underg2.viewport, so the viewport survives a reload. The nine manual zoom/pan buttons are deleted; a single Reset view button remains (net code removal).G3 — node drag (pin/unpin).
onPointerDownpins a node (the host auto-captures the pointer because the circle declares both down + move);onPointerMovedrags it topayload.{x,y}(already svg user-space). The simulation reheats around pinned nodes (simulationStepskips integration forfixednodes) and streams only the moved coordinates viactx.patchSvgPositions({ viewId, patches })each frame (L4 patch channel) — no full-tree re-emit, so a 500-node drag holds 60fps. A pinned node keeps a distinct ring; click a pinned node to unpin, or use Release pinned.G4 — hover highlight.
onPointerEnter/onPointerLeavedim every non-neighbour circle and brighten/thicken the hovered node's edges (hoverNeighbours). Recolor only — no re-layout, no patch channel.G5 — click vs drag. A press that releases within ~4px and ~250ms (
isTapGesture) is a click: a click on a free note node selects it and surfaces the open link; a click on a pinned node unpins it. A real drag never opens the note.Platform events used
onPointerDown/Move/Up+ automatic pointer capture (G3, G5)VNodeSvg.panZoom:'host'+ thesurface.transformsettle event (G2)onPointerEnter/Leave(G4)ctx.patchSvgPositions(...)+line.sourceId/targetId+circle.id(G3)Known platform limitation
PluginCtxexposes noctx.openNote(id)and SVG children cannot be alinkVNode, so a true single-click programmatic open is not possible. G5 ships as click-vs-drag disambiguation that surfaces the existingwikilink://open link on a genuine click. The existing wikilink open behaviour still works. Closing this fully would need a newctx.openNotehost hook (intentionally not added here — the platform is done).Preserved
All G1 features (local graph, color groups, filters, force tuning, tags-as-nodes) and the sidebar backlinks panel are unchanged. Manifest bumped to v0.3.0 with
interaction: { pointer, wheel, hover }on the fullscreen view.Tests / verify
src/__tests__/noteserGraphPluginG2G5.test.ts: click-vs-drag threshold, pinned-nodefixed-flag in the sim step, hover neighbour set, viewport round-trip.npm run lintclean,npx tsc --noEmitzero errors,npm test -- --cigreen (225 suites / 2891 tests).🤖 Generated with Claude Code