feat: detached-tab primitive; Pipeline gains a tab; Data Pane Expand (#100) - #107
Merged
Merged
Conversation
…100) Extract the schema graph's real-tab/overlay-fallback logic into a shared openInDetachedTab helper (src/ui/detached-view.js), now used by the schema graph, the EXPLAIN pipeline graph (gains real-tab support), and a new Expand button next to Copy in the results toolbar that opens a sortable/resizable snapshot of the current grid in a tab or overlay. Fixes renderGrid's raw document.createElement calls (the known detached-tab-safety blocker) and a cross-window clipboard/navigator resolution risk in copyResult/copySnapshot. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Give the detached Data Pane the same view switcher as the inline results pane (Table/JSON/Chart), scoped locally so switching view or chart config there never touches the live tab's own resultView/chart state. Extracted viewSwitcherTabs as a shared helper (second consumer). renderChart now takes optional tab/rerender/setChart/running overrides so a detached snapshot can supply its own chart-config holder, local repaint, and chart-instance slot instead of the main pane's app.activeTab()/renderResults()/app.chart. Also fixes Chart.js rendering nothing when its canvas lives in a detached tab: Chart.js's responsive sizing and resize-triggered relayout read layout through APIs (getComputedStyle, ResizeObserver, window.devicePixelRatio) bound to the window its own module runs in — always the main window, even for a canvas in a different document. renderChart now forces an explicit resize + a 'resize'-mode update off the canvas's own geometry (a plain DOM method, realm-agnostic) once it's actually attached. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
about:blank ships no favicon, so every real browser tab opened via openInDetachedTab (schema graph, pipeline, Data Pane) showed the browser's generic default icon instead of the app's own. Add a faviconHref seam (mirrors the stylesText seam) and copy a matching <link rel=icon> into the child tab's <head> alongside the mirrored stylesheet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…mitive-100 # Conflicts: # CHANGELOG.md # src/ui/app.js # tests/helpers/fake-app.js
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.
What & why
Closes #100.
Extracts the schema graph's real-tab/overlay-fallback logic (
openInTab/openInOverlay/buildGraphPanel/mirrorThemeinsrc/ui/explain-graph.js) into a shared primitive,openInDetachedTab(src/ui/detached-view.js), used by three consumers:BroadcastChannel/postMessage bridge — real additional scope, not built speculatively here). The active view/sort/column-widths/chart-config are all local to the snapshot — switching them there never touches the live tab's ownresultView/chart state, and vice versa (confirmed live: running a new query in the main tab doesn't blank or repaint the already-open snapshot).Also fixes the issue's named blocker:
renderGrid(src/ui/results.js) built its<table>/<thead>/<tbody>/<tr>via rawdocument.createElementinstead ofh(), which would build nodes in the wrong document when called inside a detached tab.app.state.detachedView(a count, not a bool — so several views can be open at once) tracks how many detached views are currently open, per the issue's reactivity requirement.Scope decision vs. the acceptance-bullet wording
The acceptance criteria's parenthetical says the Data Pane snapshot should support "sort/copy/export/row-limit." I shipped sort (free via
renderGrid) and copy, but deliberately did not add Export or an interactive row-limit selector to the Data Pane:showSaveFilePickeruser-activation mismatch that's out of scope to solve here.r. The existing truncation note (renderGrid's owncapparam) carries over automatically.Real bug fixed along the way
copyResult()resolvednavigator.clipboardoff the main window unconditionally. Calling it from a click that originated in a detached tab (a different top-level browsing context) could be rejected by the Clipboard API's focused-document permission model. Refactored intocopySnapshot(r, targetDoc), resolvingnavigatorfrom the target document'sdefaultView(verified live: grantingclipboard-writeon a fresh browser context, Copy succeeds from inside a real detached tab).Found & fixed via
/code-review(8 finder agents + self-verification)mount()returned — but the schema graph builds its own action cluster (theme/zoom/undo-redo) asynchronously, later, viarender(). Appending the ✕ eagerly meant it ended up stranded next to the title instead of staying the rightmost control once the graph loaded. Fixed by threading a pre-wiredcloseBtnintomount()instead, so each caller places it wherever its own trailing actions cluster ends up (restores the original schema layout exactly)..cd-backdropcell-detail drawer closes on the first Escape, the pane on the second).openInDetachedTab'stry/catchoriginally wrappedmount()'s execution, not just the window-open attempt — a bug inside a caller's content-building code would have been misattributed as "popup blocked," producing a duplicate overlay plus an orphaned broken tab. Narrowed thetryto only the window-open + cross-realm.documentaccess.Follow-up: full Table/JSON/Chart switcher in the Data Pane
A later commit adds the switcher the acceptance criteria didn't originally call for but was requested in review.
renderChartgained optionaltab/rerender/setChart/runningoverrides (all defaulting to today's main-pane behavior —app.activeTab()/renderResults(app)/app.chart/app.state.running.value, so the main pane's call site is unchanged) so the Data Pane can supply its own local chart-config holder, local repaint, and chart-instance slot instead.viewSwitcherTabsis extracted as a shared helper (second consumer — schema/pipeline don't use it, but the main toolbar and the Data Pane both build the identical Table/JSON/Chart tab strip).Real bug found and fixed while wiring this up: Chart.js rendered nothing at all inside a detached tab — first a 0×0 canvas, then (after a partial fix) correctly laid-out axes/labels but no visible bars/points. Root cause: Chart.js's responsive sizing and resize-triggered relayout (
getComputedStyle,ResizeObserver,window.devicePixelRatio, and a debouncedupdate()gated on an "is this canvas attached" check) all read through APIs bound to the window Chart.js's own bundled module executes in — always the main window, even when the canvas it's drawing into belongs to a completely different document.renderChartnow forces one explicitchart.resize(w, h)+chart.update('resize')off the canvas's own geometry (offsetWidth/offsetHeight— plain DOM properties, realm-agnostic, and already pre-html{zoom}perzoomScale's own doc comment) via arequestAnimationFramescheduled on the canvas's own window, once it's actually attached. Verified against the live antalya cluster: bars/labels render correctly in the detached tab now; the main pane's chart (which never hit this cross-realm path) is unaffected.Follow-up: detached tabs showed the browser's generic favicon
window.open('', '_blank')opens anabout:blanktab, which ships no favicon either (same problem thestylesTextseam already solves for CSS). Added afaviconHrefseam (mirrorsstylesText) and copy a matching<link rel=icon>into the child tab's<head>alongside the mirrored stylesheet, for all three consumers. Verified live: the Data Pane tab's favicon now matches the main page's exactly.Verification
npm test: 1222/1222 passing,src/ui/detached-view.jsat 100/100/100/100,results.js/explain-graph.js/app.jswithin their existing per-file floors.npm run build: succeeds.npm run test:e2e: 39/39 passing (chromium/firefox/webkit).antalyaClickHouse cluster (26.3.10) via Playwright: Data Pane Expand opens a real tab with a working grid/Copy/cell-drawer, Escape priority behaves correctly, the Table/JSON/Chart switcher works (including the Chart.js cross-window fix — bars render), switching the Data Pane's chart type doesn't touch the live tab's own chart config, and running a new query in the main tab doesn't disturb an already-open snapshot. Pipeline Expand now opens in a real tab with working pan/zoom and no JS close button. (Schema-graph's live path wasn't re-driven — its trigger is a drag-and-drop gesture — relying on the existing test suite + direct code trace for that one.)Reconciled
CHANGELOG.md[Unreleased].inbox) for a pre-existing, unrelated latent bug three independent code-review passes hit while working in this area:app.docis dead andapp.documentis never assigned increateApp(every consumer readingapp.documentsilently falls back to the globaldocument). Not fixed here — out of scope for Detached-tab pattern: extract shared primitive; add Data Pane Expand + convert Pipeline Expand #100 and would need re-verifying call sites this PR doesn't touch.Checklist
npm testpasses (the per-file coverage gate is non-negotiable)npm run buildsucceeds (single-filedist/sql.html)src/core/, network insrc/net/(injected fetch), DOM insrc/ui/CHANGELOG.md([Unreleased]) updated