Skip to content

fix(sdui-parser): port the unconsumed dashboard-widget-option warning in lockstep with objectui - #12927

Merged
os-elon merged 2 commits into
mainfrom
claude/issue-12810-dashboard-widget-options-port
Aug 28, 2026
Merged

fix(sdui-parser): port the unconsumed dashboard-widget-option warning in lockstep with objectui#12927
os-elon merged 2 commits into
mainfrom
claude/issue-12810-dashboard-widget-options-port

Conversation

@os-elon

Copy link
Copy Markdown
Collaborator

Fixes#12810

Ports checkDashboardWidgetOptions and its module into this repo's hoisted
@objectstack/sdui-parser, so a dashboard widget options key riding the
spec's .passthrough() that no renderer consumes draws the same
unconsumed-widget-option warning naming the consumed set (the objectui#5709
ruling, 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 is
FROM objectui f241a4df4a7d66c5750092addd60018fbc0a9100 (2026-08-28
07:58 UTC), read from a fresh clone. Ported INTO objectstack aef1b7e6.

The card's four premise claims were re-measured at aef1b7e6 and all reproduce:
checkDashboardWidgetOptions in validate.ts 0 · a dashboard-widget-options
module in that src tree 0 · positive control validateTree 1 · input-type.ts
present 1. Item 1 was genuinely unported.

What is byte-equal, and the one token that is not

The module's executable region (its first import line to EOF) was extracted
from both copies and hashed. Two readings, both stated because only the pair is
honest:

  • Raw: DIVERGED. The complete divergence is enumerated in the PR diff and is
    exactly one line plus the comment explaining it.
  • Normalized (drop that comment, fold the literal back to the constant):
    92664a507d6f09f454ff563bb8bde4ceb3666dd9a14b10f9d3ada323f07dd1f7 on both
    sides. 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:

  1. the comparator on a copy of objectui's file with severity: 'warning'
    mutated to 'error' (mutation confirmed on disk: the two grep -c counts
    flipped 1/0 and 0/1) reported DIVERGED;
  2. the comparator fed a file lacking the anchor refuses with an explicit
    FAILURE rather than answering on an empty region;
  3. the NORMALIZER was ablated separately, since a normalizer that erases a real
    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, whose objlitconst shape
reads a SCREAMING_SNAKE constant at a code: position and must reduce it to a
literal — and its literal grammar is [A-Za-z][A-Za-z0-9_]*, which a KEBAB-case
value 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-option is a parser DIAGNOSTIC code, not an ADR-0112 wire
code, 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 constant
stays exported and still drives the suppressWarnings escape hatch, and a test
pins the literal equal to UNCONSUMED_WIDGET_OPTION so the two spellings cannot
drift 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 / DashboardRenderer read sites, its docs claim, its two census
tests. 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
DashboardWidgetOptionsSchema declares in packages/spec/src/ui/dashboard.zod.ts,
and description is documented on WidgetLike.options in
packages/spec/src/system/i18n-resolver.ts as the one key translateDashboard
writes.

validate.ts itself is now, after normalizing the ManifestValidationResult /
ValidationResult naming, textually identical to objectui's except for the
three 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 a
dead option key still passes the save gate (ok true); and a tree carrying one
of 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.json is absent), so
resolveSduiManifest() returns undefined and validateJsxPages runs
parse-only — validateTree is not reached from the production gate at all
today.
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. The
restore was proved, not assumed: the file's blob hash equals its HEAD blob hash
(095cde7febbed96f31f12f8cbd4ea56608274ec3) and git diff HEAD is 0 bytes. No
rebuild 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 and
within the package, so the exports-to-dist resolution hazard does not apply
here.

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-grid host; index-naming; every widget in the array visited; the full
accepted set drawing nothing, with a one-extra-key control separating "accepted"
from "the check stopped running"; every out-of-scope shape (no dataset, empty
dataset, legacy component format, deferred expression bags, a non-host
component, an unknown host); and the suppressWarnings escape hatch with an
unsuppressed 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:

  • ValidationResult is exported through export * from './types.js' on a
    PUBLISHED package. Renaming it to objectui's ManifestValidationResult is a
    breaking change to @objectstack/sdui-parser's public surface, bought for a
    naming difference triage itself graded as "naming only, no behaviour".
  • markHtmlTierNode / provenance.ts is symbol-keyed and invisible to JSON and
    to 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 — the
derivation 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 tests
passed. pnpm --filter @objectstack/sdui-parser typecheck — clean, and it
really covers the new tests: tsc --listFiles lists both the new test file and
the new module (count 1 each), so "typecheck green" is not the vacuous kind.

Declared narrowing. Repo-wide pnpm lint was not run; ESLint was run over
the four changed source files (--no-inline-config --format json): 4 files, 0
errors, 0 warnings. This is a measurement rather than a gap because this repo
runs one eslint.config.mjs which, in its own words, "never enables type-aware
linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY
file" — 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.mjs exits 3 ("no reading at all" — it
listed no issue), and check:type-check-debt --re-measure needs the whole
workspace closure built, which did not fit the container's foreground cap while
another agent held the build lock.

Changeset

Derived, not assumed. packages/sdui-parser declares no private field and
carries publishConfig.access: public, so it publishes; this change adds four
exported symbols and a new diagnostic code that validateTree and compile
emit, which consumers see. The git log receipt agrees: the immediately
preceding sibling port of this same card family (4a9f46179, #12857) carried
.changeset/sdui-parser-union-arm-type-mismatch.md. Added as minor, matching
it.

Findings filed

Three, all finding + a topic label, none assigned, none queued:


Generated by Claude Code

… 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
@github-actions

github-actionsBot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/sdui-parser, touching 12 documentable anchor(s). ⚠️1 changed file(s) yielded no anchor (packages/sdui-parser/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/error-catalog.mdx(via dateGranularity (literal))
  • content/docs/data-modeling/analytics.mdx(via dateGranularity (literal))
  • content/docs/data-modeling/queries.mdx(via dateGranularity (literal))
  • content/docs/protocol/objectql/query-syntax.mdx(via dateGranularity (literal))
  • content/docs/ui/dashboards.mdx(via dateGranularity (literal), sortBy (literal), sortOrder (literal), stageOrder (literal))

2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx(via dateGranularity (literal))
  • content/docs/releases/v17.mdx(via dateGranularity (literal), sortBy (literal), sortOrder (literal))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/sdui-parser/src/index.ts) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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 — 2 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 750fff55921e1e4a6906cbe30c22ec87cf94350bpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 6e0b430ed4de1621843a882836d615a93f61c3cd — the merge of head 02a0ab545dba9e9a6529ac595b7f183b2a5bd706 into base 750fff55921e1e4a6906cbe30c22ec87cf94350b, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 750fff55921e1e4a6906cbe30c22ec87cf94350b → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-elon
os-elon marked this pull request as ready for review August 28, 2026 09:41
@os-elon
os-elon enabled auto-merge August 28, 2026 09:41
@os-elon
os-elon added this pull request to the merge queueAug 28, 2026
Merged via the queue into main with commit 8beb3deAug 28, 2026
34 checks passed
@os-elon
os-elon deleted the claude/issue-12810-dashboard-widget-options-port branch August 28, 2026 10:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

2 participants

@os-elon@claude