Skip to content

Detached-tab pattern: extract shared primitive; add Data Pane Expand + convert Pipeline Expand #100

Description

@BorisTyshkevich

Part of #68 (Roadmap to 1.0.0), Phase 3 — Windows.

Problem. Only the schema graph opens in a real browser tab (openSchemaViewopenInTab, falling back to openInOverlay when the popup is blocked — src/ui/explain-graph.js:648-654). Pipeline (openPipelineFullscreenopenGraphFullscreen, src/ui/explain-graph.js:301-329) is overlay-only, and the Data Pane has no "expand" affordance at all. Three surfaces, three different levels of support for the same idea: view this content full-size, ideally in its own tab.

Scope.

  1. Extract the shared primitive, replacing the schema-specific openInTab/openInOverlay pair (src/ui/explain-graph.js:593-654) — which today duplicate the try-tab/fallback-to-overlay logic — with one general-purpose helper:

    // openInDetachedTab(app, { title, mode, mount })
    //   mode: 'graph' | 'grid' — informational only, picks a body class so each
    //     caller's own CSS still applies (e.g. graph-overlay-canvas vs a data-pane
    //     body wrapper).
    //   mount({ doc, bar, body, close }) is called once, synchronously, after the
    //     tab (or overlay fallback) is set up:
    //     - doc:   the document to build into (child tab's document, or mainDoc
    //              in the overlay fallback) — pass to withDocument()/h() so
    //              elements land in the right realm.
    //     - bar:   the title-bar element — append extra buttons/actions here
    //              (e.g. zoom controls for graphs, nothing extra for Data Pane).
    //     - body:  the empty content mount.
    //     - close: tears the view down — browser-tab-close, Esc, ✕, and
    //              backdrop-click (overlay only) all funnel through it.
    //   mount() may return a teardown fn, invoked from close() (detach pan-zoom
    //     listeners, etc.) — same role as makeController's destroy() today.

    Preserve today's try/fallback behavior exactly: window.open('', '_blank') synchronously on click, mirror app.stylesText + theme into the child doc (mirrorTheme), fall back to the in-app .graph-overlay backdrop on any failure (popup blocked, null window, COOP-severed document). Schema's openSchemaView becomes a thin mount() callback on top of this helper (no behavior change). Pipeline's openPipelineFullscreen becomes another mount() callback, gaining the tab-with-fallback behavior Schema already has.

  2. Add an Expand button next to Copy in the results toolbar (src/ui/results.js:432) that opens the Data Pane (grid + toolbar: sort/copy/export/row-limit) via the new helper, mode 'grid'.

    Known blocker to fix as part of this scope: renderGrid (src/ui/results.js:463-520) builds the <table>/<thead>/<tbody>/<tr> elements with raw document.createElement(...) (lines 467, 470, 490, 495, 497) instead of the h() hyperscript helper. That bypasses the ambient-document mechanism (withDocument() / module-level DOC in src/ui/dom.js:12-21) that the schema-tab mirroring already depends on — as written, renderGrid would build nodes in the opener's document even when called inside a child tab, and appendChild-ing them there throws (WrongDocumentError) or silently misbehaves. Fix renderGrid to build through h() (or take the target doc explicitly) so it's detached-tab-safe.

  3. Snapshot, not live-sync. The detached Data Pane renders a snapshot of the result at expand-time; it does not update if the user runs a new query in the main tab afterward. Live-sync would need cross-document reactivity (signals don't cross window realms — would require a BroadcastChannel/postMessage bridge) and is real additional scope; track it as a separate follow-up issue only if actually wanted, don't build it speculatively here.

  4. Reactivity. Track "is a detached view currently open" with a signal scoped to this new state only (e.g. app.state.detachedView) — not a DOM-ref or raw-field. This issue's signal work is limited to the new state it introduces; it does not fold in migrating the pre-existing file-menu/user-menu/save-popover/editingSavedId/_bannerDismissedFor bookkeeping — that cleanup is tracked separately in UI consistency polish: disclosure chevrons, toast dismiss, popover autofocus, open-state tracking #102.

Acceptance.

  • Data Pane has an Expand button that opens a snapshot of the current grid (with sort/copy/export/row-limit) in a new tab; falls back to an in-app overlay if the tab can't be opened.
  • Pipeline Expand opens in a new tab the same way, with the overlay as fallback only.
  • Schema graph's existing tab/overlay behavior is unchanged (now sharing the extracted helper).
  • renderGrid builds through h()/an injected doc and works correctly inside a child tab's document.
  • New open/close tracking uses a signal scoped to this feature; UI consistency polish: disclosure chevrons, toast dismiss, popover autofocus, open-state tracking #102 is left to handle the pre-existing surfaces.
  • npm test green at the per-file coverage gate.

Note (out of scope): modifier-key pan-zoom is inconsistent between the pipeline graph (free-drag pans) and schema graph (Cmd/Ctrl+drag pans, plain drag selects) — flagged for #66's GraphSurface extraction (roadmap Phase 5) rather than fixed here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions