Skip to content

fix(components): element:definition-list, element:repeater and action:button publish their spec-declared inputs; types retires ActionGroup (objectui#11168 slice 2) - #11313

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-11168-element-family-inputs
Oct 1, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-11168-element-family-inputs

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Refs #11168

Clause-②: yes (narrowing) — widening: action:button.size now publishes default and icon (the page validator refused both), and element:definition-list.items is no longer required; narrowing: element:definition-list.columns takes the NUMBERS 1 / 2 instead of the strings, element:repeater.filter / .sort declare of: 'object', and the @object-ui/types ActionGroup interface is removed. The fixed group ships it as minor (the version-alignment rule in AGENTS.md); the break is stated in both changesets.

Slice 2 of objectui#11168, under objectui#11111 decision 3 = B (record 5902351047): each key decided by its own measurement through the real SchemaRenderer, the real registry and the installed @objectstack/spec 17.5.0 rows. Declare what the renderer honours; refuse or retire what it does not. The card stays open for slice 3; this PR lands with Refs.

What each key measured, and what moved

All rendering rows mount the block the way a page does (real SchemaRenderer, real registry). "Validator" means validateTree over the manifest manifestFromConfigs builds from the whole registry, the JSX-page compiler's and the save gate's pair.

Key Measured at BASE 1ccb5ba7d Decision
element:definition-list.columns 2 draws sm:grid-cols-2; '2' draws grid-cols-1 (the renderer compares props.columns === 2). The spec refuses '1' / '2' (invalid_value). The published enum was the strings, so the validator raised error:invalid-enum on columns: 2, the value the designer writes, and passed '2'. Narrowed: the enum members are the numbers 1 and 2 ({ label, value } form). The validator now accepts 2 and reports '2'.
element:definition-list.items (required flag) Absent: the spec accepts it and the renderer shows "No details", but the validator raised error:missing-required-prop. required dropped, declared of: 'object'. In scope as the same block's input; see "Bounded in-place fix" below.
element:definition-list.items (members) term is the dt; description is shown as-is: string or number verbatim, object as JSON, omitted as an em dash. label / value items render a blank term and an em dash, and the spec refuses them (unrecognized_keys at items.0). Member pin registered.
element:repeater.fields A bare name and { field } each print that field's value, in order, after the title; a missing field prints an em dash. A { field, label } entry prints the value and never the label; the spec refuses it (invalid_union at fields.0). Member pin registered. The description no longer advertises label, and the internal RepeaterColumn type drops it. No of (member union), recorded in MULTI_KIND_MEMBER_CONTRACTS.
element:repeater.filter The rules reach adapter.find as $filter, member for member, with {current_user_id} / {current_org_id} in value resolved first; a token-free list arrives unchanged. The spec refuses the record form (invalid_type at filter). Member pin registered, of: 'object'. The lowering to the server AST is the adapter's own contract, pinned in @object-ui/data-objectstack.
element:repeater.sort The items reach $orderby unchanged and in order. The spec refuses a bare name as a member (invalid_type at sort.0). Member pin registered, of: 'object'.
action:button.size default, sm, lg and icon reach the Button primitive as-is; md draws the same classes as default, by mapping (schema.size === 'md' ? 'default' : …). The registration published sm / md / lg, so the validator raised error:invalid-enum on default and icon. The spec row declares all five. Widened: publishes default, sm, md, lg, icon, with a description saying md renders as default.
@object-ui/types ActionGroup Reachable from the package entry: the built dist/index.d.ts re-exports it from ./ui-action.js. Zero importers: git grep over this repository and over ../objectstack (the second has no @object-ui/types importer at all). Not the mirror of action:group. That is ActionGroupBlockSchema (spec row by reference). It disagreed with the row on six counts: required name (refused), required label and actions (optional), visible string-only, and no location / variant / size. The whole interface is retired, with a tombstone naming the replacement. That covers the booked name and the five other divergences of a published type with zero consumers.

Bounded in-place fix: element:definition-list.items required

The claim books columns and the items member pin on this block, and not the required flag. The dispatch asked whether that flag is in this slice. All four in-place conditions hold, so it is fixed here:

  1. Same defect class as this card: a published declaration disagreeing with the 17.5.0 row on a block this card owns.
  2. Mechanical: drop required: true. The spec docblock names the shape.
  3. The file is in this claim's surface (data-list.tsx).
  4. Same gate family; it is pinned in the same new pin file, and no new gate is added.

Evidence is in the table above, and ablation E below. ⚠️ The seat may want the claim's file surface to name this key.

Ledger arithmetic (registry-inputs-spec-parity.test.ts)

Re-derived by a static census of the edited file. A census run on BASE first reproduced BASE's own pins as the control.

BASE (control) this branch
unjudgedBlocks 4 4
offSpecInputs 0 0
unpublishedKeys 3 3
refusedArms 2 1 (object-form.layout stays: objectui#11117 holds that arm this round)
memberPins 4 0
owner objectui#11168 13 8
MEMBER_PIN_EXEMPTION_CEILING 4 0 (the list is empty)

objectui#8652 0 and objectui#8649 0 are untouched. The guard's own the objectui#11111 ledger is capped at exactly the entries it lists asserts the same figures at runtime.

Ablations

Predictions were written to a file before any run. Each leg went through objectstack scripts/ablation-replace.mjs: the anchor had to hit, and the blob change was verified. Each leg had an outer trap restoring from HEAD by absolute path. Vitest aliases @object-ui/* to src, so no dist/ sits between a mutation and its assertion. Every restore was proven: blob == HEAD blob, git diff HEAD empty, git status empty.

Leg Mutation Predicted Observed
A columns enum back to the strings 3 red: guard element:definition-list declares no arm the spec refuses outright + 2 pin rows exactly those 3, 247 passed (250); blob e54574ef408e to 25d2c302c708 and back
B delete the $orderby hand-off 2 red: pin sort row + data-list.sortKey-10664 SUBJECT exactly those 2 of 22
C $filter gets the raw authored filter 1 red: the context-token row exactly that 1 of 20; the token-free control stayed green
D size enum back to sm / md / lg 2 red in the button pin file; guard green exactly those 2, 283 passed (285)
E items required again 1 red: the optional-items row exactly that 1 of 20. ⚠️ First attempt was a no-op: the replacement contained the anchor, so the tool refused (anchor count 1 to 1) and restored without running. Re-anchored and re-run.
F restore ActionGroup (interface + entry re-export) tsc TS2578 on the 2 directive lines; vitest 2 red error TS2578: Unused '@ts-expect-error' directive on exactly the two @ts-expect-error directives of action-group-retired-11168.test.ts (tsc exit 2), vitest 2 of 2 red

Gates and tests (head 19a44d591)

Heavy runs went through objectstack os-verify-lock.sh. Exits were captured by redirect-then-$?. Each suite log's first line is 19a44d591.

  • Build: turbo run build --filter='@object-ui/components^...' 7 of 7; --filter='@object-ui/console^...' 34 of 34. @object-ui/types^... is empty (no workspace deps), so types was built itself.
  • Type-check (script name echoed in each log):
    • @object-ui/components (tsc --noEmit && tsc -p tsconfig.test.json) exit 0;
    • @object-ui/console (tsc --noEmit && tsc -b tsconfig.node.json --force) exit 0;
    • @object-ui/types (tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json) exit 0.
    • --listFilesOnly lists both component pin files, the types pin and the guard in their projects.
  • Suites:
    • packages/components 348 files in three batches: 155 passed; 96 passed and 1 skipped; 96 passed.
    • packages/types 302 passed (7604 tests).
    • apps/console 139 files in two batches: 70 passed, 69 passed.
    • Readers outside those packages (every test naming these blocks or ActionGroup, the sdui-parser suite, schema-catalog pageheader, react config-bag): 38 passed (991 tests).
    • Final union: guard + 3 pin files Test Files 4 passed (4), Tests 307 passed (307).
  • check:*, all exit 0:
    • changeset presence / no-major / fixed / overwrite / claims, and pending-changeset-literals;
    • control-bytes, new-line-citations (0 new), test-path-roots, action-forward-parity, handler-key-reads, component-surface-parity, element-data-source-declaration, spec-symbols;
    • readme-exports, unreferenced-sources, phantom-deps, self-import, esm-specifiers;
    • doc-snippets (698 of 698 block(s) judged, 0 failed), doc-types, doc-examples;
    • prompt-keys, installed-pin-claims, designer-field-key-parity, doc-example-readers, registry-bare-names, i18n-keys.
    • ⚠️ On the first run readme-exports exited 1 and doc-snippets / doc-examples exited 2: unbuilt cli / plugin-ai, a prerequisite and not a verdict. They were green after the gate's own scoped --build-filter build.
  • check-governed-queue-guard --test on the 10 changed paths: NOT GOVERNED.
  • Lint, declared narrowing: eslint (this repo's lint is eslint .) on the 8 touched TS files gave 0 errors and 28 warnings, 0 of them on an added line.
    • Population: --print-config returns a rule set for each file.
    • File count: 8, from --format json.
    • Invariance: eslint.config.js contains neither project nor parserOptions (controls: rules 13 hits, typescript-eslint 7), so it is not type-aware and this diff cannot move a verdict in an untouched file.
  • NOT MEASURED:
    • check:sdui-registration-pins: needs a console bundle, and the diff touches no sideEffects array.
    • check:spec-floors: the diff touches no package.json.
    • Repo-wide pnpm test / lint / type-check: CI's.

Docs

No page in content/docs/**, no package README and nothing under skills/** documents these keys or ActionGroup (census: git grep for definition-list, element:repeater and ActionGroup). The ButtonSchema size row in schema-reference.md is the button node, not action:button. These blocks' authoring surface is the manifest built from the registrations changed here, so no document is made false. The two changesets carry the release text.

Acceptance notes (noted, not filed)

  • @object-ui/types UIActionSchema.size is typed 'sm' | 'md' | 'lg'. Measured: tsc refuses size: 'icon' and size: 'default' (TS2322); the control lg is accepted. The action:button row declares all five. Same family as the ActionGroup booking (the types mirror of the 17.5.0 action rows). It was not fixed here because UIActionSchema is also the member type of the action:group / action:menu / action:bar lists, so widening it is not a mechanical, pinned shape. For the seat to book on this card or its family closer.
  • Eleven pending changesets name a file this diff touches (check:changeset-claims, report-only). Each was read; none states a fact this diff moves.

Implemented by an os-dev subagent of the domain:spec @ objectui seat, session https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT.


Generated by Claude Code

claude added 4 commits October 1, 2026 01:27
… what their renderer reads (objectui#11168 slice 2)

Each key decided by its own measurement through the real SchemaRenderer,
per objectui#11111 decision 3 = B:

- element:definition-list.columns: the enum members are the NUMBERS 1 and
  2. The renderer compares `props.columns === 2`, so the published strings
  '1' / '2' drew one column, the spec row refuses them, and the page
  validator refused the number the designer writes.
- element:definition-list.items: no longer required. Absent renders the
  "No details" state and the spec row does not require it; the validator
  raised missing-required-prop on a list both accept. Declared `of: object`.
- element:repeater.fields: the description stops advertising a `label`
  member, which is never printed and which the spec refuses.
- element:repeater.filter / sort: declared `of: object`, the only member
  kind the spec rows accept.

Member pins for items, fields, filter and sort, plus the columns and
items rows, live in element-list-inputs-11168.test.tsx. The guard strikes
the refused arm and the four member-pin bookings: refusedArms 2 -> 1,
memberPins 4 -> 0, MEMBER_PIN_EXEMPTION_CEILING 4 -> 0, owner count
objectui#11168 13 -> 8, and element:repeater.fields joins
MULTI_KIND_MEMBER_CONTRACTS.

Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT
Co-authored-by: Claude <noreply@anthropic.com>
…declares (objectui#11168 slice 2)

The registration published `sm` / `md` / `lg`; the 17.5.0 row declares
`default`, `sm`, `lg`, `icon` and `md`. Measured through the real
SchemaRenderer: `default`, `sm`, `lg` and `icon` reach the Button primitive
as-is, and `md` renders as `default` (the renderer's mapping). So the page
validator refused two sizes the renderer honours. The input now publishes
all five, with a description that says `md` renders as `default`.

Pinned in action-button-icon-inputs-11168.test.tsx: the published set
equals the spec's accepted set, each size draws the primitive's classes
for that size (md draws default's), and the validator accepts each and
reports one outside the set.

Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT
Co-authored-by: Claude <noreply@anthropic.com>
…1168 slice 2)

`ActionGroup` was re-exported from the @object-ui/types entry
(dist/index.d.ts) and nothing in this repository imported it. It was not
the mirror of the `action:group` block, which is ActionGroupBlockSchema
(the spec row by reference). It disagreed with that row on six counts:
it required `name` (the row refuses it; slice 1 retired it from the
block's inputs), it required `label` and `actions` (optional on the row),
it typed `visible` as a string only, and it lacked `location`, `variant`
and `size`. Retiring the whole interface rather than only `name`: an
exported type with zero consumers that mirrors nothing.

Breaking for an external TypeScript consumer that imports it. Released
as minor, per the fixed group's version-alignment rule (AGENTS.md).
Pinned retired by action-group-retired-11168.test.ts: @ts-expect-error
legs on the module and the entry, with lit controls.

Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT
Co-authored-by: Claude <noreply@anthropic.com>
…ActionGroup retirement

Two changesets, one per released package, both minor per the fixed
group's version-alignment rule: @object-ui/components for the
element:definition-list / element:repeater / action:button inputs, and
@object-ui/types for the removed ActionGroup interface.

Claude-Session: https://claude.ai/code/session_01VhxTqosz7wn54ahqyxgERT
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 11 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/6051-gantt-flat-config-declared-keys.md

  • names packages/types/src/index.ts → packages/types/src/index.ts — edited by this change

    GanttConfig itself gains nine members and is a published type, exported by name from packages/types/src/index.ts: lockField, objectField, summaryExtent, defaultCollapsedDepth, borderColorField, dependencyTypes, timeZone, exportFileName, interactions. The entry file's diff is empty only because the export list already named the type — the widening happened at the declaration.

.changeset/6306-action-icon-type-resolution.md

  • names action-button.tsx → packages/components/src/renderers/action/action-button.tsx — edited by this change

    Scope is this one renderer. type: schema appears in exactly two files under renderers/action/ — action-button.tsx (already correct) and action-icon.tsx; action:group and action:menu compose their members differently and are untouched.

.changeset/6349-types-internal-name-collisions-batch-1.md

  • names ui-action.ts → packages/types/src/ui-action.ts — edited by this change

    ActionSchema — renamed, because the two shapes are unrelated. crud.ts and ui-action.ts both declared it. Measured member-by-member they share 9 keys out of 28 each: crud.ts extends BaseSchema and pins type: 'action' (a UI node — a button in a component tree), ui-action.ts extends nothing and types type as ActionType (a spec-v2 action definition, with name, locations, params, target). Re-pointing either at the other would silently hand a consumer a different type, so this took the rename branch (objectui#5044 is the precedent for choosing the surviving name). ui-action.ts's declaration is now spelled UIActionSchema — the name src/index.ts has always PUBLISHED it under, via export type { ActionSchema as UIActionSchema }, which is now a plain re-export. The package's public surface is unchanged: ActionSchema still means crud.ts's legacy shape and UIActionSchema still means ui-action.ts's, exactly as before. Nothing outside ui-action.ts imported the old spelling — there is no ./ui-action subpath in exports, so the old name was never reachable from outside the package.

.changeset/6726-find-envelope-records-arms.md

  • names components/src/renderers/basic/data-list.tsx → packages/components/src/renderers/basic/data-list.tsx — edited by this change

    | module | what it does | | --- | --- | | components/src/hooks/related-count-store.ts | related-list tab badge count | | components/src/renderers/basic/data-list.tsx | element:repeater rows | | components/src/renderers/basic/elements.tsx | element:number client-side aggregate | | components/src/renderers/basic/record-picker.tsx | element:record_picker options | | plugin-detail/src/renderers/record-activity.tsx | record:activity self-fetch | | plugin-detail/src/renderers/record-history.tsx | record:history self-fetch | | plugin-view/src/ObjectView.tsx | non-grid (kanban / calendar / gallery / timeline) fetch |

.changeset/6783-readprops-degenerate-config-bag.md

  • names data-list.tsx → packages/components/src/renderers/basic/data-list.tsx — edited by this change

    Five modules under packages/components/src/renderers/basic/ — elements.tsx, data-list.tsx, text-input.tsx, record-picker.tsx, metadata-viewer.tsx — each carried a copy of the same reader, { ...(schema?.props ?? {}), ...(schema?.properties ?? {}) }. ?? only replaces null/undefined, so a non-object bag went into the object spread and came back out as indexed keys: for properties: 'not-a-bag', the config bag a renderer received was { '0': 'n', '1': 'o', … '8': 'g' } — nine keys nobody authored. The five copies are now one readProps (renderers/basic/readProps.ts) that asks isConfigBag, and a degenerate bag on either side contributes no keys.

.changeset/8067-component-input-member-kind.md

  • names apps/console/src/__tests__/registry-inputs-spec-parity.test.ts → apps/console/src/__tests__/registry-inputs-spec-parity.test.ts — edited by this change

    A registration's type: 'array' said a value was a list and stopped there, so a member that drifted from @objectstack/spec was invisible to every layer that reads a declaration. page:header.actions is the measured cost: the contract declares z.array(z.string()) ("Action IDs"), the renderer read the members as ActionDef objects, and the repo-wide parity gate in apps/console/src/__tests__/registry-inputs-spec-parity.test.ts stayed green for the whole life of the drift because both sides carried the key and neither could say what was inside it. What settled it was a maintainer ruling, not a test — and even after the fix, "these are ids" survived only as English in the registration's description.

.changeset/action-callback-retired-7068.md

  • names ui-action.ts → packages/types/src/ui-action.ts — edited by this change

    Where the live meaning lives. Post-success navigation is the spec's onSuccess block, { navigate, openIn }, declared on UIActionSchema (ui-action.ts) and forwarded to the runner (objectui#5934). A success or failure notice is successMessage / errorMessage — adjacent keys on the same legacy ActionSchema, NOT retired, and still accepted on both faces.

.changeset/component-meta-converge-and-deprecate-alias-5893.md

  • names packages/types/src/index.ts → packages/types/src/index.ts — edited by this change

    ⚠️ Dated note, 2026-09-29 — the one-occurrence reading above has since moved — objectui#10979. "In-repo, PluginComponentMeta has exactly one occurrence — its own export line" above is the reading of the commit this change landed on (575b71c850), where a whole-tree search for the name returned that one line, in packages/types/src/index.ts. This change then added its own pins and this entry, and later in this same release objectui#5674's retirement of the sibling alias PluginComponentInput (PR objectui#10826) added mentions in its test and its entry. Re-measured on main at 2eaf5be27, the name occurred on 19 lines in 7 files. In non-test source it appeared only on the export line and on one doc-comment line in plugin-scope.ts, and the one file that imported it was this change's own component-meta-single-declaration.test.ts. The text above is kept as the reading of this change.

.changeset/i18nlabel-inverted-pin-5612.md

  • names ui-action.ts → packages/types/src/ui-action.ts — edited by this change

    The I18nLabel "inverted pin" now watches the premise it claims to watch, and ui-action.ts no longer imports a symbol it never uses (objectui#5612, objectui#5613).

.changeset/param-dialog-title-fallback-4282.md

  • names ui-action.ts → packages/types/src/ui-action.ts — edited by this change

    title is declared on no action surface in the ecosystem: it is absent from @objectstack/spec's ActionSchema (44 keys walked at spec 17.0.0), from @object-ui/core's ActionDef and its pinned ACTION_DEF_KEYS / SPEC_ACTION_KEYS inventories, and from @object-ui/types' renderer view (ui-action.ts) and crud.ts ActionSchema / BaseSchema. None of the four action renderers — action:button, action:icon, action:group, action:menu — forwards it either. So the right-hand side of that || could not be reached by authored metadata: a fallback that cannot fire, which is the "declared is not enforced" shape objectstack#4075 exists to reduce. Nothing a user hits changes; the line now reads exactly one key, matching the description line directly below it.

.changeset/record-detail-param-dialog-title-5610.md

  • names ui-action.ts → packages/types/src/ui-action.ts — edited by this change

    title is declared on no action surface in the ecosystem: it is absent from @objectstack/spec's ActionSchema (44 keys walked at spec 17.0.0), from @object-ui/core's ActionDef and its pinned ACTION_DEF_KEYS / SPEC_ACTION_KEYS inventories, and from @object-ui/types' renderer view (ui-action.ts) and crud.ts ActionSchema / BaseSchema. None of the four action renderers — action:button, action:icon, action:group, action:menu — forwards it either. So the right-hand side of that || could not be reached by authored metadata: a fallback that cannot fire, which is the "declared is not enforced" shape objectstack#4075 exists to reduce. Nothing a user hits changes; the line now reads exactly one key, matching the description line directly below it.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with caa0cd392 (merge-base with origin/main): 8 file(s) changed outside .changeset/, read against 1878 pending declaration(s) that publish a body (2491 pending in total). · run

@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3585.8 KB 3607.4 KB
Main entry chunk (gzip) 150.4 KB 350 KB
Entry file index-BGupad2c.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 571.73KB 136.92KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 229.96KB 63.80KB
fields (index.js) 261.11KB 66.26KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.49KB 9.23KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 40.95KB 11.48KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 52.90KB 15.35KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 139.36KB 37.31KB
plugin-designer (index.js) 216.45KB 44.60KB
plugin-detail (index.js) 244.79KB 64.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 173.75KB 44.64KB
plugin-gantt (index.js) 173.03KB 43.07KB
plugin-grid (index.js) 231.61KB 63.61KB
plugin-kanban (index.js) 49.32KB 15.48KB
plugin-list (index.js) 116.63KB 28.97KB
plugin-map (index.js) 23.50KB 7.82KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 33.05KB 9.67KB
plugin-tree (index.js) 11.20KB 3.89KB
plugin-view (index.js) 90.32KB 22.76KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.55KB 39.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 21.59KB 7.71KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 19a44d5919fa8f1207e407f999968a3dfec2395e
Local-runs: none

Isolated at-tier contract reviewer, run inside the domain:spec @ objectui seat's session, written 2026-10-01T02:38Z. Inputs, and nothing else: card objectui#11168 (its body and all thirteen comments: the objectui#11111 decision 3 = B ruling 5902351047 it cites, the slice-1 records 5908412638 / 5910162281 as precedent, the bookings 5909046067 / 5911924944 / 5909523766, the slice-2 claim 5922735326, the dev's os-dev-report 5923487000 and the seat's ACCEPT 5923506054, the last two read as claims); PR #11313 (body, ten-file list, the net diff against main at the merge-base 1ccb5ba7d); the installed contract read at its tag (@objectstack/spec 17.5.0, objectstack 0f6dcac5e9, packages/spec/src/ui/component.zod.ts: ElementDefinitionListPropsSchema, ElementRepeaterPropsSchema, ActionButtonPropsSchema, ActionGroupPropsSchema, BUTTON_PRIMITIVE_SIZES); objectui AGENTS.md at origin/main (版本号策略) and .changeset/config.json; the renderer, guard, validator and barrel sources at the head, read from git objects; and the check-runs on the head. Nothing built, run or re-run; the dev's ablations are read as claims, not repeated. The brief was adversarial by design: the dispatch order and the dispatching seat's conclusions were not inputs.

① Derived judgments

The published registration inputs of element:definition-list, element:repeater and action:button, and the @object-ui/types package entry, are the surface under judgment. For each item the contract half is re-derived from the spec source at the tag and the renderer half from the renderer source at the head; the pin files are read for consistency, and their runtime verdict is CI's (the check-run line in ③).

  1. element:definition-list.columns: narrowed, strings to numbers. Registration: enum: ['1', '2'] becomes [{ label: '1', value: 1 }, { label: '2', value: 2 }]. Spec row: columns: z.literal([1, 2]), with an error text that names the string spelling as the likeliest mistake. Renderer (data-list.tsx at the head): props.columns === 2 ? 'sm:grid-cols-2' : 'grid-cols-1', so the string the old enum published drew one column. Validator (sdui-parser/src/validate.ts): enumValues flattens the { value } form and includes compares by identity, so 2 now passes and '2' reports invalid-enum, the inverse of before; the object form is the declared ManifestInput.enum shape (string or { value, label? }), and codegen.ts flattens it the same way for the JSX intrinsics. The repository's own pages already write the number (apps/console/src/dev/DevLists.tsx and DevModal.tsx: columns: 2), and no document under content/, skills/, examples/, apps/ or a package src/ writes the string as a value (the two hits are prose in packages/types/src/record-components.ts). Right.

  2. element:definition-list.items: required dropped, of: 'object' declared, member pin registered. Spec row: items: z.array(strictObject({ term: z.string(), description: z.unknown().optional() })).optional(), with label / value as refused aliases. Renderer: Array.isArray(props.items) ? props.items : [], and empty renders "No details"; per item it.term is the dt and toText(it.description) the dd (string or number verbatim, object as JSON, nullish as an em dash). The required: true the registration carried made the page validator report missing-required-prop on a list the contract and the renderer both accept; dropping it is the declare-what-is-honoured arm on a key already in the claim's block and file. of: 'object' matches the row's member kind; its validator effect is one member-type-mismatch warning on a non-object member (no enum arm, so warning). The pin's { term, description } statement is what the renderer reads. Right, the in-place required fix included.

  3. element:repeater.fields: the description stops advertising label; no of; a MULTI_KIND_MEMBER_CONTRACTS entry. Spec row: z.array(z.union([z.string(), strictObject({ field: z.string() })])), with guidance refusing label by name. Renderer: cols maps each fields entry, a string to { field } and an object as-is, then prints row[c.field] only; there is no header row, so a label was never rendered. The module-private RepeaterColumn interface loses label? (not exported, so no public type moves). No of, because the member contract is a union, the same reason record:highlights.fields sits in that map. Right.

  4. element:repeater.filter / .sort: of: 'object', member pins at the adapter seam. Spec rows: z.array(ViewFilterRuleSchema) and z.array(SortItemSchema). Renderer: useResolvedFilter(props.filter, filterScope) becomes query.$filter (context tokens resolved first), props.sort becomes query.$orderby unchanged, both on adapter.find(props.object, query). The renderer reads no member itself; the pins assert what the adapter is handed on every query, which is the honest reach of a block that delegates the lowering, and the slice-1 of: 'object' precedent on the two actions lists applies. Right.

  5. action:button.size: widened to the five the row declares. Registration: ['sm', 'md', 'lg'] becomes ['default', 'sm', 'md', 'lg', 'icon'], with a description stating md renders as default. Spec row: z.enum([...BUTTON_PRIMITIVE_SIZES, 'md']) with BUTTON_PRIMITIVE_SIZES = ['default', 'sm', 'lg', 'icon'], the same five. Renderer: schema.size === 'md' ? 'default' : (schema.size || 'default'), handed to the Button primitive, whose cva size variants are exactly default / sm / lg / icon. Every published value is honoured, four as-is and md by the pre-existing mapping the row's own describe names; not a declared-but-dropped input. Closes booking 5909046067. Right.

  6. @object-ui/types ActionGroup: the interface removed from ui-action.ts and from the entry's export type { … } block, with a tombstone and a pin. Reachability: the deleted index.ts line is the proof it sat on the package entry. Consumers: git grep -w ActionGroup at the head finds no import of the type anywhere in objectui (the one prose hit, in plugin-detail's record-alert.tsx, names the React component, not the type); no package barrel re-exports * from '@object-ui/types', so the symbol reached no second entry; /home/user/objectstack has no @object-ui/types importer at all. The six divergences hold against ActionGroupPropsSchema: name required where the row refuses it; label and actions required where the row has both .optional(); visible?: string where the row takes actionCondition() (boolean, CEL string or envelope); no location / variant / size where the row has all three. The replacements the tombstone and the changeset name exist: ActionGroupBlockSchema in packages/types/src/zod/public-blocks.zod.ts, under the package's ./zod export, and ActionGroupProps exported by the spec file above. Retiring the whole type rather than only name is the "refuse or retire what it does not honour" arm applied to a published type with zero consumers; a name-less ActionGroup would have kept four other disagreements in the shipped .d.ts. Right. Residual, stated by the pin file itself: an external TypeScript consumer is not observable from here; it gets a compile error naming the symbol, and the changeset names the replacement.

  7. Guard ledger at the head, re-derived from the file. UNJUDGED_SPEC_BLOCKS holds 4 entries owed to objectui#11168 (object-gantt, object-map, object-timeline, object-tree); OFF_SPEC_EXEMPTIONS 0; UNPUBLISHED_EXEMPTIONS 3 (action:button endpoint and undoable, action:icon endpoint); OFF_SPEC_ARM_EXEMPTIONS 1 (object-form.layout:enum; the element:definition-list.columns:enum entry is deleted); MEMBER_PIN_EXEMPTIONS is the empty object (both owedEntries spreads deleted). Caps 4 / 0 / 3 / 1 / 0, as OBJECTUI_11111_LEDGER_CAPS now reads; owner objectui#11168 4 + 0 + 3 + 1 + 0 = 8, as the per-owner pin reads; objectui#8652 and objectui#8649 stay 0; MEMBER_PIN_EXEMPTION_CEILING 0 with nothing left in the list. The four new MEMBER_PINS entries point at packages/components/src/renderers/basic/__tests__/element-list-inputs-11168.test.tsx, which the guard's import.meta.glob (packages/*/src/**/*.test.tsx) reaches and which names both block types and each key, the three things memberPinProblem checks. The PR body's 2 to 1, 4 to 0, 4 to 0 and 13 to 8 are the figures the file carries. Right.

  8. Pin tests, read for consistency. Every positive row carries a control (the sibling element:repeater.object still reported missing; the token-free filter list; absent sort; four distinguishable size class lists; a lit sibling type beside each @ts-expect-error leg). Spec refusals are asserted by { code, path }, not a bare success: false. The action:button rows run through the manifest built from the whole registry, the pair the save gate uses. Nothing in them is a public-surface change.

Nothing else in the ten files moves an accept set or a public surface.

② Semver level

The diff publishes a widening (two size values and an items no longer required, accepted by the manifest, the intrinsics and the page validator) and a narrowing (the columns value domain, object-only members on items / filter / sort, and a removed published type). The governing arm is the narrowing. The PR body's line reads Clause-②: yes (narrowing) followed by both arms spelled out, and the fleet's one reader (clause2-line.mjs readClause2Line) reads it as value yes, arm narrowing. Line right.

Two changesets, @object-ui/components: minor and @object-ui/types: minor. Both packages sit in the single fixed group of .changeset/config.json (40 packages), and AGENTS.md 版本号策略 forbids major in a changeset and ships objectui's own breaking change as minor with the semantics stated in the body. Both bodies state it: "Narrowed (breaking for an author who wrote them, which the spec already refuses)" and "Breaking (types only): the ActionGroup interface is removed". Not skip-changeset: released src/ of two fixed-group packages moves. Changeset Bump Policy, Changeset Fixed Group Check, Changeset Declaration, Changeset Claim Re-read and Changeset Overwrite Report are success on the head. Level right.

Each sentence, since both publish verbatim into the CHANGELOG. Components: the columns paragraph matches the renderer comparison and the validator's before and after; "lists of objects … the only member kind the spec accepts" matches the two array-of-object rows; the items paragraph matches the optional row, the renderer's empty state and the old missing-required-prop; the size paragraph matches the five-value row, the primitive's four and the md mapping; the fields paragraph matches the union row and the header-less list; "Nothing changes at render time for a value the spec accepts" holds, since the diff touches registrations, a module-private TS interface, tests and changesets, and no render path. Types: "Nothing in this repository imported it" is the census in ①.6; "not the type of the action:group block" is right (ActionGroupBlockSchema is); the six counts are right; both replacement paths resolve; the closing version-alignment sentence states the rule it cites. One wording note, non-blocking: the components opening sentence says the blocks "publish their inputs as the … rows declare them", while element:repeater.fields deliberately publishes no of for its union row; the fields paragraph two lines down says exactly that, so the reader is not misled.

③ Boundary flags

The dev report's open_questions is empty; its six deviations and one out_of_scope_findings entry, the PR body's own flag, and the seat's bookings are each answered or escalated:

  • items.required fixed in place (deviation 1; the PR body's "the seat may want the claim's file surface to name this key"). Answered: same block, same claim file (data-list.tsx), same pin file and gate family; the row is optional and the renderer reads it absent. The seat's ACCEPT amends the file surface. Right.
  • of: 'object' beyond "register member pins" (deviation 2). Answered: slice-1 precedent (records 5908412638 / 5910162281, item 3), the rows' member kind is an object, and the arm is on the Clause line. Right.
  • Whole-interface ActionGroup retirement, not name only (deviation 3; booking 5911924944 asked to measure entry reach, then retire name on the mirror). Answered in ①.6: the measurement found zero consumers and five further divergences, and both halves of the booking (reach, then retire) are done. Right.
  • Two changesets (deviation 4): one per released package in the fixed group; the changeset gates are green. Right. Trailers, and ablation E's re-anchoring (deviations 5 and 6): process notes; the body, the changesets and the code comments carry no model identifier. Noted.
  • UIActionSchema.size typed 'sm' | 'md' | 'lg' (the one out_of_scope_findings entry; verified at the head in ui-action.ts). The same class as the ActionGroup finding: a published types mirror narrower than the 17.5.0 row, so an author typed by it still cannot write size: 'icon' on an action:button that now accepts it. Not fixed here for the stated reason (it is the member type of three lists). Escalated: the seat's ACCEPT books it on this card's remaining slices (item 1). Right.
  • The seat's bookings 2 to 6 (the stale calendar pin prose in the guard file, endpoint upstream as objectstack#21005, undoable awaiting a ruling, object-form.layout after objectui#11117, the four lazy blocks and the ObjectTree pointer 5909523766): all outside this diff, and nothing in it depends on them. The one OFF_SPEC_ARM_EXEMPTIONS entry left is object-form.layout, booked exactly as the claim excluded it.
  • Hot file and base: the PR reads mergeable: true, mergeable_state: behind; the guard file passes to objectui#8654 after this lands, per the ACCEPT. Draft by design until this record; the seat readies and enqueues after it.
  • Check-runs on the head, read twice. At 2026-10-01T02:32Z: 42 runs, 30 success, 3 skipped, 0 failure, 9 in_progress (Test (shard 1/8) through Test (shard 8/8) and Spec Main Shape Gate). At 2026-10-01T02:37Z, before this record was posted: 43 runs, 40 success, 3 skipped by their own conditions (dependabot, Test (coverage), Test (coverage shard …/4)), 0 failure, 0 in_progress; the nine finished success, and the Test rollup joined them. The eight shards carry the guard's and the three pin files' runtime verdicts, so the contract-side halves above ("the row accepts this", "the ledger is capped at exactly its entries") are the gates' verdicts as well as this reading of the source. Every changeset gate, Lint, Type Check, Test (dist pins), Build & E2E, Governed Surface Queue Guard and Line Citation Gate are success.

Implemented-by: claude/issue-11168-element-family-inputs
Reviewed-by: session_01VhxTqosz7wn54ahqyxgERT

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 1, 2026 02:39
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 1, 2026 02:39
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 1, 2026
Merged via the queue into main with commit cd5b19a Oct 1, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11168-element-family-inputs branch October 1, 2026 02:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants