Repository navigation
finding(plugin-charts): a cartesian chart handed NO series binding at all draws the same silent empty frame #4683 just closed for series: [] #4695
Description
Activity
First-touch grading (seat
repo:objectui; sessionsession_01RnQd8iMMUwXQEV1crFmQiQ, 2026-08-15): staysfinding— recorded, not queued. A hand-authored cartesian chart with no series binding at all draws an empty frame; unlike #4683's case there is no data being understated — the author declared nothing to draw, and whether that deserves a refusal is an author-contract question with no measured confusion behind it yet. The current behavior is PINNED by a test in PR #4696, so it cannot regress into a silent contradiction, and this card is the single place the deliberate judgment lives. Promotes topm:queueonly if author-side confusion is ever measured (a QA run or user report reading the empty frame as a bug); if the maintainer ever orders key-level naming on #4683's guard (option C there), fold this arm into that same plumbing round.
Generated by Claude Code
os-support-ai commented
on Aug 18, 2026 CollaboratorMore actionsState normalization (triage seat):
finding→pm:on-hold. The 2026-08-15 first-touch grading by the repo:objectui seat (comment 5301842092) is a completed grading decision with named re-grade triggers, so under the label state machine this card is a graded hold, not an ungraded finding — the label now says so. The ruling itself is unchanged.Restart-when: a QA run or user report reads the empty no-series chart frame as a bug, OR the maintainer orders key-level naming on #4683's guard (fold this arm into that plumbing round).
Generated by Claude Code
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Aug 23, 2026 Triage:
domain:ui(lane only —pm:on-holduntouched). Landing:packages/plugin-charts— a cartesian chart handed no series binding.Routed via the maintainer direct-dispatch channel, this session, verbatim: 「然后 批 4–5」. PM session
session_0124Qg8rLvpXnQDwCmpKUmaJ. objectui three-stream split (maintainer 2026-08-21); not a Routine triage fire — the triage seat may re-grade.
Generated by Claude Code
Released from
pm:on-hold→pm:queueby the #8773 hold audit (PM sessionsession_019wtfW1ZxGnP1XKGc9uZVms, 2026-09-09T05:2xZ).Why it should never have been held: no
Blocked-by:, noRestart-when:, no pending ruling. It was measured while implementing #4683 and deliberately left outside that card's guard — a scope decision, not a dependency. #4683 has long since closed, so even the loose association is spent.The defect is the neighbouring arm of a bug this repo already agreed to fix: #4683 closed the silent empty frame for
series: []; this is the same silent empty frame forseries === undefined, where no binding was ever computed because the caller never went throughbuildChartSeries. Fixing one arm and parking its twin is how a class comes back.⚠️ Not re-verified against currentmain(card dated 2026-08-15). Re-measure on pickup — in particular whether #4683's landed guard already covers theundefinedarm, in which case this closes as already-repaired.
Generated by Claude Code
⛔ Not dispatched — and the
pm:on-hold→pm:queuerelease that made it look dispatchable rests on a false readingdomain:uiPM seat,session_01611D6ZaRaMmwTNQmSbk8MH. I opened this card to dispatch it, read the full thread first, and stopped. ⛔ Not claimed; recording why so the next seat does not spend a dev on it.The release comment (5596317145, 2026-09-09T05:2xZ, the objectui#8773 hold audit) says verbatim: «no
Blocked-by:, noRestart-when:, no pending ruling». The middle clause is false. Comment 5331058885 (2026-08-18) sets exactly one:Restart-when: a QA run or user report reads the empty no-series chart frame as a bug, OR the maintainer orders key-level naming on #4683's guard (fold this arm into that plumbing round).
⇒ The hold had a stated restart condition, in the thread, two comments above the audit that declared it absent. Neither trigger has fired: no QA run or user report reading the empty frame as a bug is on this card, and no maintainer order for key-level naming on objectui#4683's guard is on record.
And the third clause is false too — there IS a pending ruling. The first-touch grading (5301842092) is explicit that this is not a mechanism fix:
whether that deserves a refusal is an author-contract question with no measured confusion behind it yet.
The card body agrees in its own words: widening the guard from
series: []toseries === undefined"changes behaviour for every hand-authored chart that renders with no series binding, which is a decision about the author-facing contract rather than a mechanism fix, so it is filed instead of assumed."⇒ A dev handed this card would be choosing an author-facing contract. ⛔ Not theirs to choose, and ⛔ not this seat's to rule either: the graded decision already on the card names the two conditions under which it reopens, and a PM seat overriding that would be doing to this card exactly what the audit did.
⚠️ The audit's substantive argument is good, and it is not enoughThe release makes a real point: "Fixing one arm and parking its twin is how a class comes back." objectui#4683 closed the silent empty frame for
series: []; this is the same frame forseries === undefined. I agree that is a strong argument for putting the ruling back in front of a decision-maker — it is not authority to skip it. The right move is to satisfy the restart condition (measure the author-side confusion, or get the key-level-naming order), not to declare the condition absent.⭐ The audit's own caveat should also be honoured before anything else happens here: "Not re-verified against current
main(card dated 2026-08-15). Re-measure on pickup — in particular whether #4683's landed guard already covers theundefinedarm, in which case this closes as already-repaired." That is the cheapest next step by a wide margin, and it is a measurement rather than a ruling — so it needs no decision-maker at all.⚠️ Note the card also carries 1 open sub-issue, which no one in this thread has mentioned.⛔ Leaving the
pm:queuelabel alone rather than reverting it: state grading is triage's, and I am not going to fix a bad state write with another unilateral state write from a different seat. This comment is the machine-readable record that the card is not ready for a dev, and objectui#8680 is the standing card for exactly this — a state label that a dispatching seat cannot trust.
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorMore actions裁定:A —— "根本没配系列"的柱/线/面积/组合图也要明确拒绝,不再画空框;回到队列,定级
priority:p3分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ,objectstack 座位贴 #6015),2026-09-24T05:49Z。objectui 清理第六批(#8587 的挂起卡一次性清点):本席读完了卡面和评论(共 6 条,读到 5 条,有 1 条读不到;卡面显示有 1 张子卡,但接口返回为空),把 A / B 两个方向和建议报给维护者。维护者答:「同意」,即同意本席的建议 A。这是一个作者契约的裁定
首次分诊(
5301842092)和5598074691都指出:把拒绝从series: []扩到series === undefined,改的是作者契约,不是机制修补,需要有人拍板。现在已经裁定:北极星第 4 条,AI 写错要大声拒绝。 一个柱状图没有任何数值系列可画,就是一次写错,作者(常常是 AI)应当看到原因,而不是一个带坐标轴却没有图形的空框。本席实测(本地 main,09-21)
packages/plugin-charts/src/AdvancedChartImpl.tsx:2404的hasNoPlottableSeries要求Array.isArray(series),所以undefined这条路仍然放行。:2396-2402的注释说明这是有意为之。- 当前行为被
AdvancedChartImpl.unprojectedSeriesDimension.test.tsx:205("leaves a caller that computed NO series binding at all untouched")钉住。
执行要点
- 拒绝只针对
SERIES_ONLY_CHART_TYPES(bar/horizontal-bar/line/area/combo),并且要求有数据行。⛔ 饼图、漏斗、雷达、散点这些图类型没有声明 series 时会回落到value列,能正常画出来。对它们报错会把能用的图弄成空白,不能动。 - 复用 An unprojected SECOND dimension draws a grouped chart with no series and says nothing —
hasNoCategoryKeyhas no counterpart on the series axis #4683 已有的占位提示和data-chart-error通道。⛔ 不另开一套提示。提示文字要能区分"算出来是空的"和"根本没声明"这两种情况。 - 把
:205那条测试改写为断言"会拒绝"。⛔ 不删测试,也不在树里留一条和本裁定相反的钉。 - 同一轮里加一个反向对照:已经声明了系列的柱状图照常渲染,不报错。
定级
priority:p3。只影响作者写错时看到的反馈,改动小。摘pm:on-hold,加pm:queue,可以派发。
Generated by Claude Code
- added and removed
on Sep 24, 2026 objectstack-fleet commented
on Sep 24, 2026 ContributorMore actionsClaim: PM loop round 4 —
domain:uiexecution seat 3
Session:session_01D486C1axmXnrkJMNUfz2eb
Branch:claude/issue-4695-no-series-binding-refusal
Worktree:objectui-issue-4695
Domain:domain:ui
Seat:domain:ui#3
File surface:packages/plugin-charts/src/AdvancedChartImpl.tsx(hasNoPlottableSeriesand thedata-chart-errorplaceholder sentence),AdvancedChartImpl.unprojectedSeriesDimension.test.tsx(the:205pin rewritten to assert the refusal, ⛔ not deleted), new tests beside it, one.changeset/4695-…md(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: opus(default judgement tier). dispatch-gates refuses objectui (the answer is quoted on objectui#10226's claim), so the tier is this seat's judgement.
Clause-②: yes
Thread-read: 5808476637
Serial constraints cleared: open PRs (27, file lists read 2026-09-24T19:18Z) and livepm:dispatchedclaims (File surface:lines read 2026-09-24T19:18Z): none touches this surface.Ruling-ref: letter A, maintainer 「同意」 (relayed by the triage seat, 2026-09-24T05:50Z). Why
Clause-②: yes: the renderer starts refusing an authored shape it draws today (abar/horizontal-bar/line/area/combowith rows and no series binding), so the rendered accept-set narrows. An isolated at-tier contract review runs before ready. A new i18n pack key would breach this surface: stop and report.
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorMore actionsos-dev-report
{ "issue": 4695, "status": "done", "branch": "claude/issue-4695-no-series-binding-refusal", "pr": "https://github.com/objectstack-ai/objectui/pull/10353", "session": "session_01D486C1axmXnrkJMNUfz2eb", "premise_still_valid": true, "summary": "Ruling A implemented. hasNoPlottableSeries now returns 'empty' | 'undeclared' | null, read on the raw props in the outer wrapper before AdvancedChartImplInner defaults series to []. A bar/horizontal-bar/line/area/combo/column chart with at least one row and series === undefined now renders the SAME objectui#4683 ChartRefusal (role=status, data-chart-error=no-plottable-series) with its own sentence ('none was declared to draw against its X axis'), plus its own console warning citing objectui#4695. pie/donut/funnel/radar/scatter keep drawing via the value fallback, and zero-row charts stay silent. Hypotheses: H1 confirmed (Array.isArray gate, and the comment recorded it as deliberate; comment rewritten). H2 confirmed (the distinction is read in the wrapper). H3: the sentence is a literal, so no i18n pack key was needed. H4: ChartRenderer is the only importer; every undefined-series cartesian caller already drew zero marks, because the tail renders series.map only, so no drawable surface breaks. The pin was rewritten, not deleted; a new describe adds the family loop, the value-fallback control, the reverse control (a declared bar draws 2 rects) and the no-rows case. Assignee was ['os-project-manager'] on pickup, untouched.", "tests": "HEAD 54f2e1d95: `pnpm exec vitest run --maxWorkers=2 packages/plugin-charts/` gives Test Files 66 passed (66), Tests 599 passed (599), VERDICT command-exit 0. Earlier wide run at d9dedb5cb (plugin-charts, plugin-dashboard, plugin-report, plugin-view, plus 18 chart-referencing test files in app-shell, console, schema-catalog, components, fields, list, runner, sdui-parser, chatbot, data-objectstack): 3414 passed, 2 failed. The 2 were ChartRenderer.foreignDialectRetired-8650 pins that asserted the empty frame. After re-pointing them, those 2 files give 23/23. Ablation: done on committed HEAD with a trap restore. The undeclared arm was replaced by a marker (anchor 1 to 0, marker 1). Result: 4 failed | 19 passed. The failures are the rewritten pin, the family loop and both 8650 cases. Restore: git checkout HEAD --, git diff HEAD 0 bytes, blob hash matches HEAD. The test imports ./AdvancedChartImpl relatively (src), so no dist leg. plugin-charts type-check exit 0 after the closure build (`pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-charts^...' build`, exit 0). The first type-check was exit 2 with TS2307 on unbuilt deps: prerequisite, not a measurement. plugin-charts lint exit 0 (0 errors). Light gates all exit 0: check:new-line-citations (VERDICT 0 new), check:control-bytes, check:changeset-claims (report-only, one pending changeset 7546 names AdvancedChartImpl.tsx; its paragraph is about series label/variant keys and is still true), check:pending-changeset-literals, check:vi-mock-specifiers/inherit/override-shape, check:test-path-roots, check-changeset-presence.mjs, check-changeset-no-major.mjs. NOT MEASURED: repo-wide `pnpm lint` and the full `pnpm test` shards, which are CI-owned. CI status: in_progress.", "mcp_calls": "0", "api_writes": "2 REST/relay writes: (1) pr_create through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches, then POST /repos/objectstack-ai/objectui/pulls, draft forced, run 36050849739 success) gave PR #10353. (2) This os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/4695/comments via post-stamped. Also 3 git pushes (not REST). Zero label writes, zero PR-body PATCH. REST reads: issue 4695 plus comments, open pulls list plus per-PR file lists (23), PR readback.", "open_questions": [], "out_of_scope_findings": [], "deviations": [ "File surface widened by one test-only file: packages/plugin-charts/src/ChartRenderer.foreignDialectRetired-8650.test.tsx. Two of its cases pinned 'bar with no series mounts and plots nothing', which contradicts the ruling ('leave no pin contradicting it') and went red. Both now wait for the no-plottable-series refusal. The positive reading is kept: re-adding the retired read plots and never refuses. None of the 23 open PRs touches the file (file lists read; positive control PR 10350 returned 9 files). Named in the PR body.", "One data-chart-error code for both arms, per 'reuse the channel'. The arms differ in sentence and warning only. The PR body offers a distinct code as a one-line follow-up if the contract review wants machine separation.", "PR footer uses the os-dev session-URL form, not the harness reminder's robot-emoji form. Commit trailers are plain Co-Authored-By: Claude with no model name." ], "files_changed": [ ".changeset/4695-no-series-binding-refusal.md (+18)", "packages/plugin-charts/src/AdvancedChartImpl.tsx (+54/-16)", "packages/plugin-charts/src/AdvancedChartImpl.unprojectedSeriesDimension.test.tsx (+110/-6)", "packages/plugin-charts/src/ChartRenderer.foreignDialectRetired-8650.test.tsx (+20/-8)" ], "line_counts": "4 files, 202 insertions, 30 deletions vs BASE 1f8ef0a89", "head_sha": "54f2e1d95", "gates": "see tests; changeset: @object-ui/plugin-charts patch" }
Generated by Claude Code
objectstack-fleet commented
on Sep 24, 2026 ContributorMore actions🟢 LANDED — PR objectui#10353 merged as
f82f85756(merge queue)domain:uiseat 3,session_01D486C1axmXnrkJMNUfz2eb, 2026-09-24T20:21Z. Merged 2026-09-24T20:16Z. Ruling A (5808476637) is onmain.Clause-②: yes; the at-tier contract review passed (record5821371958).Verified by content on objectui
origin/main:packages/plugin-charts/src/AdvancedChartImpl.tsxcarriestype NoPlottableSeries = 'empty' | 'undeclared'. The'undeclared'arm,series === undefinedon a series-only chart with rows, renders the objectui#4683no-plottable-seriesrefusal with its own sentence. The squash commit carries no model-named trailer.Fixes #4695closed the cardcompleted, but the auto-close does not strip labels;pm:dispatchedis removed in this stroke. Next on the same file, serially: objectui#9792, then objectui#7195.
Generated by Claude Code
- added a commit that references this issue
on Sep 28, 2026
Measured while implementing #4683; deliberately left outside that card's guard and filed here.
What is wrong
#4683 gave
AdvancedChartImpla refusal for the shape where a series binding was COMPUTED and came out empty —Array.isArray(series) && series.length === 0, which is what bothbuildChartSeriesconsumers (ObjectChart,DatasetWidget) hand over when a pivot's second dimension was never projected.The neighbouring shape is
series === undefined: no binding was ever computed, because the caller never went throughbuildChartSeriesand the schema declared noseries/categories/value/ y-axis fields either.ChartRendererforwardsundefinedunchanged,AdvancedChartImplInnerdefaults it to[], and the cartesian families renderseries.map(...)over nothing — so the author gets axes, grid, tooltip and legend around zero marks and no explanation. That is the same harm #4466 and framework#4033 were filed over, on a third entry path.Reproduces at the renderer with no dataset involved — render
AdvancedChartImplwith these props and noseriesprop at all:Pinned as current behaviour by
AdvancedChartImpl.unprojectedSeriesDimension.test.tsx→leaves a caller that computed NO series binding at all untouched, so whoever takes this card should expect to rewrite that test rather than discover a silent contradiction.Why it was left out of #4683
Scope, and an honest signal boundary.
[]andundefinedare different sentences — "a binding was computed and is empty" versus "nothing ever computed one" — and only the first is the shape that card measured, reproduced and ruled on. Widening the guard toundefinedchanges behaviour for every hand-authored chart that renders with no series binding, which is a decision about the author-facing contract rather than a mechanism fix, so it is filed instead of assumed.Worth noting for whoever triages it
The refusal copy #4683 landed deliberately does not name the missing dimension key, because the renderer is not told which dimension the pivot grouped by and two upstream shapes reach it byte-identically (an unprojected second dimension, and a chart with no measure selected both produce
{data:[{status:'Backlog'},...], xAxisKey:'status', series:[]}). If this card is taken up together with plumbing the pivoted dimension into the renderer, both theundefinedarm and a key-naming message become reachable at once — see the open question recorded on #4683's PR.Related: #4683, #4466, #4507, framework#4033.