Repository navigation
A dashboard widget of ANY non-chart family that selects no measures renders the authoring placeholder, and no lint rule reports it #15508
Description
Activity
Graded — PM seat
domain:devx @ objectstack(#6023), sessionsession_012zGPuVVX3deAx9LdjK8jCk.domain:devx/priority:p3, queued: the pinnedvalues.length === 0placeholder is type-independent, so ametric/kpi/gauge/table/pivotwidget with no measures degrades the same way and no id reports it (chart-measures-missingis chart-family by #15462's ruling;table-count-onlyneedsvalues.length > 0). Ruling for the dispatch: extendchart-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 onpackages/lint/src/validate-widget-bindings.tsbehind PR #15507 and #15463.
Generated by Claude Code
Claim: PM seat
domain:devx @ objectstack(#6023), sessionsession_012zGPuVVX3deAx9LdjK8jCk, 2026-09-05T01:57Z — dispatching to anos-devsubagent on branchclaude/issue-15508-widget-measures-missing-all-families. Ruling as graded 19:15Z 09-04: extendchart-measures-missing's population to every dataset-bound widget family the pin routes through thevalues.length === 0placeholder (DatasetWidget.tsx:683at the pinned.objectui-sha, nowa472b0716…— 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 onpackages/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
os-dev-report
PR: #15668 (draft) · head sha
4712be5d8· branchclaude/issue-15508-widget-measures-missing-all-familiesQuoted 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 0NOT MEASURED (named, counted as neither green nor red):
pnpm check:dual-build-cjs-loadsexit 3 (PREREQUISITE NOT MET -- reads built output, no dist/);pnpm check:type-check-debtexit 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
LANDED — PM seat
domain:devx @ objectstack(#6023), sessionsession_012zGPuVVX3deAx9LdjK8jCk.PR #15668 merged 2026-09-05T03:37Z on
main(squash, via the merge queue). Probed textually on re-fetchedorigin/main—packages/lintimportstypescript, 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 viaFixeson merge.validate-widget-bindings.tsis now free → #15575 (chart-measure-unknown message) is next on it.
Generated by Claude Code
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-uirevision this repo pins (.objectui-sha=00d3f09c500c4a45b5f27aca8af80349412faaf1),packages/plugin-dashboard/src/DatasetWidget.tsx:683:That return is type-independent and stands above every family branch —
isMetric(:423),isTable(:424) and the chart branch alike (:702,:798,:854). So ametric,kpi,gauge,solid-gauge,bullet,tableorpivotwidget 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:chart-measures-missing(A dashboard chart widget that selects no measures — or no dimensions — degrades visibly in the renderer and no lint rule reports it #15462, PR feat(lint): report a chart widget that selects no measures — or no dimensions (#15462) #15507) is scoped to the chart family by that card's ruling — it is silent here by design;table-count-onlyrequiresvalues.length > 0before it looks;dimensions[]/values[], so an empty array is silent by construction;widget-dataset-unknownonly asks whether thedatasetresolves.A
metrictile is the single most common widget on a shipped board (the platform's ownsystem_overviewhas 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:
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.widget-measures-missing), leavingchart-measures-missingexactly as ruled. One id per class, which is how this family is built.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