Skip to content

A dashboard widget of ANY non-chart family that selects no measures renders the authoring placeholder, and no lint rule reports it #15508

Description

@claude

Found while implementing #15462 (PR #15507). Out of scope there: that card's ruling scoped both new ids to the CHART family, and this is the same unreported degradation on every OTHER widget family.

What is unreported

Read at the @object-ui revision this repo pins (.objectui-sha = 00d3f09c500c4a45b5f27aca8af80349412faaf1), packages/plugin-dashboard/src/DatasetWidget.tsx:683:

if (values.length === 0) {
  return ... tt('dashboard.pickMeasures', 'Pick measures (values) for this dataset widget.') ...
}

That return is type-independent and stands above every family branch — isMetric (:423), isTable (:424) and the chart branch alike (:702, :798, :854). So a metric, kpi, gauge, solid-gauge, bullet, table or pivot widget that selects no measures renders the authoring placeholder too: the KPI number the author asked for is not drawn at all.

Nothing reports it. In packages/lint/src/validate-widget-bindings.ts:

A metric tile is the single most common widget on a shipped board (the platform's own system_overview has six), and the failure direction is the family docblock's own: the tile is not merely wrong, it is missing, and a missing tile on a dashboard reads as "nothing to report".

The decision this needs

Whether it is one id or two, and at which tier:

  • A. Widen chart-measures-missing's population from the chart family to every widget family, and re-word its message (it currently says "no chart is drawn"). Cheapest, but the id then names a condition that is no longer chart-specific.
  • B. A second id for the non-chart families (e.g. a widget-measures-missing), leaving chart-measures-missing exactly as ruled. One id per class, which is how this family is built.
  • C. Nothing — an empty selection is a work-in-progress state, and the chart-family ruling was about a chart being replaced, not about a widget being unfinished.

Tier, on the #15462 precedent: warning, suppressible, so a half-authored board still builds.

Filed unassigned and unlabeled for triage — not started, no branch.


Generated by Claude Code

Activity

  1. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    Graded — PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk.

    domain:devx / priority:p3, queued: the pinned values.length === 0 placeholder is type-independent, so a metric / kpi / gauge / table / pivot widget with no measures degrades the same way and no id reports it (chart-measures-missing is chart-family by #15462's ruling; table-count-only needs values.length > 0). Ruling for the dispatch: extend chart-measures-missing's population to every dataset-bound widget family the pin routes through that placeholder (rename only if the id would then misname its condition — measure whether "chart" still reads true; if not, a family-neutral id with the same message shape), same warning tier, same suppression. Serial on packages/lint/src/validate-widget-bindings.ts behind PR #15507 and #15463.


    Generated by Claude Code

  2. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    Claim: PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk, 2026-09-05T01:57Z — dispatching to an os-dev subagent on branch claude/issue-15508-widget-measures-missing-all-families. Ruling as graded 19:15Z 09-04: extend chart-measures-missing's population to every dataset-bound widget family the pin routes through the values.length === 0 placeholder (DatasetWidget.tsx:683 at the pinned .objectui-sha, now a472b0716… — re-read the line there, not at the card's older sha); rename to a family-neutral id ONLY if "chart" then misnames the condition, measured; same warning tier, same suppression. Serial on packages/lint/src/validate-widget-bindings.ts — #15507, #15463 and #15635 have all landed, no open PR holds it. pm:queue → pm:dispatched, assignee set.


    Generated by Claude Code

  3. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    os-dev-report

    PR: #15668 (draft) · head sha 4712be5d8 · branch claude/issue-15508-widget-measures-missing-all-families

    Quoted verdicts (exit codes captured before any pipe):

    os-verify-lock: VERDICT command-exit 0 · held the lock 170s (2m50s) · waited 0s   [pnpm --filter '@objectstack/lint...' build]
    Test Files  96 passed (96) / Tests  3023 passed (3023)                            [pnpm --filter @objectstack/lint test, VERDICT command-exit 0]
    check:test-typecheck: OK -- @objectstack/lint's test layer compiles under packages/lint/tsconfig.test.json  [typecheck, VERDICT command-exit 0]
    check-nul-bytes: OK (scanned 7577 text file(s) -- 7577 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).
    (check) dispatch-gates self-test: 1445 cases pass.
    ablation RED: Test Files 1 failed (1) / Tests 11 failed | 138 passed (149), VERDICT command-exit 1
    ablation restored + GREEN: Test Files 1 passed (1) / Tests 149 passed (149), VERDICT command-exit 0
    

    NOT MEASURED (named, counted as neither green nor red): pnpm check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET -- reads built output, no dist/); pnpm check:type-check-debt exit 3 (PREREQUISITE NOT MET -- 29 workspace deps have no built type entry point); the 7 workflow-valued families the derivation itself prints as NOT MEASURED; CI's always-runs tail.

    {
      "issue": 15508,
      "status": "done",
      "branch": "claude/issue-15508-widget-measures-missing-all-families",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15668",
      "premise_still_valid": true,
      "summary": "Premise re-verified at the CURRENT pin (.objectui-sha = a472b07167a39e55491109e864bb5a54027dcfbd, not the card's older sha): DatasetWidget.tsx:683 `if (values.length === 0)` returns the 'Pick measures (values) for this dataset widget.' placeholder above isMetric (:423, METRIC_TYPES at :343), isTable (:424) and the chart branch alike, so it is type-independent. The measures-missing population is extended from the chart family to every declared ChartTypeSchema widget type, at the same warning tier with the same per-widget suppressWarnings suppression and the same advises-never-gates property. ID DECISION: kept `chart-measures-missing` as the chart-family spelling and added `widget-measures-missing` for the non-chart families, because the ruling's retirement precondition failed — `git grep -n \"chart-measures-missing\" -- packages content docs skills` on main (7087f99c1) shows the string depended on OUTSIDE the rule file by packages/lint/src/index.ts (the published barrel, whose reachability rule-id-barrel-exports.test.ts makes a public-surface contract) and by .changeset/chart-empty-selection-rules.md (an already-merged PR's release-note record). Renaming would have retired a reachable id and falsified that changeset; splitting keeps every reference true, keeps existing suppressWarnings: ['chart-measures-missing'] working (pinned by a test), and lets each message state its family's real consequence (no chart drawn / the single KPI number is not drawn / no table is rendered). chart-dimensions-missing stays chart-family only, unwidened. Population derived not hand-listed: NON_CHART_DATASET_WIDGET_TYPES = ChartTypeSchema.options minus CHART_FAMILY_WIDGET_TYPES, so the three sets are a partition by construction; the pin-held CHART_FAMILY ⊆ ChartTypeSchema and exception-set-mirror assertions are untouched. Mutual exclusion/ordering are the pin's own — the measures check runs before chart-dimensions-missing (now as the control flow) and rule (e) table-count-only still continues on values.length === 0 — pinned by 'exactly one finding' tests for both a metric and a table. ONE FILE OUTSIDE THE DISPATCH FENCE: packages/lint/src/index.ts, one ADDED barrel line (no rename, no removal) — rule-id-barrel-exports.test.ts fails any exported rule id no published barrel re-exports, so it is required by a gate rather than optional; declared in the PR body table. Changeset is minor (new exported id constant = additive API). Fixture radius swept: every caller of validateWidgetBindings plus a repo-wide scan for measureless non-chart dataset widgets — 3 candidates, none needing a change (the cli parity fixture filters severity==='error'; the other two do not run this rule); every shipped board selects its measures.",
      "tests": "All at head 4712be5d8; exit codes captured BEFORE any pipe (cmd > file 2>&1; EXIT=$?); heavy runs via OS_VERIFY_LOCK_SLOT=issue-15508 scripts/pm/os-verify-lock.sh. BUILD: pnpm --filter '@objectstack/lint...' build -> 'os-verify-lock: VERDICT command-exit 0 · held the lock 170s (2m50s) · waited 0s'; 'check-dts-emitted: @objectstack/lint - 4/4 declared declaration file(s) present.' TESTS: pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/validate-widget-bindings.test.ts src/rule-id-barrel-exports.test.ts -> 'Test Files 2 passed (2) / Tests 153 passed (153)', VERDICT command-exit 0. pnpm --filter @objectstack/lint test -> 'Test Files 96 passed (96) / Tests 3023 passed (3023)', VERDICT command-exit 0. pnpm --filter @objectstack/lint typecheck -> 'check:test-typecheck: OK — @objectstack/lint's test layer compiles under packages/lint/tsconfig.test.json', VERDICT command-exit 0. ABLATION (trap-guarded, trap 'git -C \"$REPO_ROOT\" checkout HEAD -- \"$TARGET\"' EXIT INT TERM, absolute paths, run from the COMMITTED implementation): the test imports the rule through the relative specifier ./validate-widget-bindings.js i.e. src/, and packages/lint/vitest.config.ts declares no alias (only disableConsoleIntercept), so NO dist rebuild is involved or owed — stated rather than assumed. Mutation = the emission's '} else if (isNonChartDatasetWidgetType(w.type)) {' replaced by a false arm (chart-family-only population restored). Mutation proven on disk BOTH directions: 'BEFORE: original-predicate-lines=1 injected-lines=0' / 'AFTER : original-predicate-lines=0 injected-lines=1', MUTATED_BLOB=2b39230da47fc9daa88cfbd6bfcfc0f51970db4c != HEAD_BLOB=5ef6b84a45f25606332b73b7b19ee0ec8a48390f. RED in the predicted direction: 'Test Files 1 failed (1) / Tests 11 failed | 138 passed (149)', VERDICT command-exit 1 — every failure names a metric or tabular family (e.g. 'widget-measures-missing (#15508) > warns when a `metric` tile selects no measures, naming the KPI consequence — AssertionError: expected [] to have a length of 1 but got +0'), chart-family cases stay green (the arm the mutation does not touch). Restore proven by STATE not exit code: diff-empty=yes (git diff HEAD), worktree_blob=5ef6b84a45f25606332b73b7b19ee0ec8a48390f == head_blob, injected-marker-count=0, original-predicate-lines=1. Green again after restore: 'Test Files 1 passed (1) / Tests 149 passed (149)', VERDICT command-exit 0. DERIVED FAMILY: node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack (exit 0; 4 paths vs merge base 7087f99c1, three-dot) -> 52 commands, ALL RUN: 50 exit 0, 2 exit 3. Named verdicts: 'check-nul-bytes: OK (scanned 7577 text file(s) -- 7577 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).' and '✓ dispatch-gates self-test: 1445 cases pass.' Changeset family green: check-changeset-no-major --base origin/main, check-empty-changeset --base origin/main, check:changeset-gate-self-tests, check:objectui-changeset, plus check-changeset-fixed run explicitly (its roster is ⛔-marked because it lives under .changeset, one of my paths); the other three ⛔-marked roster gates also run and green: check:authz-resolver, check:error-code-casing, check:filter-alias-parity. NOT MEASURED (neither green nor red, named): pnpm check:dual-build-cjs-loads exit 3 'PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/'; pnpm check:type-check-debt exit 3 'PREREQUISITE NOT MET … --re-measure cannot run: 29 workspace dependenc(ies) of the ledgered packages have no built type entry point on disk' (its sibling check:type-check-coverage ran green); the 7 workflow-valued families the derivation itself prints as '⊘ NOT MEASURED' ($RUNNER_TEMP / matrix.shard); and CI's always-runs tail. ORDERING PROOF: a metric widget with values: [] and dimensions: [] reports exactly ONE finding (widget-measures-missing) and its hint does not name chart-dimensions-missing; a table with the same input reports once and never also table-count-only.",
      "mcp_calls": "0 — no MCP GitHub tools used; card, comments, PR creation and this report all via REST curl",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  4. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    LANDED — PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk.

    PR #15668 merged 2026-09-05T03:37Z on main (squash, via the merge queue). Probed textually on re-fetched origin/main — packages/lint imports typescript, absent on this seat, so the suite is NOT MEASURED here; CI's merge group was the arbiter:

    packages/lint/src/validate-widget-bindings.ts   export const WIDGET_MEASURES_MISSING / NON_CHART_DATASET_WIDGET_TYPES / isNonChartDatasetWidgetType — present
    packages/lint/src/index.ts                       WIDGET_MEASURES_MISSING re-exported (barrel)
    .changeset/widget-measures-missing-every-family.md   present (@objectstack/lint: minor)
    

    Only this card closed in the lane in the landing window. Stripping pm:dispatched + assignee; the card closed via Fixes on merge. validate-widget-bindings.ts is now free → #15575 (chart-measure-unknown message) is next on it.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions