Repository navigation
chart-measure-unknown errors on report/list/page chart series[].name with a message that names an empty series — at the pin it is a display-name override that lands on nothing #15575
Description
Activity
- addedbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3
on Sep 5, 2026 分诊 ·
domain:devx/priority:p2/pm:queueAnchor read, not guessed.
packages/lint/src/validate-chart-bindings.ts⇒domain:devx(the lint lane). Confirmed onorigin/mainf1d7872(2026-09-05T00:21:28Z):validate-chart-bindings.ts:41 export const CHART_MEASURE_UNKNOWN = 'chart-measure-unknown'; validate-chart-bindings.ts:181 const measureRef = (name, path, selected?) => { validate-chart-bindings.ts:230 if (binding.yAxis) measureRef(binding.yAxis.name, binding.yAxis.path, selected); validate-chart-bindings.ts:231 for (const s of binding.series ?? []) measureRef(s.name, s.path, selected);⇒
yAxisandseries[].namereally do go through onemeasureRef, so they get one message. That is the mechanism, and it is why the consequence sentence cannot be true for both positions at once.⭐ And the file already knows the risk:
:33— "…convention, which would makechart-measure-unknown's message a lie." The rule's own header worries about exactly this failure mode one case over.Grade — p2
- Not p3. This fires at
errortier, so it redsos validate/os build. If the key is presentation-only, an author is blocked from building over a display-name override that lands on nothing — and told the reason is "this series comes back empty", which sends them to debug a query that is fine. ⇒ Wrong tier and wrong explanation, on the loud channel. - Not p1: the author's remedy (remove or correct the
series[].nameentry) is obvious once found, nothing ships wrong, and no data moves.
⛔ But the grade is against the report charts case only — the card is explicit and I am not over-reading it
The evidence at the pin (
.objectui-sha=00d3f09c500c…) is direct for report charts and quoted from objectui's own words:DatasetReportRenderer.tsxusesmergeAuthoredSeries"because a report'schart.xAxis/chart.yAxisare bare dimension/measure NAME strings, i.e. pure data on this surface" — soxAxis/yAxisare genuinely the query binding anderroris right for those two positions — whilechart.series[]is described as "the author's per-chart override for ONE measure's display name", and an entry matching nothing is ignored (@object-ui/corechart-presentation.ts: "membership belongs to the dataset").⛔ Surfaces 2 and 3 have not been read at the pin:
- List-view charts (
ListChartConfigSchema) — the binding may genuinely be the query there. - Dataset-bound page chart components —
⚠️ and this one carries its own question::346-:353mergesaxisRefsandseriesRefsinto oneseriesarray before:231walks it, so axis positions on that surface currently take theserieslimb's message. ⇒ Whether that is deliberate or an accident of the shape must be settled in the same pass; if it is an accident, surface 3's axis positions are getting the wrong message today regardless of how the tier question resolves.
⇒ Deliverable 1 is the per-surface read at the pin, ⛔ not the tier change. Three surfaces, three answers possible.
Boundary test — a tier change is a gate move, above the trivial line
Dropping
error→warningweakens a gate for the affected positions, which is on the manual-floor list. ⭐ But there is precedent that makes this cheap rather than open-ended: #15463 put exactly this question (forchart-field-unknown, dashboardchartConfig) to the PM rather than deciding it in a PR, and the maintainer ruling there dropped the presentation-only positions towarningand reworded the message. ⇒ Same question, one rule over; the ruler has already answered the general form.⚠️ ⛔ That precedent does not decide this card — the card's own closing sentence is the rule to follow: "#15463's lesson is that the tier follows the measurement, rather than the other way round." ⇒ Measure surfaces 2 and 3, then apply the precedent per surface.Also in scope, so it is not a fourth card
Whether
chart-axis-not-selected(warning,:203, "the query does not return it, so the series plots nothing") needs the same wording pass at theseries[].nameposition.⚠️ Same shared-limb problem, lower tier. ⇒ Settle it in the same pass rather than leaving a second message with the same defect atwarning.Scope fences — endorsed
⛔ Not #15463 (different rule id, different surfaces) and ⛔
packages/lint/src/validate-widget-bindings.ts(that card's file) is not involved. ⭐ Filing rather than folding in was right: same measurement, different rule, different tier decision.⛔ Not a claim, not a dispatch — routing only.
Generated by Claude Code
- Not p3. This fires at
Claim: PM seat
domain:devx @ objectstack(#6023), sessionsession_012zGPuVVX3deAx9LdjK8jCk, 2026-09-05T03:54Z — dispatching to anos-devsubagent on branchclaude/issue-15575-chart-measure-unknown-series-name(lock depth read before dispatch per #14944: lock free, queue empty → admitted; the assignee/label write landed a minute before this comment because the first attempt's comment step failed — same claim, same seat). Ruling: MEASUREMENT FIRST, per surface, at the CURRENT pin (.objectui-sha=a472b0716…, not the card's00d3f09c5): for report charts, list-view charts and dataset-bound page chart components, read whetherseries[].nameis the query binding or a presentation-only override at the pinned renderer; THEN apply the #15463 maintainer precedent per surface — presentation-only positions drop towarningwith the true consequence ("a display-name override that lands on nothing"), query-binding positions (xAxis/yAxison reports, and any surface where the read says the binding is the query) stayerror. Settle in the same pass whether surface 3'saxisRefs+seriesRefsmerge (:346-:353) is deliberate, and givechart-axis-not-selectedthe same wording check at theseries[].nameposition. A surface whose read is inconclusive keepserrorand is reported. Hot filespackages/lint/src/validate-chart-bindings.ts(+ its test) FREE;validate-widget-bindings.tsNOT involved.pm:queue→pm:dispatched, assignee set.
Generated by Claude Code
os-dev-report
{ "issue": 15575, "status": "done", "branch": "claude/issue-15575-chart-measure-unknown-series-name", "pr": "https://github.com/objectstack-ai/objectstack/pull/15741", "premise_still_valid": true, "summary": "Measurement first, at the CURRENT pin a472b0716 (not the card's 00d3f09c5), and all three surfaces came back conclusive. Report charts: `chart.xAxis`/`chart.yAxis` ARE the query (DatasetReportRenderer.tsx:859-867 `useDatasetRows(dataset, [xAxis], [yAxis], ...)`, and :326 'the embedded chart queries only chart.xAxis x chart.yAxis'), while `chart.series[]` is a display-name override merged by `mergeAuthoredSeries` (:1034), whose unmatched entry is 'ignored - membership belongs to the dataset' (core chart-presentation.ts:178-180, implemented as `derived.map(...)` at :208-211 so an unmatched entry cannot enter the output). List-view charts: `ListChartConfigSchema` (spec view.zod.ts:1256) is a strictObject of chartType/dataset/dimensions/values - there is NO series or yAxis position on that surface at all, and `values[]` is handed to the chart as the dataset measures (ObjectView.tsx:2103-2117), so it stays error with nothing to re-tier. Page chart components: ObjectChart queries `{dimensions: schema.dimensions, measures: schema.values}` (:551-556) and then REPLACES the authored series wholesale (`series: datasetChart.series`, :890-892, one entry per selected measure via buildChartSeries :681), and reads `yAxis` nowhere - so both `properties.series[].name` and `properties.yAxis[].field` are presentation. Applied the #15463 precedent per position: the three presentation positions drop error->warning with reworded consequences; every query position (report chart.yAxis, and values[] on all three surfaces) keeps error and its existing message verbatim. Merge question (:343-353) answered: reading both shapes on the page surface is deliberate, folding them into ONE `series` limb was an accident - it gave every yAxis[].field the SERIES message - so they are now separate limbs (`binding.axes` / `binding.series`) with different sentences, walk order and paths unchanged. chart-axis-not-selected: wording is now per position - verbatim at a query position, and at each presentation position it names the derivation instead of a query that does return the measure. PM addendum done: the hand-copied `asArray` is deleted and its 10 call sites re-pointed onto `recordsOf` (object-graph.ts:181), with a test that a null member in every collection this file reads is skipped rather than dereferenced. Changeset @objectstack/lint patch, tier drop stated as the user-visible behaviour change.", "tests": "All numbers from the final commit ef5f28aad (git rev-parse --short HEAD at the run). Every exit code captured BEFORE any pipe (cmd > log 2>&1; EXIT=$?); heavy runs through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-15575. (1) pnpm --filter '@objectstack/lint^...' build -> VERDICT command-exit 0, held 177s, waited 0s - closure built BEFORE any judgement. (2) pnpm --filter @objectstack/lint exec vitest run src/validate-chart-bindings.test.ts -> 'Test Files 1 passed (1)' / 'Tests 31 passed (31)'; VERDICT command-exit 0, held 16s, waited 487s. (3) pnpm --filter @objectstack/lint test -> 'Test Files 96 passed (96)' / 'Tests 3041 passed (3041)'; pnpm --filter @objectstack/lint typecheck -> 'check:test-typecheck: OK - @objectstack/lint's test layer compiles under packages/lint/tsconfig.test.json' (so the edited test file is MEASURED, not excluded); both under VERDICT command-exit 0, held 61s, waited 222s. (4) pnpm lint -> exit 0, the FULL repo scan (eslint . --no-inline-config), not narrowed. (5) ABLATION (trap-guarded, absolute paths): the test imports './validate-chart-bindings.js' relatively, so vitest resolves SOURCE not dist - NO rebuild leg on either leg. Reverting the tier to unconditional error, proven on disk before reading anything (PRE_orig_count=1/PRE_mut_count=0 -> POST_orig_count=0/POST_mut_count=1, hash 17f6f5bf -> b118bd3b): MUTATED_EXIT=1, 'Test Files 1 failed (1)' / 'Tests 8 failed | 23 passed (31)' - red in the expected direction. Restore proven by observation, not by an exit code: RESTORE_orig_count=1/RESTORE_mut_count=0, RESTORE_DIFF_HEAD=empty, RESTORE_HASH_MATCHES_HEAD_BLOB=yes (git hash-object == HEAD blob 17f6f5bf45c8ebadf030146cbbd6d5dd823cdce9), then RESTORED_EXIT=0, 'Tests 31 passed (31)'. (6) DERIVED FAMILY: node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack on the final head -> 53 commands from the real change set (3 paths, three-dot vs merge base 581d8f84c); ALL 53 RUN, 50 exit 0. (7) pnpm -s check:pm-dispatch-gates -> exit 0, 'dispatch-gates self-test: 1445 cases pass.'; pnpm check:nul-bytes -> exit 0, plus a direct grep -naP C0/DEL scan of the three changed paths -> no matches. NOT MEASURED, by name: check:dual-build-cjs-loads (exit 3, 'PREREQUISITE NOT MET - this gate reads built output, and some package has no dist/', 84 packages - needs a whole-repo build, CI's); check:type-check-debt (exit 3, 'PREREQUISITE NOT MET ... 30 workspace dependenc(ies) ... have no built type entry point on disk' - CI's, and no ledger number raised on it); check:docs-transcript-drift was exit 3 first (packages/lint/dist missing) and was RE-RUN after building @objectstack/lint -> exit 0, '4 declared transcript value(s) across 405 page(s) ... equal what the registry derives today' (measured); plus the 7 CI-variable families and the 36 artifact rosters the derivation itself scores outside the runnable total. The derivation printed a STALE TREE warning (branch behind origin/main; scripts/pm/check-half-states.mjs and scripts/pm/ensure-pm-labels.sh moved in that range) - re-derived after git fetch origin main, command list byte-identical, and check:pm-half-states (the family those two files define) ran exit 0.", "mcp_calls": "1 - one targeted search_issues dedup before filing #15734. Everything else went through repo-scoped REST (the probe passed; note GitHub's /search/issues is 403 in this session, 'sessions are bound to their configured repositories', which is why the dedup used the one permitted MCP call).", "open_questions": [ { "question": "The brief asked for 'the same suppressWarnings mechanism as the family', but that mechanism does not exist on these three surfaces: suppressWarnings is declared on the dashboard WIDGET only (packages/spec/src/ui/dashboard.zod.ts:584). ReportChartSchema and ListChartConfigSchema are strict and do not carry it; the page props bag is untyped. Should the key be declared on them so the new advisories are individually silenceable?", "options": [ "A - Ship as landed: the three positions are warning (non-gating, since consumers split on severity === 'error') but not individually suppressible; the hint says so rather than advertising a key that does not exist.", "B - Declare suppressWarnings on ReportChartSchema / ListChartConfigSchema / the object-chart props entry in a follow-up spec card, then wire the read here.", "C - Leave the surfaces alone and drop the finding entirely at presentation positions." ], "recommendation": "A now, B as a separate card if the PM wants parity with the dashboard surface. Declaring the key is a schema change, which is a stop condition on this card, and C loses a finding that is genuinely worth reporting - the author wrote a key and believes it is in force." } ], "out_of_scope_findings": [ "filed as #15734: chart-axis-not-selected resolves a report chart against report.values, but at the pin the chart queries only [chart.yAxis] - so at the report chart.yAxis position it warns about a measure the chart DOES query, and at the report series position the deciding set is the singleton {chart.yAxis}. Wording corrected in this PR; changing WHICH findings fire is the decision that card carries.", "not filed, reported here: the page-component surface accepts an authored properties.series[] that ObjectChart discards wholesale even when it MATCHES a selected measure (:890-892 replaces the array rather than merging it, unlike the report surface's mergeAuthoredSeries). So a correct series[] entry on a dataset-bound page chart is inert too - a renderer-side gap, in objectui, not a lint rule this card could report." ] }
Generated by Claude Code
LANDED — PR #15741 merged as
a51eb86b7. Card closed byFixes.Probe on re-fetched
origin/main(a9b9c7dc9), PM seat:packages/lint/src/validate-chart-bindings.tsimportsrecordsOf(:131), declarestype MeasurePosition(:220) andUNKNOWN_CONSEQUENCE(:223); the only remainingasArraymention is the docblock at:125describing the deleted copy; the test file carries 31it(cases; changesetchart-measure-unknown-presentation-positions.mdpresent. Lint vitest NOT MEASURED here (no deps in this checkout); both required jobs were success onef5f28aad.Stripping
pm:dispatchedand the assignee in the same action. Freesvalidate-chart-bindings.tsfor PR #15751's ledger fix (#15636).
Generated by Claude Code
Found while implementing #15463 (PR #15571). Out of scope there — that card names one rule id (
chart-field-unknown, dashboardchartConfig), and this is a different id on different surfaces — but it is the same measurement, applied one rule over.The claim
packages/lint/src/validate-chart-bindings.tsrunsmeasureRefoverseries[].namefor all three surfaces it covers (:231, fed by:258for report charts and:346-:353for list charts and dataset-bound page chart components). Aterrortier its message reads:Read at the
@object-uirevision this repo pins (.objectui-sha=00d3f09c500c4a45b5f27aca8af80349412faaf1), that consequence looks wrong for theseries[].nameposition specifically, because the renderer does not build the series from it:packages/plugin-report/src/DatasetReportRenderer.tsximportsmergeAuthoredSeriesand says why, in its own words: "mergeAuthoredSeries— notmergeAuthoredPresentation— because a report'schart.xAxis/chart.yAxisare bare dimension/measure NAME strings, i.e. pure data on this surface". SoxAxis/yAxisreally are the query binding on a report, anderroris right for those two positions.chart.series[]is not. The same file describes it as "The author's per-chart override for ONE measure's display name — the entry ofchart.series[]whosenameIS that measure", matched by name against the derived series. An entry matching none is ignored, exactly as@object-ui/coresrc/utils/chart-presentation.tsstates for the dashboard's copy: "an authored entry naming a measure that is NOT in the dataset selection is ignored — membership belongs to the dataset".So a
series[].namenaming an undeclared measure does not empty a series; it is a display-name override that lands on nothing. That is the ignored-key class, not a broken query — which is precisely what #15463 measured forchart-field-unknownand what the maintainer ruling there resolved by dropping those positions towarningand rewording the message.Why this is a card and not a patch
The tier is a product judgement, the same one #15463 put to the PM rather than deciding in a PR, and the answer may differ per surface. Three things need measuring per surface before anything moves:
report.chart,report.blocks[].chart) — the evidence above is direct, andxAxis/yAxisshould clearly keeperror.ListChartConfigSchema) — not yet read at the pin; the binding may genuinely be the query there.:346-:353mergesaxisRefsandseriesRefsinto oneseriesarray before:231walks it, so axis positions on that surface currently take theserieslimb's message. Whether that is deliberate or an accident of the shape is worth settling in the same pass.Also worth deciding: whether
chart-axis-not-selected(warning,:203, "the query does not return it, so the series plots nothing") needs the same wording pass at theseries[].nameposition.What to decide
errorand correct only the consequence sentence for theseries[].nameposition.series[].nametowarningon whichever surfaces the pin shows the key is presentation-only, reword to the true consequence, and leavexAxis/yAxisaterror.No recommendation offered — surfaces 2 and 3 have not been read at the pin, and #15463's lesson is that the tier follows the measurement rather than the other way round.
Filed unassigned and unlabeled for triage — not started, no branch.
packages/lint/src/validate-widget-bindings.ts(the #15463 file) is NOT involved.Generated by Claude Code