Skip to content

feat(sdui-parser): port objectui's one base-prop list into the save gate - #20799

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20578-sdui-base-props-port
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20578-sdui-base-props-port

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #20578

Clause-②: yes (widening)

What

Ports objectui's one base-prop list into the save gate, at the console pin db11afd4967c (objectui#11008 for bind/hidden, objectui#11044 for the single list). objectui anchors: packages/sdui-parser/src/validate.ts:137 (SDUI_BASE_PROPS, docblock :60-136, skip :213) and packages/sdui-parser/src/codegen.ts:13-32, :95-110.

  • validate.ts: BASE_PROPS is now the every-node members of a 20-entry list: the 9 existing plus bind, hidden, visibleWhen, hiddenOn, testId. Six where-undeclared members (name, label, description, placeholder, data, ariaLabel) skip unknown-prop only when the type declares no input of that name; a declared one keeps its type-mismatch check.
  • codegen.ts: generateDts emitted a hand-kept export interface SduiBaseProps (public through index.ts). It is now emitted from the same list, and a component that declares a base attribute Omits it from the base it extends. This is the second consumer of the list, so it is exported from validate.ts for codegen.ts and is not re-exported from index.ts.
  • Correction: the first revision of this PR (and its report) claimed codegen.ts emits no base-props interface. That was false; the second commit fixes the code and the validate.ts LOCKSTEP paragraph.
  • Existing test html-tier-manifest.test.ts now expects AProps extends Omit<SduiBaseProps, "className">, the same string objectui pins in html-tier-manifest-10735.test.ts:114.

Clause-② measurement

Scanned every component's inputs in the tracked sdui.manifest.json (107 components).

  • Newly every-node: bind, hidden, visibleWhen, hiddenOn, testId are declared by 0 components each, so no declared type check is removed.
  • where-undeclared: name 3, label 10, description 6, data 7, placeholder 0, ariaLabel 0 declaring components. The skip is guarded by !byName.has(key), so a declared key takes the same path and severity as before.
  • Validator: for this manifest, undeclared uses lose a unknown-prop warning; no error-severity validateTree diagnostic is removed.
  • Generated types: SduiBaseProps goes from 8 members to 19, so a .tsx page authoring bind, hidden, visibleWhen, hiddenOn, testId, name, label, description, placeholder, data or ariaLabel on a tag that does not declare it was a TypeScript error against generateDts output before and compiles now. That widens the accepted authoring surface and the published type output, hence yes (widening) and a minor changeset.

Tests

  • New base-props-one-list.test.ts: each of the 13 attribute every-node members draws no unknown-prop; the list is pinned entry by entry (20 entries, type alone with no attribute type); each of the 6 where-undeclared keys draws nothing when undeclared; a declared label given a number still draws type-mismatch; a non-base key is still unknown-prop; the emitted SduiBaseProps equals the list's attribute entries in order; a component with no declared base attribute extends the whole base and one declaring label extends Omit<SduiBaseProps, "label">. type is not tested through validateTree: on a node it is the tag itself, so any such case passes through unknown-component.
  • pnpm --filter @objectstack/sdui-parser exec vitest run: 13 files, 224 tests passed; typecheck exit 0 (os-verify-lock, VERDICT command-exit 0).
  • No generated .d.ts or golden file for this output exists in the repo outside packages/sdui-parser/ (grep for SduiBaseProps and generateDts).

Gates (head 5cb34e77dc)

dispatch-gates --commands derived 61 families; 56 ran and exited 0, including check:sdui-lockstep, check:published-files, check:query-options-erasure, check:type-check-coverage, check-changeset-no-major, check:doc-authoring, check:nul-bytes. NOT MEASURED, exit 3 PREREQUISITE NOT MET (need a built dist/, left to CI): check:dts-closure, check:dual-build-cjs-loads, check:lean-entry-closure, check:sourcemap-no-sources-content, check:type-check-debt. The earlier body's count of 52 was wrong.

Acceptance notes

None outside the card.


🤖 Generated with Claude Code

