Skip to content

docs(spec): drill the dashboard chartConfig liveness row to one verdict per key - #17861

Merged
os-bill merged 2 commits into
mainfrom
claude/issue-17385-chartconfig-liveness-drill
Sep 12, 2026
Merged

docs(spec): drill the dashboard chartConfig liveness row to one verdict per key#17861
os-bill merged 2 commits into
mainfrom
claude/issue-17385-chartconfig-liveness-drill

Conversation

@os-bill

@os-bill os-bill commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

What this is

packages/spec/liveness/dashboard.json's widgets.chartConfig row carried one blanket live over all 14 keys ChartConfigSchema declares. This gives it children — one verdict, with evidence, per key — and corrects the one sentence in its note that stopped being true this morning.

Clause-②: no — no schema key moves, no accept set widens or narrows, no export changes. A liveness row is a claim about the schema, not the schema.

Executes the maintainer's principle ruling on decision batch #112, verbatim and untranslated: 「这批的问题,我们的项目以objectstack 协议为准,文档应该以实际实现为准。」 This PR is the 文档以实际实现为准 half.

Part of #17385 — see What remains below. Deliberately not a closing keyword: the protocol half is ruled but unlanded, and this card has to stay open to carry it.

The measurement

Re-measured by hand against this checkout's own .objectui-sha pin 53ded82bf7a494f54e344e19099dbf00854b8694, read with git show at PIN colon PATH — not at that sibling checkout's HEAD, which sits on an unrelated branch (claude/focused-archimedes-wqa3gp, 3fbdd4a2).

key verdict what consumes it, at the pin
title live chart-presentation.ts#chartConfigPresentation — non-empty string lowered onto the chart schema, drawn in the ChartFrame
subtitle live same guard, same frame
description live same guard; becomes the chart container's role="img" + accessible name
colors live same function, split two ways: a string[] becomes the positional palette, a {value: color} record becomes categoryColors merged over the dimension field's own option colours
height live same function, guarded finite and > 0
showLegend live same function — the first key ever lowered on this face
showDataLabels live same function; printed as a Recharts LabelList
annotations live same function, non-empty array only; drawn as ReferenceLine / ReferenceArea
interaction live same function, non-array object only; the tooltip toggle plus Brush
xAxis live #mergeAuthoredPresentation#axisPresentationtitle / format / min / max / stepSize / showGridLines / position / logarithmic forwarded, field dropped
yAxis live the same, per array entry; the COUNT of entries is itself presentation (it declares the secondary axis) so every slot survives
series live #mergeAuthoredSeries#seriesPresentation — mark, axis side, label, colour, stack, variant, dashArray, opacity forwarded, name dropped
type dead nothing. chartConfigPresentation has no type branch; DatasetWidget picks the family from the WIDGET's own type through CHART_TYPE_MAP, and the ...chartPresentation spread after it carries no type
aria dead nothing, on either face. The chart implementation declares no aria prop; the ARIA injection reads the flat ariaLabel / ariaDescribedBy / role; the react tier's dataProps omits it

The two dropped keys are not an oversight — ChartAxis.field and ChartSeries.name are dropped so that membership stays with the dataset: the renderer synthesises series out of yAxis[].field when a chart declares none, so a forwarded field would be a live membership channel on a dataset-bound widget.

Both dead verdicts are pinned in the renderer repo as negatives, by name"ignores chartConfig.type — the widget type owns the chart family", "ignores chartConfig.aria — no accessible name appears anywhere", "ignores aria — nested and flattened". That is what makes them re-askable rather than asserted.

Neither dead verdict is acted on here. Recording a verdict is what feeds the ADR-0049 enforce-or-remove worklist; executing one moves a published accept set.

Controls on every zero

  • chartConfig in DashboardRenderer.tsx at the pin = 0 occurrences (grep -o | wc -l, occurrences not lines). Lit control DashboardRenderer in the same file = 18. Dark control zzchartConfigzz = 0.
  • Whole-tree sweep at the pin: 21 files mention chartConfig. Every one read. The 19 hits in app-shell/src/views/ObjectView.tsx are a local variable bound to viewDef.chart — the view surface, a different key; types/src/designer.ts and types/src/complex.ts are chartConfig?: any declarations; normalizeChartSchema.ts is a comment. The only consumer of a dashboard widget's chartConfig is DatasetWidget@object-ui/core's chart-presentation.

