Uh oh!
There was an error while loading. Please reload this page.
fix(sdui-parser): port the unconsumed dashboard-widget-option warning in lockstep with objectui - #12927
Conversation
… in lockstep with objectui `DashboardWidgetOptionsSchema` ends in `.passthrough()`, so a dashboard widget `options` key no renderer reads parses, validates and lints cleanly — legal, silent and inert. objectui's copy of this parser has warned on that since the objectui#5709 ruling (2026-08-23); this repo's hoisted copy emitted nothing, so the same authored page produced a diagnostic on one surface and silence on the other — the dialect split the two copies' invariant forbids. Ports `checkDashboardWidgetOptions` and its module, and calls it at the end of `validateTree`'s known-component branch. The module's executable region is a byte-equal port of objectui's save for ONE token, stated at the site: the emitted `code` is an inline literal here rather than the exported constant, because `check:dispatcher-error-vocabulary` reads a SCREAMING_SNAKE constant at a `code:` position and cannot reduce a kebab-case value, and that finding cannot be declared away. A test pins the literal equal to `UNCONSUMED_WIDGET_OPTION` so the two spellings cannot drift. The file header is the other deliberate divergence: objectui's cites renderer files and census tests this repo has none of. Additive by construction — `warning` severity only, reporting an already-inert state — so what this copy accepts and rejects is unchanged; a dedicated test pins that. Unit tests are the only witness: this repo resolves no `sdui.manifest.json`, so `validateJsxPages` runs parse-only and `validateTree` is not reached from the production gate today. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 2 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6e0b430ed4de1621843a882836d615a93f61c3cd && git checkout 6e0b430ed4de1621843a882836d615a93f61c3cd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 750fff55921e1e4a6906cbe30c22ec87cf94350b 02a0ab545dba9e9a6529ac595b7f183b2a5bd706 && git checkout -B drift-repro 750fff55921e1e4a6906cbe30c22ec87cf94350b && git merge --no-ff 02a0ab545dba9e9a6529ac595b7f183b2a5bd706
node scripts/docs-audit/affected-docs.mjs --json 750fff55921e1e4a6906cbe30c22ec87cf94350b
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#12810
Ports
checkDashboardWidgetOptionsand its module into this repo's hoisted@objectstack/sdui-parser, so a dashboard widgetoptionskey riding thespec's
.passthrough()that no renderer consumes draws the sameunconsumed-widget-optionwarning naming the consumed set (the objectui#5709ruling, 2026-08-23). Item 1 of the card, which triage scoped this to. Item 2
was split to #12814 and is already in the tree; nothing here touches it.
Reference point, re-established before porting
The card was measured against
objectui@46f0bb4; that had moved. This port isFROM objectui
f241a4df4a7d66c5750092addd60018fbc0a9100(2026-08-2807:58 UTC), read from a fresh clone. Ported INTO objectstack
aef1b7e6.The card's four premise claims were re-measured at
aef1b7e6and all reproduce:checkDashboardWidgetOptionsinvalidate.ts0 · a dashboard-widget-optionsmodule in that src tree 0 · positive control
validateTree1 ·input-type.tspresent 1. Item 1 was genuinely unported.
What is byte-equal, and the one token that is not
The module's executable region (its first
importline to EOF) was extractedfrom both copies and hashed. Two readings, both stated because only the pair is
honest:
exactly one line plus the comment explaining it.
92664a507d6f09f454ff563bb8bde4ceb3666dd9a14b10f9d3ada323f07dd1f7on bothsides. BYTE-EQUAL.
Per the card's own standard, a hash-equality claim without an ablation is a
claim about the comparator. Three legs, all run:
severity: 'warning'mutated to
'error'(mutation confirmed on disk: the twogrep -ccountsflipped 1/0 and 0/1) reported DIVERGED;
FAILURE rather than answering on an empty region;
difference would make reading B meaningless: one further token mutated inside
the normalized text still hashes differently.
The one deliberate token. objectui writes
code: UNCONSUMED_WIDGET_OPTION.This repo runs
check:dispatcher-error-vocabulary, whoseobjlitconstshapereads a SCREAMING_SNAKE constant at a
code:position and must reduce it to aliteral — and its literal grammar is
[A-Za-z][A-Za-z0-9_]*, which a KEBAB-casevalue cannot satisfy. The constant form is therefore reported as an unresolvable
code constant, and that finding class cannot be discharged by a declaration row.
Measured, not assumed: with the constant form the gate reported exactly 1
finding, naming this file; with the literal it reports 0.
unconsumed-widget-optionis a parser DIAGNOSTIC code, not an ADR-0112 wirecode, and an inline quoted literal is the form both vocabulary gates already
accept for the six sibling diagnostic codes in
validate.ts(
unknown-component,unknown-prop,not-a-container,inert-expression,type-mismatch,invalid-enum). The emitted VALUE is unchanged, the constantstays exported and still drives the
suppressWarningsescape hatch, and a testpins the literal equal to
UNCONSUMED_WIDGET_OPTIONso the two spellings cannotdrift apart. Reasoning is at the site, not only here.
The file header is the other stated divergence, and it has to be: objectui's
header cites the maintenance machinery that derives the census — its
DatasetWidget/DashboardRendererread sites, its docs claim, its two censustests. None of those exists in this repo (measured: there is no dashboard
renderer package here at all), so copying those sentences would ship claims this
checkout cannot support. The rewritten header says instead where each half of
the census is authoritative, and both halves that ARE measurable here were
verified in this repo: the five declared keys are exactly the five properties
DashboardWidgetOptionsSchemadeclares inpackages/spec/src/ui/dashboard.zod.ts,and
descriptionis documented onWidgetLike.optionsinpackages/spec/src/system/i18n-resolver.tsas the one keytranslateDashboardwrites.
validate.tsitself is now, after normalizing theManifestValidationResult/ValidationResultnaming, textually identical to objectui's except for thethree LOCKSTEP comment blocks this repo adds. That is the whole remaining
difference between the two files, enumerated by diff rather than asserted.
The port is additive — pinned, not assumed
The card was split from #12814 precisely because this half only reports an
already-inert state. Three assertions pin that this copy's accept/reject set did
not move: every emitted diagnostic is
warning; a page whose only defect is adead option key still passes the save gate (
oktrue); and a tree carrying oneof each pre-existing defect produces exactly the pre-port code set. If any of
those flips, this stopped being a port and needs re-grading.
Tests are the only witness, and here is what they do and do not cover
This repo resolves no
sdui.manifest.json(there is none in the tree, and@objectstack/console/dist/sdui.manifest.jsonis absent), soresolveSduiManifest()returns undefined andvalidateJsxPagesrunsparse-only —
validateTreeis not reached from the production gate at alltoday. A green CI proves almost nothing about this change.
20 unit tests were written for it, and they were ABLATED rather than trusted.
With the call site deleted (confirmed on disk: occurrence count 1 then 0, and a
non-empty
git diff HEAD), 11 of the 20 fail. Restored, 20/20 pass. Therestore was proved, not assumed: the file's blob hash equals its HEAD blob hash
(
095cde7febbed96f31f12f8cbd4ea56608274ec3) andgit diff HEADis 0 bytes. Norebuild was needed for the mutation to bite, which is itself the evidence that
these tests read SOURCE and not a stale
dist/— the import is relative andwithin the package, so the
exports-to-distresolution hazard does not applyhere.
Covered: the emitted envelope field for field; that the message names the
full consumed set (derived from the array the implementation prints, never
restated); the general mechanism on a second widget type and on the
dashboard-gridhost; index-naming; every widget in the array visited; the fullaccepted set drawing nothing, with a one-extra-key control separating "accepted"
from "the check stopped running"; every out-of-scope shape (no
dataset, emptydataset, legacycomponentformat, deferred expression bags, a non-hostcomponent, an unknown host); and the
suppressWarningsescape hatch with anunsuppressed twin as control.
Not covered, and no test here can cover it: that any of this runs in
production. That needs the manifest wiring, which is unowned — filed as a
finding, see below.
Also not covered: the renderer half of the census. This repo has no
dashboard renderer, so the accepted set is pinned literally rather than
re-derived, and the pin names the two files to re-read when either half moves.
The drift hazard that leaves behind is filed as a finding too.
Item 3, assessed and deliberately declined
Triage marked it non-blocking and "may ride along", and explicitly forbade a
rename sweep. Declined, with reasons rather than silence:
ValidationResultis exported throughexport * from './types.js'on aPUBLISHED package. Renaming it to objectui's
ManifestValidationResultis abreaking change to
@objectstack/sdui-parser's public surface, bought for anaming difference triage itself graded as "naming only, no behaviour".
markHtmlTierNode/provenance.tsis symbol-keyed and invisible to JSON andto diagnostics. Porting it into this copy would add a marker with zero read
sites in this repo — a declared-but-unconsumed surface, which is the shape
this repo's scope discipline says not to add.
Neither is a diagnostic-code divergence, so neither is part of the invariant
this card exists to restore.
Verification
Full local gate union re-run on the final commit
5e5ff2c7, all green:check:cross-package-test-inputs·check:test-source-alias·check:type-source-resolution·check:published-files·check:page-declaration-shape·check:slot-lookup·check:objectui-changeset·check:changeset-gate-self-tests·check:pm-half-states·check:objectql-double-limit·check:engine-double-contract·check:where-matcher·check:dispatcher-error-vocabulary·check:error-code-casing·check:query-options-erasure·check:type-check-coverage·check:nul-bytes·check-empty-changeset·check-changeset-no-major·check-adr-0087-registration·check-ci-filter-parity·check-comment-mask-adoption·check-plugin-teardown-shape·docs-audit/check-affected-docs.That list was re-derived from the ACTUAL diff via
scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, not taken from the dispatch lead — thederivation added ten families the lead did not name, and one of them
(
check:dispatcher-error-vocabulary) is the one that went red.pnpm --filter @objectstack/sdui-parser exec vitest run— 5 files, 60 testspassed.
pnpm --filter @objectstack/sdui-parser typecheck— clean, and itreally covers the new tests:
tsc --listFileslists both the new test file andthe new module (count 1 each), so "typecheck green" is not the vacuous kind.
Declared narrowing. Repo-wide
pnpm lintwas not run; ESLint was run overthe four changed source files (
--no-inline-config --format json): 4 files, 0errors, 0 warnings. This is a measurement rather than a gap because this repo
runs one
eslint.config.mjswhich, in its own words, "never enables type-awarelinting (no
parserOptions.project, no typed@typescript-eslintrules) for ANYfile" — so a five-file diff cannot move the verdict on any file it does not
touch. CI runs the full farm regardless.
Two gates could not be measured here and are recorded as NOT MEASURED rather
than green:
scripts/pm/check-half-states.mjsexits 3 ("no reading at all" — itlisted no issue), and
check:type-check-debt --re-measureneeds the wholeworkspace closure built, which did not fit the container's foreground cap while
another agent held the build lock.
Changeset
Derived, not assumed.
packages/sdui-parserdeclares noprivatefield andcarries
publishConfig.access: public, so it publishes; this change adds fourexported symbols and a new diagnostic code that
validateTreeandcompileemit, which consumers see. The
git logreceipt agrees: the immediatelypreceding sibling port of this same card family (
4a9f46179, #12857) carried.changeset/sdui-parser-union-arm-type-mismatch.md. Added asminor, matchingit.
Findings filed
Three, all
finding+ a topic label, none assigned, none queued:sdui.manifest.json, sovalidateJsxPagesrunsparse-only and
validateTreeis dead code in the production gate. This is thesequencing gap triage called out as binding and unowned; it is what makes this
port and Port the union-arm
type-mismatchcheck into the hoistedsdui-parser— the half of the lockstep debt that CHANGES what this copy accepts and rejects #12814 actually run.gates as a literal and an undischargeable finding as a constant. This is the
hole the one-token divergence above works around per-site.
CONSUMED_WIDGET_OPTION_KEYSis pinned rather thanderived, so a new declared key in
DashboardWidgetOptionsSchemawould makethis warning a false positive on legal metadata, silently.
Generated by Claude Code