feat(schema): full lineage view in a live new tab — drag-move, undo/redo, detail pane#55
Merged
Merged
Conversation
…edo, detail pane Reworks the fullscreen schema-lineage view from an in-app modal overlay into a real browser tab driven by the opener (same-origin about:blank), with an in-app overlay fallback when pop-ups are blocked / COOP severs the opener. - New tab kept live by the opener: the page CSS + theme are copied in, and the opener (which holds the OAuth token + ch-client) fetches node detail on demand and writes it into the child document. `window.open`/`stylesText` are injected seams on createApp(env); h()/s() gained a `withDocument` seam so the same builders populate the child realm (duck-typed node check fixes cross-realm `instanceof Node`). - Headline: "Schema: <db>", the engine colour legend (incl. Buffer/Merge), a day/night switcher, and a right-aligned actions cluster; no ✕ in the tab. - Mouse model with three distinct cursors: pointer (click → detail pane), move ✛ (⌘/Ctrl-drag → relocate node, incident edges re-route straight), grab (plain drag → pan). Esc closes the detail pane. - Node-move undo/redo (⌘Z / ⌘⇧Z / ⌘Y + headline buttons), per-result position persistence, and an Enum/long-type truncation so a card can't blow out the layout width. Pure geometry/history/card math lives in src/core (graph-layout.js, schema-cards.js) at 100% coverage; the DOM glue stays in src/ui. 985 tests pass, per-file coverage gate green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CNaybba6T2qFDDEYWqzXh1
…a:<db>" The full view opens in a fresh tab with no focused element, so real ⌘/Ctrl keystrokes never reached the document listener — the cursor stayed in click mode and node drag (and undo/redo) didn't work until you clicked in first. - Make the canvas focusable (tabindex=-1) and focus it after render, and call win.focus() on open so the tab is foregrounded — keydown now lands without a prior click. Verified live: activeElement === canvas, a real ⌘ keydown toggles the .modkey cursor state. - Name the browser tab "Schema:<db>" (e.g. "Schema:eth") instead of the generic "Schema graph"; the in-app overlay fallback never touches the main app title. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CNaybba6T2qFDDEYWqzXh1
…olling)
The canvas.focus() added for keyboard handling scrolled the focused canvas into
view, pushing the headline bar above the viewport on first load — it only
reappeared after scrolling up.
- focus({ preventScroll: true }) so focusing never scrolls the header off.
- overflow: hidden on body.schema-tab — the full-height view never scrolls (the
graph pans within the canvas), so there's nothing to scroll the bar away.
Verified live: barTop 0, scrollTop 0, body not scrollable, canvas still focused
(⌘/Ctrl keys still drive cursor mode + undo/redo).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CNaybba6T2qFDDEYWqzXh1
Review follow-ups for the full schema view (PR #55): - Overlay fallback: guard render()/fail() once the view is closed so a late-arriving fetch can't attach (and leak) ⌘Z/⌘Y handlers on the main document that would swallow the editor's own undo/redo. - expandSchemaGraph: wrap the whole post-open flow in try/catch → view.fail, so a token-refresh rejection or build throw can't strand the tab on "Loading…". - Node ⌘-drag: end the drag on a buttons===0 move (release off-window no longer sticks the node to the cursor); re-read the viewBox each move so a ⌘/wheel zoom mid-drag keeps tracking the pointer. - Re-route edge LABELS with their edges on a move (not just the paths), and put a straightened 2-point edge's label on the segment midpoint instead of the target arrowhead. - Grow the layout bounds on a move so Fit/double-click can reframe a node dragged past dagre's original extent. - Refit the full view + overlays on window resize (scoped opt; inline panes unchanged) so drag/pan keep tracking after a resize. - Overlay theme toggle routes through app.toggleTheme (state + saved pref + header icon stay in sync); the tab toggle stays local. Icon rebuilt inside withDocument so it's created in the view's own realm. - Clear the latched .modkey cursor on window blur. - Reuse: panBy + the new-tab drag share dragDeltaToSvg; precompute each node's incident edges once instead of rescanning per mousemove frame. Default theme is now light (day) — state fallback + the template's initial paint, so there's no dark flash before JS runs. Tests added for every new branch; 994 pass, per-file coverage gate green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G1pC6hkksFXLogTCVgNSgK
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.
Summary
Reworks the fullscreen schema-lineage view from an in-app modal overlay into a real browser tab driven by the opener, with an in-app overlay fallback when pop-ups are blocked or COOP severs the opener.
New-tab, live via opener
window.open('', '_blank')is same-origin (about:blank), so the opener builds + drives the child DOM directly — no second JS bundle, no postMessage. The opener keeps the OAuth token + ch-client, so click-to-detail still fetches live and writes results into the child document.data-theme/data-densityare copied into the tab.createApp(env):openWindow,stylesText.h()/s()gained awithDocument(doc, fn)seam so the same builders populate the child realm. Cross-realm fix: node detection duck-types onnodeTypeinstead ofinstanceof Node(a child window has its ownNodeconstructor, which silently stringified elements to[object HTMLDivElement]).await-then-open would be pop-up-blocked); the lineage is fetched, thenrender()/fail()fills the tab.Headline
Schema: <db>· engine colour legend (incl. Buffer/Merge) · day/night switcher · right-aligned actions cluster. No ✕ in the tab (the browser tab's own close serves that); the overlay fallback keeps it.Mouse model — three distinct cursors
Node move: undo/redo + persistence
Other
Enum8(...)column can't blow out a card's (and the graph's) width; the full type still shows in the detail pane.Architecture / conventions
Pure geometry, move-history, and card math live in
src/core/(graph-layout.jsnew,schema-cards.js) at 100% coverage; DOM glue stays insrc/ui/; side effects are injected seams. No new runtime deps.Tests
985 pass, per-file coverage gate green (pure/render layers 100;
app.jsglue above its floor).Verification
Built and smoke-tested live on the github.demo cluster (agent Chrome): real tab renders with full legend, ⌘-drag move + edge re-route, click→detail (live opener fetch), Esc-close, theme toggle, and undo/redo via both keyboard and buttons.
🤖 Generated with Claude Code