Skip to content

Wire graph→text navigation: cycle node occurrences in the SQL editor#31

Merged
melonamin merged 5 commits intomasterfrom
feat/issue-23-graph-to-text-nav
Apr 14, 2026
Merged

Wire graph→text navigation: cycle node occurrences in the SQL editor#31
melonamin merged 5 commits intomasterfrom
feat/issue-23-graph-to-text-nav

Conversation

@melonamin
Copy link
Copy Markdown
Member

Closes #23. Builds on the nameSpans / bodySpan fields shipped in #20.

Behavior

  • Single click on a node → highlights and scrolls the SQL editor to the node's first nameSpan (falls back to the legacy single span for column nodes, which intentionally don't populate nameSpans yet — gated on IntelliSense: alias.column completion (semantic resolver) [epic] #27).
  • Double click on a CTE node → jumps to the bodySpan instead.
  • ◀ n/total ▶ badge in the header of selected table/CTE/view nodes — clickable arrows cycle through occurrences. Hidden when the node has fewer than two occurrences (single-occurrence nodes already had their one location highlighted on click; an unclickable "1/1" badge would just be noise).
  • Keyboard shortcut: `n` advances, `Shift+n` returns to the previous occurrence. The handler is suppressed inside inputs, textareas, and the CodeMirror editor so it does not eat ordinary keystrokes. Cmd/Ctrl/Alt+n still reach the browser.

Editor coordinate fix

SqlView previously dispatched the analyzer's UTF-8 byte offsets directly to CodeMirror, which expects character offsets. This is a no-op for ASCII SQL but produces incorrect highlights when SQL contains multi-byte characters (e.g., a column comment or table name in CJK / emoji). Now uses `byteOffsetToCharOffset` from `@pondpilot/flowscope-core` for both the active highlight and the issue highlights.

Store changes

  • New `focusedOccurrenceIndex` slice plus `cycleOccurrence(direction)` and `focusOccurrence(index)` actions, exposed through `useLineageActions` and `useLineage`.
  • Cycling logic lives in the store so it is testable in isolation and shared between the per-node controls and the keyboard shortcut.
  • `selectNode` / `selectStatement` / `setResult` reset `focusedOccurrenceIndex` to 0.

Files added

  • `packages/react/src/components/OccurrenceCycler.tsx` — the badge component, used in both `TableNode` and `SimpleTableNode`.
  • `packages/react/src/hooks/useOccurrenceShortcuts.ts` — global keyboard handler.
  • `packages/react/tests/occurrenceCycling.test.ts` — 8 unit tests.

Validation

  • `yarn workspaces run typecheck`: clean
  • `yarn workspaces run test`: 127/127 passing (including 8 new)
  • `just lint-ts`: clean
  • `yarn workspace @pondpilot/flowscope-app build`: clean (only the pre-existing chunk-size warning)

What I did NOT verify

I did not run the dev server and click around in a browser — I have no browser access in this environment. The compilation / lint / unit-test surface is green, but please do a quick visual smoke test before merging:

  • Click a multi-reference table node → editor scrolls and highlights, badge shows "1/N".
  • Click ▶ → highlight advances to next occurrence; wraps after N.
  • Press `n` while focus is in the graph pane → cycles forward; `Shift+n` → backward.
  • Double-click a CTE node → editor jumps to the parenthesized body.
  • Type `n` in the SQL editor → ordinary keystroke, no cycling.

Out of scope

Closes #23. Builds on the name_spans / body_span fields shipped in #20.

Behavior:
- Single-clicking a graph node highlights and scrolls to the first
  occurrence from `nameSpans` (falling back to the legacy single `span`
  for column nodes, which intentionally don't populate `nameSpans` yet).
- Double-clicking a CTE node jumps to its `bodySpan` instead.
- Each selected table/CTE node renders a small ◀ n/total ▶ badge in its
  header for cycling through occurrences. The badge is hidden when the
  node has fewer than two occurrences.
- Global keyboard shortcut: `n` advances, `Shift+n` returns to the
  previous occurrence. The handler is suppressed inside inputs,
  textareas, and the CodeMirror editor.

Editor coordinate fix:
- SqlView previously dispatched the analyzer's UTF-8 byte offsets
  directly to CodeMirror, which expects character offsets. Now uses
  `byteOffsetToCharOffset` from core for both the active highlight and
  the issue highlights. No-op for ASCII SQL; correct for non-ASCII.

Store:
- New `focusedOccurrenceIndex` slice plus `cycleOccurrence(direction)`
  and `focusOccurrence(index)` actions. Keeps cycling logic in the
  store so it is testable in isolation and shared by the per-node
  controls and the keyboard shortcut.

Tests: 8 vitest cases covering forward/back cycling, wrap-around,
< 2-spans no-op, no-selection no-op, and out-of-range guards.

Refs #17, #20.
Consumers such as the demo app mount GraphView + SqlView directly
without wrapping them in LineageExplorer, so the keyboard hook was
never installed in practice. Moving the hook inside GraphView binds
it to the actual surface the user interacts with.

Found during a browser smoke test where the badge and ▶ button cycled
correctly but n / Shift+n had no effect.
Regenerates wasm bindings against current Rust sources so downstream
TypeScript consumers see the up-to-date types. No behavior change.
Extract node-merging logic into `nodeOccurrences` so graph builders, the
worker, and the store all cycle through the same combined `nameSpans`
when the same node appears in multiple statements. Route span→char
conversion through `trySpanToCharRange` to tolerate stale spans during
file switches, and pull `requestNavigation` from the store via a stable
selector to break a render loop triggered by the transient `actions`
object from `useLineage()`.
@melonamin melonamin merged commit 83b57ee into master Apr 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Navigation: graph → text (name occurrences, CTE body highlight)

1 participant