Skip to content

feat(ui): database data source for kit charts (DASH-3)#143

Merged
eliotlim merged 2 commits into
mainfrom
feat/dash-3-db-chart-source
Jul 13, 2026
Merged

feat(ui): database data source for kit charts (DASH-3)#143
eliotlim merged 2 commits into
mainfrom
feat/dash-3-db-chart-source

Conversation

@eliotlim

Copy link
Copy Markdown
Owner

What

The kit chart block can now be sourced from a database (group-by + measure), not just reactive-input expressions — unifying toward one chart engine. Board: DASH-3 (Epic: Dashboards).

Model

source is now EITHER an expression (unchanged) OR a DB binding, keyed by a new sourceMode prop:

  • sourceMode: 'expr' (or absent) → existing expression path, byte-identical. Back-compat is structural — existing serialized charts have no sourceMode → untouched.
  • sourceMode: 'database'dbId, dbGroupBy (property id or parent-group), dbAggType (count/sum/avg/min/max), dbAggProp (numeric prop for non-count), + dbSnapshot (resolved-series cache for export).

Data flow

useDbChartSeriesuseOptionalData().listRows(dbId) (seed) + subscribeRows(dbId, cb) (live, same reactivity as DB bar/pie views) + getDatabase(dbId).schema. Aggregation reuses SDK aggregateRows(rows, view, properties) (no reinvention). Render contract stays source-agnostic ({value, labels, palette}), so no chart-kind changes.

Config UI

"Reactive inputs | Database" segmented toggle in KitSettings. Database mode: DB picker (pages.filter(hostedDatabaseId)) + group-by select + measure (agg + numeric "of" prop), mirroring databaseMenus.tsx. Reactive mode unchanged (expression + ScopeHints).

Export (provider-less viewer)

Custom blocks have no data provider in the viewer/export bundle; useOptionalData() returns null there and the chart falls back to dbSnapshot. The live block snapshots its resolved series to dbSnapshot; computeExportCells seeds from it and exportBlocks.ts bakes it as a constant literal → export renders last-known data, not blank.

⚠️ Reviewers please scrutinize

A DB-bound chart writes dbSnapshot back into the CRDT doc on first resolve (guarded by JSON-equality + !readOnly; converges after one write). This means opening a page with such a chart mutates the doc — a read→write side-effect with dirty-state/provenance/edit_log implications. It's the mechanism that makes export work without a data provider, but please assess whether a cleaner approach (snapshot only on explicit save/export, or a non-synced cache) is warranted.

Tests / verify

New chartsDb.test.ts 10/10. Full pnpm verify foreground green: build:libs, typecheck (6), lint, test (sdk 188, ui 991, server 659), test:e2e (server + mcp 40).

Scope: packages/ui (charts.tsx + data barrel + export seeding). No new chart kinds (DASH-5), no interactivity (DASH-2).

Give the kit chart block a database data source alongside the reactive-input
expression source. A chart's `source` is now EITHER an expression (unchanged,
`sourceMode` absent/`expr`) OR a database binding (`sourceMode: 'database'`):
group a database's rows by a property and fold a measure, reusing the SDK
`aggregateRows` pipeline the database bar/pie views ride.

- Model: new block props dbId / dbGroupBy / dbAggType / dbAggProp + a
  dbSnapshot of the resolved series. Existing serialized charts have no
  sourceMode and render byte-identically (expression path untouched).
- Resolve: useDbChartSeries seeds via DataClient.listRows and re-aggregates on
  subscribeRows (the same reactivity the DB views use); getDatabase supplies the
  schema. Falls back to useOptionalData so the provider-less viewer degrades to
  the snapshot instead of throwing.
- Render contract stays source-agnostic: both paths feed {value, labels} to the
  kind's render.
- Config: a Reactive-inputs | Database source toggle in the KitSettings popover;
  database picker + group-by + measure (count/sum/avg/min/max of a numeric
  property) in Database mode, expression + ScopeHints in Reactive mode.
- Export: the live block snapshots its aggregated series to dbSnapshot;
  computeExportCells + exportBlocks bake it in as a constant literal (+ group
  labels) so a DB-bound chart exports its last-known data rather than blank.
- Tests: aggregateDbSeries data resolution, readDbBinding back-compat, and
  DB-bound vs expression export.

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

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app.book.pub Ready Ready Preview, Comment Jul 13, 2026 4:00am

Request Review

…ew (DASH-3)

Review blocker: the on-view `dbSnapshot` write persisted derived data into the
CRDT doc on first resolve, routing a write through the logged/synced/undo save
path — so merely opening or PRESENTING a DB-chart page marked the viewer as
having edited it (saver dirty → savePage → edit_log, undercutting OB-165
provenance), churned peer sync, and landed on the undo stack.

Fix: opening/presenting a DB chart now writes NOTHING. Export resolves each
DB-bound chart's series fresh via the in-app DataClient that initiates the
export, threading a transient `blockId → {value, labels}` map through the (sync)
projection — no persisted snapshot anywhere.

- New pure module `kit/chartData.ts`: the DB-chart data logic (binding read,
  aggregateDbSeries, DbChartSeriesMap) plus `resolveDbChartSeries(client,
  blocks)` — enumerates DB-bound kitchart blocks, fetches each database's rows +
  schema once, aggregates. Shared by the live component and the export path.
- charts.tsx: deleted the persist-to-doc effect and the `dbSnapshot` prop.
  `useDbChartSeries` still resolves live for on-screen display only.
- Export threading: optional `dbSeries` param on projectSnapshotForExport →
  computeExportCells + projectBlocksForExport, forwarded by the re-projecting
  toHtml / toSlideDeck / buildDocumentModel. Resolved in the two async sites that
  hold a client: BlockPageDocument.handleExport (md/pdf/slides/unsaved-html) and
  gatherSite (html site). performSave stays unresolved (DB charts publish no
  named values; avoids per-save listRows churn).
- Config nits: auto-select the sole database on switching to Database mode;
  balanced Measure/Of columns with visible labels; hint when a numeric measure
  has no numeric property; dropped redundant aria-labels; Kind select shares
  `selectClass`.
- Tests: export-time resolution via a threaded map (no persisted snapshot),
  block carries no dbSnapshot, unresolved DB chart exports empty not broken, and
  resolveDbChartSeries over a fake client.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KFk2T9k3p7ghCjdzMfkA5w
@eliotlim eliotlim merged commit 0723b0d into main Jul 13, 2026
10 checks passed
@eliotlim eliotlim deleted the feat/dash-3-db-chart-source branch July 13, 2026 04:14
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.

1 participant