Skip to content

feat(#332): Dashboard Command/Ctrl-drag tile move, text selection, shared cell-detail - #337

Merged
BorisTyshkevich merged 4 commits into
mainfrom
feat/dashboard-drag-celldetail-332
Jul 20, 2026
Merged

feat(#332): Dashboard Command/Ctrl-drag tile move, text selection, shared cell-detail#337
BorisTyshkevich merged 4 commits into
mainfrom
feat/dashboard-drag-celldetail-332

Conversation

@BorisTyshkevich

Copy link
Copy Markdown
Collaborator

What & why

Closes #332.

Improves Dashboard interaction consistency and data inspection, interaction-wiring only — no Dashboard schema, query execution, panel format, or persistence semantics change.

1. Command/Ctrl-drag tile movement. The native whole-card HTML5 draggable (and its dragstart/dragover/drop wiring) is removed. Tile reorder is now a modifier-gated pointer gesture that matches the schema graph (#55): a move starts only on primary button + Command/Ctrl + a 4px movement threshold. A plain drag never reorders and never preventDefaults an unmodified pointerdown, so text now selects and copies inside Markdown/text, table, and logs tiles. Hit-testing is a pure core/tile-reorder.ts helper over rects captured at drag-start (happy-dom's elementFromPoint always returns null, so hit-testing can't be a live DOM query). A completed move dispatches the existing atomic move-tile command exactly once and preserves canonical dashboard.tiles[] order; a cancelled gesture (pointercancel / window blur / Escape) changes nothing; the click a completed move synthesizes on its origin card is swallowed so it never activates a cell. While Command/Ctrl is held the grid shows a grab affordance (.dash-grid.modkey); an active move dims the moving card and outlines the drop target. Read-only Dashboards never wire movement.

2. Text selection inside tiles. Falls out of (1): no native drag, and user-select is disabled only for the duration of an active modifier-drag. Edit chrome (grip/delete/resize) stays non-selectable.

3–4. Shared cell-detail for table and logs tiles. The Dashboard onCell (previously a no-op) now calls the shared Workbench openCellDetail (results.ts) in the Dashboard's own document — in both edit and read-only modes — with the source column name/type and the raw (untruncated) value. No Dashboard-specific viewer is constructed. core/logs.ts logRowDisplay is widened to {name,type,raw,display} per field so each source-backed log field (time, level, message, and each extra) is independently clickable and keyboard-operable (Enter/Space); a selection gesture ending on a field never opens the drawer. DashboardApp gains prefs for the shared drawer's resize-persist seam.

Layering

  • Pure logic in src/core/: new tile-reorder.ts (threshold + rect hit-test) and the widened logs.ts row-shaping, both 100% covered.
  • src/ui/dashboard.ts stays a thin adapter over the pure helpers; the library-free pointer wiring is fully unit-tested under happy-dom.

Review

A self-review pass caught and fixed an inverted click-suppression (a same-tile ⌘-drag would have spuriously opened cell-detail) and a vacuous suppression test; both are now covered. Added guards against overlapping gestures and a flow-KPI tile's unplaced card being a phantom drop target.

Checklist

Test plan

  • Unit gate green (156 files, 4308+ tests); real-browser verification (text selection, ⌘/Ctrl-drag + destination feedback, drawer resize/backdrop, light/dark, Grid Tiles/Full/Report/2-col/3-col, detached read-only route, narrow viewport) is done via e2e (GitHub Actions) + a manual otel-cluster pass — e2e is CI-only in this environment, not run locally.

🤖 Generated with Claude Code

BorisTyshkevich and others added 2 commits July 20, 2026 13:09
…ared cell-detail

Replace the Dashboard's native whole-card HTML5 drag with a modifier-gated
pointer gesture and wire table/log cells into the shared Workbench cell-detail
drawer — no Dashboard-specific viewer.

Tile reorder (dashboard.ts): a move starts only on primary-button + Command/Ctrl
+ a 4px movement threshold, matching the schema graph's interaction model. A
plain (unmodified) drag never reorders and never calls preventDefault, so text
selection works inside Markdown/text, table, and logs tiles. Hit-testing is a
pure core/tile-reorder.ts helper over rects captured at drag-start (happy-dom's
elementFromPoint always returns null). A completed move dispatches the existing
atomic move-tile command exactly once and preserves canonical dashboard.tiles[]
order; a cancelled gesture (pointercancel / window blur / Escape) changes
nothing, and the click a completed move synthesizes on its origin card is
swallowed so it never activates a cell. While Command/Ctrl is held the grid
shows a grab affordance (.dash-grid.modkey); an active move dims the moving card
and outlines the drop target. Only one gesture runs at a time, and a flow-engine
KPI tile's unplaced card is skipped from hit-testing.

Cell detail: the Dashboard table/logs onCell (was a no-op) now calls the shared
openCellDetail (results.ts) in the Dashboard's own document, in both edit and
read-only modes — passing the source column name/type and the raw (untruncated)
value. core/logs.ts logRowDisplay is widened to carry {name,type,raw,display}
per field so each source-backed log field (time, level, message, each extra) is
independently clickable and keyboard-operable (Enter/Space); a selection gesture
ending on a field never opens the drawer. DashboardApp gains prefs for the
shared drawer's resize persist.

Read-only Dashboards never enable tile movement. Pure logic (hit-test, row
shaping) stays in core/ at 100%; per-file coverage gate satisfied.

Reconciles the #286 "pointer-drag-only" note in roadmap #68.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDsUDSPoDYa1M1rbpgdG3f
Owner UX rework of the #332 tile-move gesture. The modifier-only pointer drag
(with only a faint drop-target outline) is replaced by a direct-manipulation
model:

- A move now starts from the top-left GRIP with no modifier, or from the tile
  body with ⌘/Ctrl held (the schema-graph modifier model). A plain body drag
  still selects text.
- On the Grid Tiles (grafana-grid) engine the dragged tile lifts (position:fixed
  + transform follow) with a same-size placeholder holding its grid slot, and
  the sibling tiles reflow live (FLIP, honoring prefers-reduced-motion) to open
  the gap. The move commits only when the dragged tile overlaps a destination
  slot by >=2/3 of its own area (resolveOverlapInsertIndex, core/tile-reorder.ts)
  and otherwise snaps back. The dragged tile takes the slot it overlaps, and the
  placeholder previews at the exact final index the move-tile splice commits to
  (no forward-drag off-by-one). The flow engine keeps the simpler point
  hit-test drag (its KPI band has no grid slot to reflow into).
- Snap-back / restore is synchronous and independent of the signature-gated grid
  reconcile (a snap-back leaves currentDoc unchanged, so the next publish would
  otherwise skip the DOM rebuild); restoreDrag() removes the placeholder, clears
  the float styles, restores the tile's grid height, clears sibling FLIP
  transforms, and force-invalidates lastGridSig.

Pure geometry (overlap resolve, FLIP delta) lives in core/tile-reorder.ts at
100%; the DOM wrapper is unit-tested under happy-dom (float/placeholder state,
grip vs ⌘/Ctrl vs plain-body gating, forward/backward commit, snap-back +
Escape/cancel restore, reduced-motion). Reconciles the #286 note in roadmap #68.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDsUDSPoDYa1M1rbpgdG3f
@BorisTyshkevich

Copy link
Copy Markdown
Collaborator Author

Follow-up (commit b613f78): reworked the tile-move gesture per owner feedback — the modifier-only drag with a drop-target outline is replaced by grip-drag (no modifier) / ⌘-Ctrl body-drag + live grafana-grid reflow (dragged tile lifts and follows, siblings shift to open a gap) with a ≥2/3-overlap commit, else snap back. Snap-back restore is synchronous (independent of the signature-gated reconcile). Flow engine keeps the simpler point-hit-test drag. An adversarial review caught a forward-drag preview/commit off-by-one (placeholder previewed one slot earlier than the splice landed) — fixed so the placeholder previews the exact committed slot, with a regression test. Gate + build green; CHANGELOG and #68 reconciled.

Two owner-requested refinements on top of the grip-drag/live-reflow rework:

- **Commit threshold measures the TARGET slot, not the dragged tile.**
  `resolveOverlapInsertIndex` now commits when the dragged tile covers >=2/3 of
  the destination slot's area (was >=2/3 of the dragged tile's own area). This
  is the correct behavior when tiles differ in span/height — a small tile
  dropped inside a big slot no longer commits just because it's fully contained;
  it must cover 2/3 of the slot. Zero-area candidate slots are skipped.

- **Markdown cell values get a rendered preview in the cell-detail drawer.**
  When a table/log cell value looks like Markdown (new pure `looksLikeMarkdown`
  detector in `core/cell.ts` — headings, lists, blockquotes, fenced code,
  thematic breaks, links; conservative, HTML checked first), the drawer opens on
  a Rendered<->Source toggle (mirroring the existing HTML path) and renders via
  the docs pane's own top-quality `renderDocMarkdown` viewer (`ui/doc-markdown-
  view.ts` over `core/doc-markdown.ts`) — bounded, fail-closed (no innerHTML,
  links via `defaultDocLinkPolicy`, no CodeViewer seam so fences fall back to
  plain <pre>). Plain non-markup text still shows as source only. The HTML/MD
  toggle is refactored into one shared `mountToggle` helper.

No e2e harness imports results.ts/renderDashboard, so the new marked-backed
doc-markdown import adds no unbundled-harness bare-import dependency. Pure logic
(overlap resolve, looksLikeMarkdown) stays 100%-covered in core/; results.ts and
dashboard.ts hold their gates. CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDsUDSPoDYa1M1rbpgdG3f
@BorisTyshkevich

Copy link
Copy Markdown
Collaborator Author

Two more refinements (commit 25cb573), all CI green (test/e2e ×2/bundle/docker/size):

  • ≥2/3-overlap commit now measures the TARGET slot's area, not the dragged tile's — correct for tiles of differing span/height (a small tile dropped inside a big slot no longer commits just by being contained; it must cover 2/3 of the slot).
  • Markdown cell-detail preview — a table/log cell value that looks like Markdown opens the drawer on a Rendered↔Source toggle (like HTML) using the docs pane's top-quality renderDocMarkdown viewer (bounded, fail-closed: no innerHTML, links via the doc link policy, code fences fall back to plain <pre>). Plain non-markup text still shows source only.

Verified live on otel (build 25cb573): a Markdown cell renders # Heading→h4 + list + blockquote with a working Source toggle; no e2e harness imports results.ts, so the new marked-backed import adds no raw-ESM bare-import dependency. Coverage gates hold (core/cell.ts + core/tile-reorder.ts 100%; results.ts/dashboard.ts within gate).

…preview; retire markdown-lite

The cell-detail Markdown preview shipped for table/log cell VALUES, but the
Dashboard's Markdown lives in a Text tile — clicking it did nothing. Per owner
follow-up, the Text (Markdown) tile now:

- **renders inline through the shared, top-quality doc viewer** (`renderDocMarkdown`
  over `core/doc-markdown.ts`) instead of the former lightweight markdown-lite
  renderer — so the app has ONE Markdown paradigm. `renderPanelMarkdown`
  (panels.ts) wraps it in the existing `.md-view` container (keeps the dashboard
  tile-containment CSS) and uses a panel-appropriate link policy (any absolute
  http(s) URL passes; scheme-less/other schemes render as literal text — NOT the
  docs pane's `defaultDocLinkPolicy`, which would rewrite to clickhouse.com/docs).
  No CodeViewer seam (a Text panel mounts no CM6) so SQL fences fall back to a
  plain <pre>; images/raw HTML/rejected links stay literal (fail-closed).
- **is click / Enter-Space openable into the shared cell-detail drawer** showing
  the full Markdown (resizable, over the same viewer) — useful when the authored
  content overflows the tile. A drag-select inside the tile, or a click on an
  inner link, never opens it. Works in edit and read-only modes.

`core/markdown-lite.ts` (+ its test) is deleted — it was imported only by
panels.ts and superseded by the doc viewer. `panels.renderMarkdown` is replaced
by `renderPanelMarkdown`; the workbench Text-panel preview goes through the same
path. doc-markdown-view's header comment updated to note it is now the app's
single Markdown renderer.

Gate green (cell.ts/tile-reorder.ts 100%; panels.ts lines 100; dashboard.ts/
results.ts within gate); build + arch clean. No e2e harness imports panels.ts/
results.ts, so the doc-markdown (marked) graph reaches no raw-ESM harness.
CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UDsUDSPoDYa1M1rbpgdG3f
@BorisTyshkevich

Copy link
Copy Markdown
Collaborator Author

Fixed the reported issue (clicking the Markdown tile did nothing) — commit c4553c6, all CI green (test/e2e ×2/bundle/docker/size):

  • The Dashboard Text (Markdown) tile now renders inline through the same top-quality doc viewer as the reference docs (retiring the lighter markdown-lite renderer — the app now has ONE Markdown paradigm).
  • The Text tile is click / Enter-Space openable into the shared preview drawer (full Markdown, resizable), with a selection-guard and inner-link guard so a drag-select or a link click never opens it.

Verified live on otel (c4553c6): the "About this demo" tile renders .md-view > .docs-md inline and clicking it opens the drawer with the rendered Markdown. Text-panel link policy is panel-appropriate (http(s) absolute pass; scheme-less → literal text, no clickhouse.com rewrite). No e2e harness imports panels.ts/results.ts, so the doc-markdown (marked) graph reaches no raw-ESM harness. Gate/build/arch clean.

@BorisTyshkevich
BorisTyshkevich merged commit 093aef4 into main Jul 20, 2026
9 checks passed
@BorisTyshkevich
BorisTyshkevich deleted the feat/dashboard-drag-celldetail-332 branch August 6, 2026 15:28
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.

Use command-drag for Dashboard tile moves and add cell-detail preview to table/log tiles

1 participant