Skip to content

feat(cli): build-time warning for count-only table/pivot dashboard widgets (table-count-only)#1720

Merged
os-zhuang merged 1 commit into
mainfrom
feat-1719-table-count-only-warning
Jun 11, 2026
Merged

feat(cli): build-time warning for count-only table/pivot dashboard widgets (table-count-only)#1720
os-zhuang merged 1 commit into
mainfrom
feat-1719-table-count-only-warning

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #1719.

What

After the ADR-0021 single-form cutover, a table/pivot DashboardWidget whose selected values resolve to only count measures and which declares no dimensions asks the analytics service for a single summary row. That's exactly what a metric widget wants — but for a table it almost always means the author wanted a per-record listing, which is not an analytics dataset at all (ADR-0017 ListView territory). objectstack build was silently green on this shape; downstream it collapsed 19 record-listing tables across 11 dashboards (templates#27).

objectstack build now emits a non-fatal table-count-only warning per offending widget:

⚠ dashboard "expenses_overview_dashboard" › widget "pending_reports_table": a 'table' widget
  bound to dataset "expense_report_metrics" selects only count measure(s) (report_count) and
  no dimensions, so it renders a single summary row — not a per-record list.
    A flat record listing is not an analytics dataset. Model it as an object-bound ListView
    (ADR-0017) surfaced through app navigation, and use a 'metric' widget here if you only
    need the count. If a single-row table is intentional, add an explicit dimension or
    suppress with: suppressWarnings: ['table-count-only']
    rule: table-count-only  at dashboards[0].widgets[0]

How

  • packages/cli/src/utils/validate-widget-bindings.ts — new semantic-phase check mirroring the ADR-0032 validateStackExpressions pattern. It runs after Zod parse, when each widget's dataset name is linked to its defineDataset and every values entry resolves to a measure with a known aggregate. Per the issue, the heuristic is keyed on the widget's binding, not the dataset — the repro dataset also carries a sum measure and a dimension, and still warns because the widget selects neither.
  • compile.ts (= build) — prints warnings in human output; --json gains a warnings[] array. The build never fails on these.
  • lint.ts — the same rule surfaces through objectstack lint with rule id table-count-only, consistent with the existing rule registry.
  • spec/ui/dashboard.zod.ts — new optional widget field suppressWarnings: string[] as the opt-out for deliberate single-row tables.

Deviations from the issue

The proposed // objectstack-disable-next-line table-count-only comment can't work here: the config is evaluated through bundle-require, so source comments never reach the metadata the check runs on. The suppression is metadata-level instead (suppressWarnings: ['table-count-only'] on the widget), schema-validated and mentioned verbatim in the warning text.

Deliberately not warned (each is a different diagnostic or a legitimate shape): dangling dataset references, unresolvable measure names, mixed count+non-count selections, count_distinct, derived measures, any widget with ≥1 dimension, and metric widgets.

Testing

  • 14 new unit tests covering the issue's repro, the widget-binding-vs-dataset distinction, pivot, suppression, and all the skip cases (validate-widget-bindings.test.ts)
  • Full @objectstack/cli suite: 28 files / 240 tests green; @objectstack/spec dashboard+stack tests green; both packages compile
  • End-to-end: ran objectstack build on the issue's minimal repro — warning fires, build stays green, --json carries the warning, suppressWarnings silences it; objectstack lint reports the same rule
  • The platform's own system_overview table widget declares dimensions: ['action'] and does not trip the rule

🤖 Generated with Claude Code

…mensions

Closes #1719. After the ADR-0021 single-form cutover, a `table`/`pivot`
DashboardWidget whose selected `values` resolve to only `count` measures
and which declares no `dimensions` requests a one-row summary from the
analytics service — the shape a `metric` widget wants, but for a table it
almost always means the author wanted a per-record listing (which is not
an analytics dataset at all; ADR-0017 ListView territory). The build was
silently green on this shape; downstream it collapsed 19 record-listing
tables across 11 dashboards (objectstack-ai/templates#27).

`objectstack build` now emits a non-fatal `table-count-only` warning per
offending widget, keyed on the WIDGET's binding (not the dataset): the
check runs after dataset references are resolved, so a dataset that also
carries sum measures/dimensions the widget doesn't select still warns.
Dangling dataset refs and unresolvable measure names are left to their
own diagnostics; derived and count_distinct measures don't count as
plain counts.

- new `validateWidgetBindings()` in the CLI build pipeline (mirrors the
  ADR-0032 `validateStackExpressions` semantic-phase pattern); warnings
  print in human output and land in `--json` as `warnings[]`
- same rule surfaces through `objectstack lint`
- deliberate single-row tables opt out per widget via the new schema
  field `suppressWarnings: ['table-count-only']` — a metadata-level
  escape hatch, since source comments do not survive config bundling

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 11, 2026 10:01am

Request Review

@os-zhuang
os-zhuang merged commit cf6e117 into main Jun 11, 2026
12 checks passed
@os-zhuang
os-zhuang deleted the feat-1719-table-count-only-warning branch June 11, 2026 10:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

构建期告警:table/pivot dashboard widget 绑定到「count-only」数据集(很可能是应做成 ListView 的记录列表)

1 participant