https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 30, 2026
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/sdui-parser, touching 22 documentable anchor(s).

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

  • content/docs/ai/skills-reference.mdx (via visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/api/error-handling-client.mdx (via className (symbol, a field of interface SduiBaseProps), className (literal, a string literal in BASE_PROPS; a string literal in SDUI_BASE_PROPS))
  • content/docs/automation/flows.mdx (via visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/data-modeling/field-types.mdx (via visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/data-modeling/fields.mdx (via visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/data-modeling/formulas.mdx (via visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/data-modeling/schema-design.mdx (via visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/data-modeling/validation-rules.mdx (via visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/deployment/environment-variables.mdx (via visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/protocol/kernel/i18n-standard.mdx (via ariaLabel (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/protocol/objectui/concept.mdx (via visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/protocol/objectui/layout-dsl.mdx (via visibleOn (symbol, a field of interface SduiBaseProps), visibleOn (literal, a string literal in BASE_PROPS; a string literal in SDUI_BASE_PROPS), visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/protocol/objectui/widget-contract.mdx (via className (symbol, a field of interface SduiBaseProps), ariaLabel (literal, a string literal in SDUI_BASE_PROPS), className (literal, a string literal in BASE_PROPS; a string literal in SDUI_BASE_PROPS))
  • content/docs/ui/pages.mdx (via className (symbol, a field of interface SduiBaseProps), className (literal, a string literal in BASE_PROPS; a string literal in SDUI_BASE_PROPS), visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/ui/views.mdx (via visibleOn (symbol, a field of interface SduiBaseProps), visibleOn (literal, a string literal in BASE_PROPS; a string literal in SDUI_BASE_PROPS), visibleWhen (literal, a string literal in SDUI_BASE_PROPS))

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

  • content/docs/releases/implementation-status.mdx (via visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/releases/v12.mdx (via visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/releases/v15.mdx (via visibleOn (symbol, a field of interface SduiBaseProps), visibleOn (literal, a string literal in BASE_PROPS; a string literal in SDUI_BASE_PROPS), visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/releases/v16.mdx (via visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/releases/v17/17-0.mdx (via visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/releases/v17/17-2.mdx (via visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/releases/v17/17-4.mdx (via visibleWhen (literal, a string literal in SDUI_BASE_PROPS))
  • content/docs/releases/v17/17-5.mdx (via visibleWhen (literal, a string literal in SDUI_BASE_PROPS))

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
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

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 96e724475c476d018c2b6d13dcd117ade14d0aa0 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 4519e4ce745df42d23e801734bc34cd2189a0f45 — the merge of head 5cb34e77dc619f0bbed84983d96e127652f73136 into base 96e724475c476d018c2b6d13dcd117ade14d0aa0, 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 4519e4ce745df42d23e801734bc34cd2189a0f45 && git checkout 4519e4ce745df42d23e801734bc34cd2189a0f45
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 96e724475c476d018c2b6d13dcd117ade14d0aa0 5cb34e77dc619f0bbed84983d96e127652f73136 && git checkout -B drift-repro 96e724475c476d018c2b6d13dcd117ade14d0aa0 && git merge --no-ff 5cb34e77dc619f0bbed84983d96e127652f73136

node scripts/docs-audit/affected-docs.mjs --json 96e724475c476d018c2b6d13dcd117ade14d0aa0

⚠️ 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 96e724475c476d018c2b6d13dcd117ade14d0aa0 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 2ce8ceafbb22226656d94e6e0ecdd8e25c29301e
Local-runs: probe — a node one-liner over sdui.manifest.json at origin/main (107 components) to count the declaring components of the 11 keys; objectui cloned at db11afd4967c and its SDUI_BASE_PROPS region diffed byte-for-byte against the branch. No repo gate family rerun.

① Derived judgments

  1. Ported list byte-faithful — RIGHT. Branch validate.ts:144-169 vs objectui validate.ts:137 at db11afd4967c: the only differing line is the dropped export. All 20 names, both scopes and every tsType match.
  2. Every-node accept-set change — RIGHT. validate.ts:216 skips bind, hidden, visibleWhen, hiddenOn, testId; 0 of 107 manifest components declare any of them.
  3. Where-undeclared guard — RIGHT. validate.ts:220 (!byName.has(key)): a declared key falls through to the pre-PR byName.get(key) path (:240) with the same diagnostics and severities; missing-required-prop (:203-211) is untouched.
  4. Public surface — WRONG by omission. codegen.ts:41-50 (unchanged) emits a hand-kept SduiBaseProps via the public generateDts (index.ts:24). After this PR the validator accepts the five new keys while the generated types still refuse them, which is the drift objectui#11044 fixed (objectui codegen.ts:13-32, :95-110). The branch docblock validate.ts:133-138 states "objectstack's codegen.ts emits no base-props interface": FALSE.
  5. Changeset — IMPRECISE/FALSE in parts: "where the protocol declares them" is unresolvable for a consumer and wrong for the six; "newly accepted as an error" is meaningless; "served manifest" is unnamed, and the no-error-lost claim is manifest-relative (a consumer manifest declaring one of the five with an enum arm would lose an error); "module-local here" and "codegen.ts is unchanged" are internal notes, and the second hides the item-4 fact.
  6. Tests — mostly prove the claims; the type case (base-props-one-list.test.ts:42) passes vacuously via unknown-component. Nothing pins generateDts to the list.

② Semver level

Measured on the tracked sdui.manifest.json (107 components): only unknown-prop warnings disappear, and compile().ok is unchanged. patch with Clause-②: no is consistent for that manifest; the changeset must scope the claim to it.

③ Boundary flags

  • Vanished worktree, redone: no bearing; the one-commit head was verified directly.
  • Gate count 52 vs 54 in the PR body: no bearing on the contract.
  • Two report comments against a budget of one: a process breach. The first report's false "codegen.ts has no base-props list" became the premise of the narrowed scope, and that premise is shipped in the docblock.

Implemented-by: claude/issue-20578-sdui-base-props-port
Reviewed-by: session_01VDtqoecgES7ScQYGbFVDRv

VERDICT: FAIL

Required fixes:

  1. validate.ts:133-138: correct the LOCKSTEP paragraph.
  2. Resolve the validator/codegen drift: emit SduiBaseProps from SDUI_BASE_PROPS with Omit for declared base attributes (objectui codegen.ts:13-32, :95-110), with a test pinning the emitted interface to the list.
  3. Changeset: state the precise rule; "newly accepted"; name the measured manifest and scope the claim; drop the internal notes, keeping "no new export".
  4. Test :42: give the type case a real subject or drop it.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 5cb34e77dc619f0bbed84983d96e127652f73136
Local-runs: probe — objectui cloned at db11afd4967c and its codegen.ts, validate.ts and html-tier-manifest-10735.test.ts diffed byte-for-byte against the branch files; one node one-liner over sdui.manifest.json at origin/main (107 components) listing every declared input named as one of the 19 attribute members. No repo gate family rerun.

① Derived judgments

  1. Prior FAIL fix 1 (LOCKSTEP paragraph): done, and true. validate.ts:133-138 now names both consumers and the export-for-codegen-only. The list and Sets at :139-178 and the skip at :216-220 are byte-equal to objectui validate.ts:137-173 / :213 at the pin.
  2. Prior FAIL fix 2 (codegen): done, and byte-faithful. codegen.ts:22-25, :32, :61 and :79-109 are equal to objectui codegen.ts:13-32 / :80-110. The pin test is at base-props-one-list.test.ts:65-83. The html-tier-manifest.test.ts:127 expectation is character-equal to objectui html-tier-manifest-10735.test.ts:114.
  3. Prior FAIL fix 3 (changeset): done; every sentence is true. The no-error-lost claim is scoped to the named manifest and matches the scan: 0 declarers of the five; name 3, label 10, description 6, data 7, and each is still routed to byName.get (validate.ts:240). Two non-blocking imprecisions: the Omit sentence applies to every declared base attribute (a large regenerated-d.ts textual diff, type-equivalent), and "no new export" is not stated.
  4. Prior FAIL fix 4 (vacuous type test): done. It is removed, with the reason at :33-35, and type is pinned through the list at :54-56.
  5. Other surface: SDUI_BASE_PROPS, SduiBaseProp and SduiBasePropScope are module exports, unreachable from the package (index.ts:11; package.json:8-14 maps only .). Accepted. generateDts's SduiBaseProps goes from 8 to 19 members, with Omit for declared base attributes. Accepted as the port. check:sdui-lockstep still does not compare the list; that is the card's second question for the owning lane, not blocking.

② Semver level

Clause-②: yes (widening) with minor is consistent with AGENTS.md :1084-1085. The narrowing search, member by member, found no narrowed member: every declared pre-existing base attribute is equal or wider (record:alert visible), children is slot-only and untouched, and the eleven added members were never in the base before. An additional widening: the generated d.ts over the tracked manifest (RecordAlertProps, TS2430) now type-checks.

③ Boundary flags

  • The dev did not read the prior record directly: no bearing; this head was verified against objectui directly.
  • Two card comments, and the card's done report describes head 2ce8ceafbb, now stale: a process gap; the PR body carries the correct state. No bearing on the verdict.
  • The PR body was edited once and now describes the head accurately: no bearing.

Implemented-by: claude/issue-20578-sdui-base-props-port
Reviewed-by: session_01VDtqoecgES7ScQYGbFVDRv

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 07:41
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 5bed1f6 Sep 30, 2026
43 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20578-sdui-base-props-port branch September 30, 2026 08:01
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

1 participant