Skip to content

feat: dashboard table/logs tiles + per-tile result caps (#149 D9)#168

Closed
BorisTyshkevich wants to merge 1 commit into
mainfrom
feat/149-d9-table-logs-tiles
Closed

feat: dashboard table/logs tiles + per-tile result caps (#149 D9)#168
BorisTyshkevich wants to merge 1 commit into
mainfrom
feat/149-d9-table-logs-tiles

Conversation

@BorisTyshkevich

Copy link
Copy Markdown
Collaborator

Summary

  • Favorites that aren't chart-shaped now render instead of being skipped: a saved view:'table' renders as a sortable/resizable grid, and non-chartable multi-row results render as a compact logs tile when log-shaped (DateTime-ish column + a message/msg/body/log/line column, optional level column for row colors) — covers system.text_log and OTel log tables even when a numeric column would otherwise auto-chart.
  • Adds best-effort server-side result caps (max_result_rows/max_result_bytes, result_overflow_mode=break) for every tile query, plus a client-side 5,000-row trim; grid/logs tiles display the first 1,000 rows with a truncation footer.

Closes #164

Test plan

  • npm test — 52/52 files, 1522/1522 tests, 100/100/100/100 coverage held on touched pure/DOM files
  • Deployed to otel for manual verification (dirty build off d370705)
  • Manual click-through on otel: save a system.text_log/OTel-shaped favorite, confirm logs tile renders with level colors + extras, confirm grid sort/width persist across refresh

🤖 Generated with Claude Code

https://claude.ai/code/session_018GesXYWGvXv898YAiMzNAt

Favorites that aren't chart-shaped now render instead of being skipped:
a saved view:'table' renders as a sortable/resizable grid, and
non-chartable multi-row results render as a compact logs tile when
log-shaped (DateTime-ish + message column, optional level for colors) —
covers system.text_log and OTel log tables. Also adds best-effort
server-side result caps (max_result_rows/max_result_bytes) plus a
client-side 5,000-row trim for every tile query.

Closes #164

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GesXYWGvXv898YAiMzNAt
@BorisTyshkevich

Copy link
Copy Markdown
Collaborator Author

Decision (2026-07-11): this PR will not be merged as-is. The Panels refactor (#166) supersedes the standalone landing — its implementation pulls this branch's code (table/logs renderers, caps, slot lifecycle) and lands it together with the registry, closing #164. Keeping the PR open as the code source / reference until #166's PR is up.

BorisTyshkevich added a commit that referenced this pull request Jul 12, 2026
* refactor: extract the chart renderer into src/ui/chart-render.js (#166 prep)

Move renderChart/chartCfgFor/the config bar/installChartZoomFix out of
results.js into their own module, and visCap + the shared truncationFooter
into grid-render.js, so the upcoming panels registry can consume the chart
and grid renderers without a results.js import cycle. rerender is now a
caller-supplied seam (results.js passes its own repaint) instead of a
default that imported results.js back. No behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDPs4oomqSfpGGRpEqHahT

* feat: dashboard table/logs groundwork — logs core+renderer, per-tile result caps (#164, from PR #168)

Absorb PR #168's D9 code (that PR is superseded by #166 and will close
unmerged): src/core/logs.js (log-shape detection, level classes, row
shaping), src/ui/logs.js (the compact scroll-only logs view, sharing
grid-render's truncation footer), best-effort server caps on every tile
fetch (max_result_rows sentinel + max_result_bytes + overflow 'break')
with the guaranteed client trim in parseJsonResult(json, cap), and the
honest 'first 5,000 rows fetched' tile-footer note. classifyTile is
untouched here — #166's autoPanel replaces it in a later commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDPs4oomqSfpGGRpEqHahT

* feat: panel-cfg union + safe markdown subset (core, #166)

src/core/panel-cfg.js: the panel-config union (chart family | table | logs |
text) — validation per arm (chart delegates to chart-data's index checks;
logs resolves {time,msg,level} column NAMES with per-role convention
fallback — a failed name lookup is that arm's mismatch signal), deep clone
with unknown-field preservation (the ignore-and-preserve forward-compat
policy), normalize, the autoPanel heuristic ladder (logs > autoChart >
table; text never auto-proposed), and resolvePanel — the shared mismatch
policy: retain the explicit type, re-derive roles within it, fall back to
autoPanel + a diagnostic only when the type is impossible for the shape.

src/core/markdown-lite.js: pure parser for the text panel's safe Markdown
subset (headings, bold/italic, lists, links, inline code) to a plain-object
AST — the DOM is built by the UI layer from the tree, never innerHTML, so
raw HTML is inert by construction; link hrefs are http(s)-only.

core/logs.js: per-role convention scanners exported for panel-cfg's
name-based fallback. Both new modules at 100%.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDPs4oomqSfpGGRpEqHahT

* feat: library panel field — upgradeSavedEntry at every ingress, dual-write chart mirror (#166)

The saved-entry format moves chart:{cfg,key} → panel:{cfg,key?} as an
additive version-1 change (imports still hard-reject version > 1; old
builds drop the unknown field and read the mirror).

- upgradeSavedEntry (pure, idempotent) runs at every ingress: localStorage
  startup (createState), JSON import (before cleanView, so a legacy
  view:'chart' maps to 'panel' instead of being stripped), replace/append/
  merge, tab restoration (loadIntoNewTab), and share decode — including
  main.js's oauth_shared sessionStorage stash, a second deserialization
  point that bypasses decodeShare.
- view:'table' with a latent chart migrates losslessly: the roles ride in a
  nested panel.cfg.chart stash (a flat spread would collide on type).
- withChartMirror derives the legacy chart field from panel (present iff
  chart-family, deleted otherwise) at all five write sites — saveQuery,
  replaceLibrary (whose field whitelist now carries panel), mergeSaved's
  by-id branch, buildExportDoc, encodeShare — so mirror and panel never
  drift. Removing the mirror next minor requires the upgrader to strip
  chart when panel exists (noted in the module header).
- saveQuery relaxes per type: sql:'' is allowed for {type:'text'} only;
  share/bootstrap gates become sql||panel so an SQL-less text-panel link
  is not dropped; text panels export their content to Markdown and are
  skipped by the .sql script export.
- Tab model renamed chartCfg/chartKey → panelCfg/panelKey (tabPanel packs
  the key only for chart-family cfgs) across tabs/main/chart-render/
  dashboard and their tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDPs4oomqSfpGGRpEqHahT

* feat: panel registry + workbench Panel drawer tab (#166)

The results-pane Chart tab becomes Panel: a Type picker (Bar…Pie · Table ·
Logs · Text) plus per-type config, dispatching through the new
src/ui/panels.js registry (PANEL_TYPES[type].controls/renderPanel →
{node, destroy?}) that the dashboard will share, so drawer preview ≡ tile
by construction.

- Previews never execute SQL: every preview renders from the tab's last
  explicit Run result; the text panel renders cfg.content with no result
  at all (and renders through a Markdown AST → DOM builder, never
  innerHTML); query-backed types show an empty-preview hint before a Run
  and while one streams.
- Dirty pin: the preview renders resolvePanel's clone — render never
  writes tab.panelCfg; only picker/control edits write back (and mark the
  tab dirty exactly like a SQL edit). renderChart gains cfg/onCfgChange/
  typeControl seams for this; its old holder mode stays for the detached
  snapshot.
- switchPanelType (core, pure): type switches keep role continuity — the
  chart roles stash into cfg.chart when leaving the family and are
  consumed on the way back (the same lossless shape the view:'table'
  migration writes).
- Run-less saved-row open: an entry that can't auto-run (a text panel's
  empty SQL) still restores its remembered drawer view.
- Detached Data Pane: Table|JSON|Panel, the panel rendered read-only from
  the source tab's resolved cfg at expand time (no editor, v1 scope).
- resultView/SAVED_VIEWS become table|json|panel ('chart' maps forward).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDPs4oomqSfpGGRpEqHahT

* feat: dashboard partitions favorites before execution; tiles dispatch through the panel registry (#166, closes-#164 behavior)

- Favorites are partitioned BEFORE any SQL runs: a text panel renders
  immediately (synchronously, zero queries — app.runTile is never called
  for it); everything else joins the query wave as before.
- applyTileResult dispatches through renderResolvedPanel: an explicit
  saved panel wins (resolvePanel's mismatch policy — retain type,
  re-derive roles, diagnostic fallback for impossible/unknown types) and
  never vanishes — a zero-row explicit panel renders an honest '0 rows'
  state and stays out of the skip tally; unconfigured results go through
  autoPanel (logs outrank autoChart, table is the universal fallback), so
  nothing multi-row is skipped anymore. Only unconfigured empty and
  single-row (future KPI, #154) results still skip.
- classifyTile is deleted (core/dashboard.js keeps route helpers, the
  JSON transform and the caps); the slot lifecycle holds the registry's
  destroy + schema-keyed sort/width state (persists across Refresh,
  resets on schema change; header-click sorts repaint locally, no
  re-query). D9's table/logs acceptance (#164) passes on this path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDPs4oomqSfpGGRpEqHahT

* fix: finalize panel drawer persistence and preview behavior (#166)

* fix: align var-strip fields vertically, mark required with *, preview server time

Fixes two layout regressions in the #166 var-field/var-combo grid rework:
the live preview crowding into the input's own row instead of wrapping
below it, and shorter sibling fields (no preview line) sitting vertically
offset from taller ones due to outer flex containers centering instead of
top-aligning. `.var-combo` reverts to a flex column; `.dash-filters`,
`.var-strip`, and `.var-field` align to flex-start/start instead of center.

Also, per review:
- the relative-time live preview now renders in UTC ("server time")
  instead of the browser's local zone — simpler and unambiguous than the
  earlier "(your time)" disclaimer, which this replaces outright.
- the optional-field "?" suffix (silently color-only after an earlier
  removal, and untested — CSS ::after content isn't observable via
  textContent) is replaced with a "*" marker on required fields instead.
- drops the now-vestigial `.var-field` grid-template-rows/row-gap (verified
  pixel-identical without them — both children already spanned the full
  height, so a single-row grid was always sufficient).
- corrects the stale preview-format examples in README.md and CHANGELOG.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHAyFzCon72V6GbjvX475Z

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

**Phase D9 — table tiles + logs view**: saved view:'table' + non-chartable fallback render as table/logs tiles, server row cap for all tiles

1 participant