Three of this card's own premises were false, and two of this dispatch's

Re-derived by symbol and by exact sentence, not by the path:line on the card:

  1. The card says the row reads evidence "chart-config bag forwarded to the chart renderer". It has not since 2026-08-09; the sentence survives only as quoted history inside the row's own note.
  2. The card says ChartConfigSchema declares 12 keys. It declares 14.
  3. The card says the row's note still claims the walk drills one level. It did when it was written this morning — PR fix(spec): drill the liveness ledger to the depth it declares, and report what the walk did not reach #17810 landed the recursion — so that sentence is now false and is replaced here. That is the third time this one row has been re-anchored for rot, and the first time the rot was in a claim about the instrument rather than about a citation.

What remains, and why this stops here

Half 2 asks what the protocol must state about an authored chartConfig. It is ruled — batch #121, recorded on the card 2026-09-12 as C+D — and what it rules is that xAxis / yAxis / series and type are refused by name on a dataset-bound widget, with a major changeset and an ADR-0087 semantic migration. That moves a published accept set, which is the fence this dispatch set: it is the seat's to re-declare, ⛔ not this round's to decide. aria's ADR-0049 disposition is its own card, #17751.

验收备注

  • The drill surfaced six containers one level further downxAxis, yAxis, series, annotations, interaction, aria, 39 child keys between them. They are recorded in the shrink-only baseline, not drilled: fanning this row's verdicts down over them would manufacture verdicts, which the drill rule forbids by name, and the evidence work is real (three of the six are live as a whole while one of their own sub-keys is inert by design). Net on the ratchet: one row out, six in; 555 → 580 child keys recorded. Not new debt — those 39 keys were never in any population, because the walk stopped at chartConfig.
  • The dashboard row's Notes cell in liveness/README.md had already drifted one dead behind the generated artifact before this change (it ends at 34/7; the artifact read 34/8). Recorded in the cell rather than silently corrected — the count columns are generated and are the authority.
  • chartConfig.type is the one required key of ChartConfigSchema, so every dashboard author who writes a chartConfig at all is made to write a key this face ignores. Already carried by the ruled half 2; noted here so it is not read as new.

Declared deviation from the claim's file face

The claim comment declared scripts/liveness/** not touched"that tool landed today and is not this card's". Two paths under it are touched, and neither is the tool:

  1. scripts/liveness/undrilled-containers.baseline.jsonnot optional. Drilling a recorded container makes its baseline row stale, and a stale row fails the gate by design; the six newly visible containers need a disposition for the same reason. There is no way to land half 1 without this file.
  2. scripts/liveness/check-liveness.test.ts — two pins landed with the recursion (PR fix(spec): drill the liveness ledger to the depth it declares, and report what the walk did not reach #17810) use this card's coordinate as their worked example of depth-2 blindness. Drilling it makes one assert an absent coordinate and collapses the other's arithmetic to zero, so it graded the fixture instead of the walk. Re-pointed at the shape rather than at the card: widgets.compareTo (the exact structural replacement) plus widgets.chartConfig.xAxis (one level deeper, which exists only because this drill landed), and the counts pin moves to widgets.options with its constant. Neither pin loses a property it asserted. Evidence: both failed before the repair (expected [ … ] to include 'dashboard/widgets.chartConfig'; expected 55 to be 68) and the file's 9 suites / 229 tests are green after it.

Verification

  • check:livenessexit 0 before and after. Its own verdict line: "every governed-type property, at every depth the ledger drills, is classified … every container inheritance is declared".
  • Ablation, two legs, on the committed tree. HEAD blob a5fdd41f, restored and blob-verified after each leg, under a trap … EXIT INT TERM with absolute paths. The gate reads liveness/*.json from source (check-liveness.mts imports ../../src/…), so there is no dist leg to preflight.
    • A — delete the drilled child showDataLabels (on-disk proof read first: occurrences 1 → 0; blob a1376c8c ≠ HEAD). Gate exit 1: ✗ 1 UNCLASSIFIED — dashboard/widgets.chartConfig.showDataLabels. This is the leg that matters: it proves the new rows are read at depth two and are data, not prose.
    • B — strip the objectui realm marker off one child's evidence (blob 1e5d2786 ≠ HEAD). Gate exit 1: 4 entries cite a file missing from this repo. Proves the evidence strings really are read and realm-attributed at this depth too.
    • Control, unmutated: exit 0. Predicted direction "turns red" both legs; observed "turns red" both legs.
  • pnpm --filter @objectstack/spec test (--project local) and test:repo (--project repo) — both run, exits below. They are different projects and the cross-corpus scanners live in the second.
  • pnpm --filter @objectstack/spec typecheck.
  • Derived families: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, re-derived after the changeset existed (4 paths → 6 paths, 53 → 67 commands), all run, reconciled with --ran.
  • eslint --no-inline-config over the whole repo (6655 files in eslint's own scope) rather than a narrowing argument. Of the six changed paths, only the .ts file is in eslint's configuration at all; the five data/doc files report "File ignored because no matching configuration was supplied."
  • Changeset by measurement, not by rule: npm pack --dry-run --json on a built packages/spec puts liveness/dashboard.json, liveness/README.md and liveness/state-counts.md in the tarball (38 files under liveness/; positive control dist/index.js ships) and leaves the baseline out (0 files under scripts/; negative control scripts/liveness/check-liveness.mts absent). The ledger ships, so this needs a changeset.

⛔ Draft on purpose. Not flipped ready, not enqueued, no auto-merge.


Generated by Claude Code

…ct per key

The row carried a single blanket `live` over all 14 ChartConfigSchema keys.
Per-key verdicts were not expressible until the liveness walk learned to
recurse: a `children` map at this coordinate sits at depth two, which the
one-level walk read, accepted and then ignored in silence.

Re-measured against this checkout's own `.objectui-sha` pin 53ded82bf7a4:

  12 live  nine chrome keys lowered by `chartConfigPresentation`
           (title, subtitle, description, colors, height, showLegend,
           showDataLabels, annotations, interaction), plus xAxis / yAxis /
           series, whose PRESENTATION merges onto the bindings the dataset
           selection derived while `ChartAxis.field` and `ChartSeries.name`
           are dropped so membership stays with the dataset.
   2 dead  `type`, which parses and does nothing because the widget's own
           `type` owns the chart family through CHART_TYPE_MAP; and `aria`,
           which has no reader on either face. Both are pinned as NEGATIVES
           by name in the renderer repo.

Neither dead verdict is acted on here: recording a verdict is what feeds the
enforce-or-remove worklist, and executing it moves a published accept set.

Drilling the row made six containers one level further down visible for the
first time (xAxis, yAxis, series, annotations, interaction, aria - 39 child
keys). They are RECORDED in the shrink-only undrilled-containers baseline
rather than drilled: fanning this row's verdicts down over them would
manufacture verdicts, which the drill rule forbids by name.

Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Co-authored-by: Claude <noreply@anthropic.com>
…hange drills

Both pins named `dashboard/widgets.chartConfig` as their worked example of a
container the one-level walk could not see. Drilling that row makes the first
assert an absent coordinate and collapses the second's arithmetic to zero --
the control tree now already carries the fourteen verdicts the mutation was
supposed to introduce, so it measured the fixture instead of the walk.

The visibility pin names the SHAPE at both depths instead: `widgets.compareTo`
is the exact structural replacement (a container that is a drilled child) and
`widgets.chartConfig.xAxis` is one level deeper again, a container under two
drilled levels that exists only because this drill landed. The counts pin moves
to `widgets.options`, a container the shipped ledger has not drilled, and its
constant moves with it (five keys in place of one blanket verdict).

Both were written alongside the recursion they guard and neither loses a
property it asserted.

Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 3 changed file(s) yielded no anchor (packages/spec/liveness/README.md, packages/spec/liveness/dashboard.json, packages/spec/liveness/state-counts.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/liveness/README.md, packages/spec/liveness/dashboard.json, packages/spec/liveness/state-counts.md) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json c88fa2ccdc9257182cc7bbbad9532bd8a59e1583packageMentionDocs.

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

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants