docs(spec): drill the dashboard chartConfig liveness row to one verdict per key - #17861
Merged
Merged
Conversation
…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>
Contributor
📓 Docs Drift Check
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
packages/spec/liveness/dashboard.json'swidgets.chartConfigrow carried one blanketliveover all 14 keysChartConfigSchemadeclares. This gives itchildren— 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-shapin53ded82bf7a494f54e344e19099dbf00854b8694, 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).titlechart-presentation.ts#chartConfigPresentation— non-empty string lowered onto the chart schema, drawn in the ChartFramesubtitledescriptionrole="img"+ accessible namecolorsstring[]becomes the positional palette, a{value: color}record becomescategoryColorsmerged over the dimension field's own option coloursheight> 0showLegendshowDataLabelsLabelListannotationsinteractionBrushxAxis#mergeAuthoredPresentation→#axisPresentation—title/format/min/max/stepSize/showGridLines/position/logarithmicforwarded,fielddroppedyAxisseries#mergeAuthoredSeries→#seriesPresentation— mark, axis side, label, colour, stack, variant, dashArray, opacity forwarded,namedroppedtypechartConfigPresentationhas notypebranch;DatasetWidgetpicks the family from the WIDGET's owntypethroughCHART_TYPE_MAP, and the...chartPresentationspread after it carries notypeariaariaprop; the ARIA injection reads the flatariaLabel/ariaDescribedBy/role; the react tier'sdataPropsomits itThe two dropped keys are not an oversight —
ChartAxis.fieldandChartSeries.nameare dropped so that membership stays with the dataset: the renderer synthesises series out ofyAxis[].fieldwhen a chart declares none, so a forwardedfieldwould be a live membership channel on a dataset-bound widget.Both
deadverdicts 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
deadverdict 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
chartConfiginDashboardRenderer.tsxat the pin = 0 occurrences (grep -o | wc -l, occurrences not lines). Lit controlDashboardRendererin the same file = 18. Dark controlzzchartConfigzz= 0.chartConfig. Every one read. The 19 hits inapp-shell/src/views/ObjectView.tsxare a local variable bound toviewDef.chart— the view surface, a different key;types/src/designer.tsandtypes/src/complex.tsarechartConfig?: anydeclarations;normalizeChartSchema.tsis a comment. The only consumer of a dashboard widget'schartConfigisDatasetWidget→@object-ui/core'schart-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:lineon the card:ChartConfigSchemadeclares 12 keys. It declares 14.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 thatxAxis/yAxis/seriesandtypeare refused by name on a dataset-bound widget, with amajorchangeset 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.验收备注
xAxis,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 areliveas 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 atchartConfig.liveness/README.mdhad already drifted onedeadbehind the generated artifact before this change (it ends at34/7; the artifact read34/8). Recorded in the cell rather than silently corrected — the count columns are generated and are the authority.chartConfig.typeis the one required key ofChartConfigSchema, so every dashboard author who writes achartConfigat 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:scripts/liveness/undrilled-containers.baseline.json— not 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.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) pluswidgets.chartConfig.xAxis(one level deeper, which exists only because this drill landed), and the counts pin moves towidgets.optionswith 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:liveness— exit 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".a5fdd41f, restored and blob-verified after each leg, under atrap … EXIT INT TERMwith absolute paths. The gate readsliveness/*.jsonfrom source (check-liveness.mtsimports../../src/…), so there is nodistleg to preflight.showDataLabels(on-disk proof read first: occurrences 1 → 0; bloba1376c8c≠ 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.objectuirealm marker off one child's evidence (blob1e5d2786≠ 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.pnpm --filter @objectstack/spec test(--project local) andtest: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.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-configover the whole repo (6655 files in eslint's own scope) rather than a narrowing argument. Of the six changed paths, only the.tsfile is in eslint's configuration at all; the five data/doc files report "File ignored because no matching configuration was supplied."npm pack --dry-run --jsonon a builtpackages/specputsliveness/dashboard.json,liveness/README.mdandliveness/state-counts.mdin the tarball (38 files underliveness/; positive controldist/index.jsships) and leaves the baseline out (0 files underscripts/; negative controlscripts/liveness/check-liveness.mtsabsent). The ledger ships, so this needs a changeset.⛔ Draft on purpose. Not flipped ready, not enqueued, no auto-merge.
Generated by Claude Code