Skip to content

feat(components): the four action:* blocks publish the spec keys their block path honours (objectui#11168 slice 1) - #11185

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-11168-action-family-inputs
Sep 30, 2026
Merged

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

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Refs #11168
Clause-②: yes (narrowing) — breaking for an author who wrote the retired action:group.name, an object at action:group.actions or action:menu.actions, or md at action:group.size: each is now refused by the manifest, the intrinsics and the page validator, as the spec already refuses it. The widening beside it is the spec keys the four blocks now publish as inputs. objectui's fixed group ships the break as minor, stated in the changeset. The PR waits as a draft for the director seat's contract review.

Slice 1 of objectui#11168: the four action:* blocks. Every guard row this card owns on these blocks was measured through the real SchemaRenderer, the renderer's own forward and the real ActionRunner, against the installed @objectstack/spec 17.5.0 rows, and decided under objectui#11111 decision 3 = B (record 5902351047): declare what the block path honours, refuse or retire what it does not. The PR stays a draft for the director seat's contract review.

What moved

  • Declared (published as inputs): on action:button and action:icon, visible, disabled, params, description, openIn, method, bodyExtra, bodyShape, operation, patch, confirmText, successMessage, errorMessage, refreshAfter, locations, toast, resultDialog, onSuccess, objectName, plus recordIdField on action:button; on action:group, location and visible; on action:menu, size and visible.
  • Retired: action:group.name.
  • Narrowed (refused arms): action:group.actions and action:menu.actions from the object kind to a list of objects (type: 'array', of: 'object'); action:group.size from sm/md/lg to default/sm/lg/icon.
  • Member pins: action:group.actions, action:menu.actions, and the ten array/object-armed keys each leaf block now publishes.
  • Held unpublished, still booked to this card with the measurement below: endpoint on action:button and action:icon, undoable on action:button. The renderers keep forwarding both, so nothing changes at runtime.
  • public-block-binding-reach.test.tsx derives its population from public blocks that publish objectName. The two action leaves now do, and on them the key names the object an action is dispatched against, not a data binding. Both are listed and ledgered in NO_DATA_REACH with that reason, which still asserts that the block fetches nothing under the name.
  • One changeset (@object-ui/components: minor) and one paragraph in content/docs/guide/layout.md.

Per-key verdicts

Read sites are cited by symbol and quoted expression. "Pin" names the row in packages/components/src/renderers/action/__tests__/action-button-icon-inputs-11168.test.tsx (leaf blocks) or action-group-menu-inputs-11168.test.tsx (containers). Every pin row carries a control in which the key is absent or holds the other value.

action:button and action:icon (one row per key, both blocks unless noted)

Key Read site on the block path Verdict Evidence
visible ActionButtonRenderer / ActionIconRenderer: useCondition(toPredicateInput(schema.visible), recordData …) then hasDeclaredVisibilityGate(schema.visible) && !isVisible; and the SchemaRenderer node gate evaluateVisibilityPredicate(newSchema.visible, 'visible') declared, boolean/string/object holds/fails pair for a boolean, a bare CEL string and the envelope
disabled useCondition(toPredicateInput(schema.disabled), recordData) feeding disabled={hostDisabled || (hasDeclaredVisibilityGate(schema.disabled) ? … )} declared, boolean/string/object holds/fails pair per arm, control on screen in both rows
params forward Array.isArray(schema.params) ? { actionParams: schema.params … }; ActionRunner.execute hands the list to this.paramCollectionHandler(paramDefs, action) declared, array (the list to collect; static values stay on properties.params, and a node-level object is ignored under objectui#10289 ruling A) the collection handler gets the list whole and in order; a collected value reaches the executor under its member name
description forward description: (schema as any).description; the console paramCollectionHandler reads action?.description; on action:icon also the tooltip schema.label || schema.description declared, string the collection handler's action carries it; control undefined
openIn executeUrl: const openIn = source.openIn ?? action.openIn declared, enum self/new-tab new-tab gives newTab: true; control navigates in place
endpoint executeAPI: action.api || action.endpoint || action.target; the console apiHandler reads const target = action.target || action.name and never endpoint held probe: on a runner with no api handler the request goes to the endpoint; the census below finds no console read
method executeAPI: method = action.method || 'POST'; console (action.method || 'POST').toUpperCase() declared, string request verb PUT; control POST
bodyExtra buildApiRequestBody: const bodyExtra = asRecord(action.bodyExtra); console Object.assign(body, …) declared, object merged last over a static value of the same name; control without it
bodyShape buildApiRequestBody: const shape = action.bodyShape, then { [wrap]: base ?? {}, ...bodyExtra } declared, enum (flat) or object { wrap: 'data' } nests; flat and absent send the same flat body
operation isUpdateOperationAction: action.operation === 'update', then executeUpdateOperation declared, enum update patch reaches the script dispatch; control without operation merges nothing
patch executeUpdateOperation: const params = { ...patch, ...collected } declared, object merged under supplied values; a supplied value of the same name wins
confirmText ActionRunner.execute: if (action.confirmText) then the confirm handler declared, string asked first, a refusal stops the action; control asks nothing
successMessage handlePostExecution: dyn || action.successMessage || … declared, string success toast text; control is the runner's default
errorMessage handlePostExecution: action.errorMessage || result.error declared, string replaces the raw error; control shows the raw error
refreshAfter handlePostExecution: if (action.refreshAfter && result.success) result.reload = true declared, boolean the provider's result carries reload: true; control falsy
undoable (button) executeUpdateOperation: if (action.undoable && rowRecord && writtenFields.length > 0); console runtime action.undoable && obj && recId && rowRecord; RecordDetailView apiHandler action.undoable && isThisRecord && pageRecord held probe: the def the block forwards gets undo: null; the same def plus a host _rowRecord stash gets an undo
recordIdField (button) resolveServerActionRecordId: recordId = selected[0]?.[recordIdField] declared, string with one selected row the POST carries its code; control sends id
locations resolveServerActionRecordId and the console flowHandler: isRecordScopedAction(action) declared, array of string a record-scoped list refuses to run with no record in scope (error toast, no request); control list_toolbar runs
toast handlePostExecution: const showToast = action.toast ?? { … } and action.toast?.duration declared, object showOnSuccess: false, showOnError: false and duration each asserted on the toast handler
resultDialog handlePostExecution: this.resultDialogHandler(action.resultDialog!, result.data, action) declared, object the dialog handler gets the spec whole with the data, and no success toast
onSuccess handlePostExecution: readOnSuccessNavigation(action.onSuccess), then navigateOnSuccess declared, object navigate interpolated from the result, openIn: newTab opens a tab; control navigates nowhere
objectName createServerActionHandler: action.objectName || resolveObject?.(action, context) || 'global'; console handlers action.objectName || objApiName declared, string the route becomes /api/v1/actions/contact/…; control uses the page object

undoable and recordIdField are not on action:icon's spec row and its renderer does not forward them; the delivery probe shows undefined for both there, which is the probe's own lit zero.

action:group

Key Read site Verdict Evidence
location ActionGroupRenderer: declaredActions.filter(a => actionRendersAt(a, schema.location)) declared, enum (ACTION_LOCATIONS) only the matching member renders; control renders both
visible useCondition(toPredicateInput(schema.visible), recordData) and if (schema.visible && !isVisible) return null; the SchemaRenderer node gate declared, boolean/string/object holds/fails pair per arm
name none: schema.name is never read; inline mode only spreads leftover props onto the wrapping div element retired spec refuses it by name (unrecognized_keys naming name); the same members render identically with and without it, in both display modes
actions (arm) const declaredActions: UIActionSchema[] = schema.actions || [], then .filter and .map narrowed: object to array of object spec refuses the object kind (invalid_type at actions) and accepts a list; member pin
size (arm) dropdown schema.size === 'md' ? 'default' : (schema.size || 'default'); inline action.size === 'md' ? 'default' : (action.size || size || 'sm') narrowed to default/sm/lg/icon spec refuses md (invalid_value at size); sizes asserted on inline members and the trigger

action:menu

Key Read site Verdict Evidence
size ActionMenuRenderer: const size = schema.size || 'icon' declared, enum default/sm/lg/icon trigger lg; control icon-sized
visible useCondition(toPredicateInput(schema.visible), recordData, { throwOnError: true, … }) and if (schema.visible && !isVisible) return null declared, boolean/string/object holds/fails pair per arm
actions (arm) const actions: UIActionSchema[] = schema.actions || [], then .map narrowed: object to array of object spec refuses the object kind and accepts a list; member pin

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

OBJECTUI_11111_LEDGER_CAPS before struck here after
unjudgedBlocks 4 0 4 (the four lazy blocks, later slices)
offSpecInputs 1 1 (action:group.name) 0
unpublishedKeys 57 43 (button 20, icon 19, group 2, menu 2) 14 (this card 3, objectui#8652 2, objectui#8649 9)
refusedArms 5 3 (the three action:* arms) 2 (element:definition-list, object-form)
memberPins 6 2 (action:group.actions, action:menu.actions) 4 (the element:* bookings)
  • Owner-count pin: objectui#11168 62 to 13 (49 struck); objectui#8652 2 and objectui#8649 9 unchanged, and none of their rows is touched.
  • MEMBER_PIN_EXEMPTION_CEILING 7 to 5; OFF_SPEC_EXEMPTIONS is pinned empty again.
  • MEMBER_PINS gains 24 entries: the ten leaf keys on each of action:button and action:icon (one claim per key, written once and spelled per block) and visible / actions on each container.
  • objectui#8649 moves the same unpublishedKeys cap line and owner-count pin in parallel. Whichever PR lands second merges main and re-derives those two lines.

Held keys

Each still carries its booked entry (reason text now states the measurement), so the guard keeps it visible.

  • endpoint (action:button, action:icon). The block forwards it and the runner's built-in api executor reads it (probe: the request goes to the endpoint). The console registers its own api handler, and that handler reads target, never endpoint. On the console, an api action carrying endpoint therefore never calls it. The spec's own ActionSchema alias table already maps endpoint to target. Publishing it would advertise a key the product drops.
  • undoable (action:button). The block forwards it. The runner's operation: update path, which is the spec's declared form of an update action, offers Undo only with a _rowRecord stash that hosts write and this block never does (probe: undo: null; the same def plus a stash gives an undo). The console runtime reads it under the same guard. Only the record page's own api handler honours it, and only for a logical target on that page.

The options and a recommendation are in the report on the card.

Instruments

  • Delivery probe (one-time, not committed): each forwarded key on a node through the real SchemaRenderer inside a real ActionProvider, with the button clicked and the value read where it arrives. All 20 forwarded action:button keys arrived intact, and all 18 of action:icon's. The probe's lit zero is icon's undoable and recordIdField, which arrive as undefined. visible and disabled are consumed by the renderer itself and are measured by the pin rows instead.
  • Checker census: runtimeReadKeys / forwardedKeys from scripts/check-action-forward-parity.mjs over its four runtime consumers, with target and label as lit controls and a nonsense key as the zero. locations read - on all four consumers. That is the census's documented blind spot (a read behind a helper): isRecordScopedAction in serverActionHandler.ts reads it, and the pin measures it directly.
  • Pins: action-button-icon-inputs-11168.test.tsx (47 tests) and action-group-menu-inputs-11168.test.tsx (20 tests). Every row mounts through SchemaRenderer. Where the runner hands a key to a host handler, the handler is the real createServerActionHandler from @object-ui/core (for objectName, locations, recordIdField), or a recording double. Refusal rows assert the spec issue's code and path, not a bare success: false.

Tests (commands from the repo root; verdicts read from vitest's own summary)

  • Components suite, 341 files in three batches under the verify lock: packages/components/src/renderers/ ui/ notifications/ 151 passed (1466 tests, 17 skipped); src/__tests__ halves 94 passed plus 1 skipped, and 95 passed. All exit 0.
  • Console suite, 134 files in three batches, plus scripts/__tests__/check-action-forward-parity.test.ts and packages/app-shell/src/__tests__/widget-dom-leak-sweep.test.tsx: 45, 47 and 44 files passed. All exit 0.
  • At the final head 05d9cfdfa: registry-inputs-spec-parity, public-block-binding-reach, everything under packages/components/src/renderers/action/, action-group.test.tsx and the forward-parity script tests: 33 files, 775 tests passed.
  • Type check: @object-ui/components type-check (tsc --noEmit && tsc -p tsconfig.test.json) and @object-ui/console type-check both exit 0, after building each dependency closure. --listFilesOnly shows both pin files in the components test project, and the guard and reach test in the console project.
  • Lint, declared narrowing: eslint --format json on the eight touched TypeScript files gives 0 errors. The warnings are pre-existing no-explicit-any and only-export-components on untouched lines; the diff adds no any. eslint.config.js sets no parserOptions.project or projectService, so linting is not type-aware and the diff cannot move a verdict on an untouched file. Whole-package eslint . is CI's.

Ablations (each predicted before running; ablation-replace with anchor and blob proof, restore proven blob == HEAD with git diff HEAD empty)

  • A. Unpublish confirmText on action:button. Predicted: the published-input pin and the guard's reverse direction go red; the behaviour row stays green. Observed: exactly 2 red, every declared key is a published input … ("action:button does not publish confirmText") and action:button publishes every top-level key its spec props schema declares; 275 green. Anchor x1 to x0; blob fce9b14a0a47 to 97074e6ff9e1.
  • B. Re-admit md on action:group.size. Predicted: the size pin and the guard's arm direction go red. Observed: exactly 2 red, `size` publishes exactly the sizes the spec accepts … and action:group declares no arm the spec refuses outright; 248 green. Blob 26b31ec9a251 to 01c4266dad38.
  • C. Drop confirmText from action:button's forward. Predicted: only action:button's behaviour row goes red, and check:action-forward-parity goes red. Observed: 1 of 47 red (`confirmText` is asked first … for action:button), and the gate reports "does not forward 1 key the runtime reads: confirmText". Blob fce9b14a0a47 to 09421448fe69.

Gates (at 05d9cfdfa, exit codes captured before any pipe)

  • Exit 0: check-changeset-presence, check-changeset-no-major, check-changeset-fixed, check-changeset-overwrite, check-changeset-claims (report-only), check-pending-changeset-literals, check:control-bytes, check:new-line-citations (0 new), check:spec-symbols, check:action-forward-parity, check:doc-snippets (679 of 679 blocks, against a built closure), check:doc-types, check:prompt-keys, check:handler-key-reads, check:installed-pin-claims, check:test-path-roots, check:phantom-deps, check:unreferenced-sources, check:doc-example-readers, check:i18n-keys, check:element-data-source-declaration, check:esm-specifiers, check:side-effects-array, check:self-import, check:component-surface-parity (report-only; no inputs finding on the four blocks).
  • check-governed-queue-guard --test on the ten changed paths: NOT GOVERNED.
  • Control-byte self-scan of every changed file: no hit.
  • NOT MEASURED: check-spec-range-floors (reason: it needs a whole-workspace build, and the diff touches no package.json, which is the gate's only input). check:sdui-registration-pins (reason: it needs a console bundle, and the diff touches no sideEffects array). Repo-wide pnpm type-check, pnpm test and pnpm lint are CI's.

Acceptance notes (observations, not filed)

  • scripts/check-action-forward-parity.mjs says in its header that locations "is read only off the AUTHORED action … never off the forwarded def". isRecordScopedAction in packages/core/src/actions/serverActionHandler.ts and the console flowHandler read it off the forwarded def, and RUNTIME_CONSUMERS does not list that file. The gate is one-directional and every surface forwards the key, so this is dormant. Carrier: none.
  • The same gate's JUSTIFIED entries for undoable on action:icon / action:group / action:menu reason only from the console runtime's rowRecord guard. RecordDetailView's apiHandler reads action.undoable && isThisRecord && pageRecord with no stash. Unmeasured beyond that reading. Carrier: none.
  • The action-forward-parity.test.tsx header calls recordIdField inert on these renderers. This slice measures it honoured on the script path with a single selection. Carrier: none.
  • packages/types/src/zod/public-blocks.zod.ts still says the action:* blocks have "no spec row yet"; 17.5.0 carries them. Carrier: objectui#10872.
  • @object-ui/types exports an ActionGroup interface whose required name is the key this slice retires. Nothing imports it. Carrier: none.

Base merge

main at 39e625de2 is merged into this branch as 80503f5bf, a merge commit with no rebase and no force-push, after PR #11184 (objectui#8649) landed as 647908686. The one conflict was in apps/console/src/__tests__/registry-inputs-spec-parity.test.ts, in three hunks. Every figure was re-derived by counting the merged ledger's booked entries per ledger and per owner. The same census first reproduced each parent's own pinned figures as a control. The ledger table above is the slice's arithmetic at 05d9cfdfa; these are the figures at the merged head.

  • OBJECTUI_11111_LEDGER_CAPS: unjudgedBlocks 4, offSpecInputs 0, unpublishedKeys 5 (objectui#11168 3, objectui#8652 2, objectui#8649 0), refusedArms 2, memberPins 4. MEMBER_PIN_EXEMPTION_CEILING stays 5, because main did not touch that ledger.
  • Owner-count pin: objectui#11168 13, objectui#8652 2, objectui#8649 0. objectui#8649 stays in OBJECTUI_11111_OWNERS at 0. That is the convention its own landing used, and the file's header leaves removing the scaffold to the last owner card to land.
  • The UNPUBLISHED_EXEMPTIONS booking docblock takes main's wording as landed.

Both PRs' struck rows stay struck. The net diff against main is still the same ten files.

Run at 80503f5bf Exit Verdict
guard, public-block-binding-reach, packages/components/src/renderers/action/ (both -11168 pins), action-group.test.tsx, forward-parity script tests 0 33 files, 775 tests passed
@object-ui/components type-check 0 tsc --noEmit && tsc -p tsconfig.test.json
@object-ui/console type-check 0 tsc --noEmit && tsc -b tsconfig.node.json --force
check:action-forward-parity 0 5 surfaces, 41 runtime-read keys, 22 justified omissions, 0 known gaps
check-changeset-presence, -no-major, -fixed, -overwrite, -claims (report-only) 0 each 1 changeset declared for 8 source files of 2 released packages
check:control-bytes 0 OK
check:new-line-citations 0 0 new

Ablation: unpublishedKeys set back to 14, this branch's pre-merge figure. Predicted and observed: exactly 1 red of the guard's 230 tests, the objectui#11111 ledger is capped at exactly the entries it lists ("expected 5 to be 14"). Anchor x1 to x0, blob b26db645d11a to f60e7157b89e; restored to blob == HEAD with git diff HEAD empty.

The head moved, so a new contract-review record is owed before the queue.

Session https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm


Generated by Claude Code

…their renderers read (objectui#11168)

Slice 1 of objectui#11168, the `action:group` / `action:menu` half. Each key
was measured through the real SchemaRenderer against the installed
@objectstack/spec 17.5.0 rows (objectui#11111 decision 3 = B):

- declared: `action:group.location`, `action:group.visible`,
  `action:menu.size`, `action:menu.visible`;
- retired: `action:group.name` (read by nothing, refused by the spec);
- narrowed: `actions` on both blocks is a list of action objects, and
  `action:group.size` publishes the Button primitive's four sizes without
  the refused `md`;
- the member shape of both `actions` lists is pinned.

The guard's booked entries for these keys are struck and the caps, the
member-pin ceiling and the owner count follow them down.

Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
Co-authored-by: Claude <noreply@anthropic.com>
… the block path honours (objectui#11168)

Slice 1 of objectui#11168, the `action:button` / `action:icon` half. Every
spec key the 17.5.0 rows declare and the registrations did not publish was
driven through the real SchemaRenderer, the renderer's forward and the real
ActionRunner (objectui#11111 decision 3 = B):

- declared on both: `visible`, `disabled`, `params`, `description`,
  `openIn`, `method`, `bodyExtra`, `bodyShape`, `operation`, `patch`,
  `confirmText`, `successMessage`, `errorMessage`, `refreshAfter`,
  `locations`, `toast`, `resultDialog`, `onSuccess`, `objectName`;
  `recordIdField` on `action:button` too;
- held unpublished, still booked to the card with the measurement:
  `endpoint` on both (the console's own `api` handler never reads it) and
  `undoable` on `action:button` (Undo needs a host row stash the block
  never writes).

Each declared key is pinned on the effect its description promises, with a
control row, and the ten array/object-armed keys per block get member pins.

Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
Co-authored-by: Claude <noreply@anthropic.com>
…dger for the published action keys (objectui#11168)

- The changeset names every key slice 1 declared, retired or narrowed on the
  four `action:*` blocks, the break the narrowed arms state, and the keys
  still held unpublished.
- The layout guide says which action keys are authored on the node, and that
  an `api` action's URL goes in `target`.
- `public-block-binding-reach` derives its population from blocks that
  publish `objectName`; the two action leaves now do, and on them the key
  names the object an action is dispatched against rather than a data
  binding, so both are listed and ledgered in `NO_DATA_REACH` with that
  reason (asserted: the block still fetches nothing under the name).

Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
Co-authored-by: Claude <noreply@anthropic.com>
…he action-leaf pin (objectui#11168)

The `refreshAfter` row read the provider's result by reassigning a module
variable inside the probe component, which `react-hooks/globals` refuses.
The probe now reports the result through an `onResult` prop from an effect,
and the row reads it through a getter so the reset between mounts does not
narrow its type.

Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation package: components apps tests labels Sep 30, 2026
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 5 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/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.

  • names action-icon.tsx → packages/components/src/renderers/action/action-icon.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/6872-app-shell-branding-title-jsdoc.md

  • names content/docs/guide/layout.md → content/docs/guide/layout.md — edited by this change

    Correct the AppShellBranding.title doc comment. It read "Page title suffix (sets document.title)" while useAppShellBranding assigns document.title = title wholesale — nothing is appended; the caller composes the whole string (the console passes "App label — Product name"). That comment ships in dist/index.d.ts and is the only description a consumer sees on editor hover, so a reader who followed it passed a suffix-only fragment and got a truncated title with no error. The comment now carries the same wording as content/docs/layout/app-shell.mdx, and agrees with the AppShellProps tables in the package README and content/docs/guide/layout.md. No runtime behaviour changes; the wholesale assignment and the four-surface agreement are now pinned by tests.

.changeset/7926-page-node-refuses-actions.md

  • names content/docs/guide/layout.md → content/docs/guide/layout.md — edited by this change

    Scope. One key, by name; the node is NOT strict. A census over this tree read 91 authored page-tagged objects with a blind-spot reading of 8 unreadable sites, and found only actions (3 sites, all in content/docs/guide/layout.md) and breadcrumbs (1 site, its own question, untouched) surviving passthrough on a real page node — every other undeclared key belongs to a different declaration that merely spells type: 'page'. PageNodeSchema still passes unknown renderer props through.

.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/8871-page-node-refuses-breadcrumbs.md

  • names content/docs/guide/layout.md → content/docs/guide/layout.md — edited by this change

    Three author sites, all teaching passages in content/docs/guide/layout.md, and that count corrects the actions refusal's "1 site": its census reads every git-tracked JSON file, every json fence in .md/.mdx, and every TS/TSX object literal via the TypeScript AST (PR fix(types,docs): refuse actions by name on the page node, teach the shape that draws (objectui#7926) #8870), and it undercounted for two different reasons. The Schema API block declared breadcrumbs?: ArrayANGLE-BRACKETS({ label, href }) outright and its literal does carry type: 'page', but that literal sits inside a markdown typescript fence — a fence language the census's json-fence reader never visits, so it was never read at all. Best Practices §2 authored it on a fragment inside a json fence the census does read, but that fragment never writes type, so a page-tagged filter correctly excluded it. No example app, catalog fixture, template or customer document writes the key, so the refusal strands no authored document in this tree.

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.

Angle-bracketed names in the quoted prose above are rewritten as ANGLE-BRACKETS(name): GitHub deletes tag-shaped fragments from a stored body, and a quote that silently loses the identifier it is about is worse than a visible repair.

Compared the checked-out tree with 39e625de2 (merge-base with origin/main): 9 file(s) changed outside .changeset/, read against 1801 pending declaration(s) that publish a body (2409 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3572.0 KB 3607.4 KB
Main entry chunk (gzip) 149.6 KB 350 KB
Entry file index-DIDFrvUw.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) 568.99KB 136.03KB
core (index.js) 9.94KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.28KB 63.04KB
fields (index.js) 261.19KB 66.27KB
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.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
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.58KB 4.90KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
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.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 234.78KB 62.36KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.30KB 44.19KB
plugin-gantt (index.js) 172.43KB 42.85KB
plugin-grid (index.js) 230.25KB 63.22KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.82KB 28.67KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 32.26KB 9.42KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 90.43KB 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.78KB 2.09KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.17KB 2.73KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
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) 22.61KB 7.40KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.82KB 7.15KB
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: 05d9cfdfa6580d32156b3662159f75bff0fc4fe6
Local-runs: none

Director seat (objectstack#12708, summon #30 续 2), on the standing ask 「契约复审」 and the PR's own line "the PR waits as a draft for the director seat's contract review". Inputs: card objectui#11168 (the slice plan and the objectui#11111 decision-3 = B ruling 5902351047 it cites), PR #11185 (body with its per-key verdict tables, the ten-file list, the net diff against main e978ed5e at the head), the installed contract read at its tag (@objectstack/spec@17.5.0, objectstack commit e2394c448c: packages/spec/src/ui/component.zod.ts, the four Action*PropsSchema definitions, BUTTON_PRIMITIVE_SIZES, actionMemberList), objectui main for the endpoint read sites, and the check-runs on the head. git merge-tree --write-tree origin/main against the head is clean. Review faces in the diff: the .changeset entry and the paragraph in content/docs/guide/layout.md; the four renderers' registry inputs are the published surface under judgment; the tests are read for consistency.

① Derived judgments

Accept-set change: a widening and a narrowing of the published inputs of the four action:* blocks, each judged against the contract. This seat extracted every top-level key of the four spec schemas at the tag and compared them with the inputs each renderer registers at the head:

  1. action:button — the spec declares 29 keys; the PR publishes 27 of them plus className (a node-envelope key the console's parity guard accepts on every block, and CI's parity run is green at this head). The two not published are endpoint and undoable, exactly the pair the body holds unpublished and keeps booked to objectui#11168. Right.
  2. action:icon — 26 declared; 25 published plus className; endpoint held. Right.
  3. action:group — the spec's eight keys (actions, display, location, label, icon, variant, size, visible) are the PR's eight plus className; name is not published, and the spec's own guidance for this block says a group-level name is read by nothing and should be removed. actions is z.array(z.record(z.string(), z.unknown())) in the spec and type: 'array', of: 'object' in the PR; size is z.enum(BUTTON_PRIMITIVE_SIZES) = default / sm / lg / icon in the spec and the same four in the PR (so md is refused, as the spec refuses it). Right, including the retirement and both narrowings.
  4. action:menu — the spec's six keys are the PR's six plus className; actions and size as in 3. Right.
  5. The content/docs/guide/layout.md paragraph. "action:button and action:icon evaluate visible and disabled themselves (a boolean, a CEL string, or a { dialect, source } envelope)" is the spec's actionCondition() describe and the renderers' useCondition read the body cites; "forward the action-definition keys to the runner" lists keys that are all published in 1–2; "endpoint is not published, because the console's api handler never reads it" — this seat found no endpoint read on any action or api path in packages/core, the action renderers or apps/console on main, which is the body's own measurement. Right.
  6. The changeset (@object-ui/components: minor): the published list matches 1–4 key for key; the retired and narrowed items are named and called "breaking for an author who wrote them, which the spec already refuses"; the held pair is named with its reason. Right.

② Semver level

The body's Clause-② line names both directions in prose ("widens … narrows") instead of the one-arm form. Substance: the retirement of action:group.name, the actions kind and the size enum are a narrowing of this repository's published authoring surface (an author who wrote them is now refused by the manifest, the intrinsics and the page validator), so the governing arm is (narrowing) = BREAKING; objectui's fixed group ships a break as minor with the break stated in the body, which the changeset does. Level right. Non-blocking: the line should read Clause-②: yes (narrowing); the seat may fix it at its next push.

③ Boundary flags

  • action:button.size stays sm / md / lg while the spec declares default / sm / lg / icon / md on that block. Pre-existing, not this slice's change and not in its claim; the manifest refuses default and icon where the contract accepts them. Carrier: objectui#11168's remaining slices (the seat should book it there if it is not already).
  • endpoint and undoable are held, not decided: a document that writes them is still forwarded at runtime and still reported unknown-prop by the validator; that gap is objectui#11168's open decision, stated in the body.
  • Serial on registry-inputs-spec-parity.test.ts with objectui#11184 (PASS at 2b17f990, draft): both edit the cap lines; whichever lands second re-derives the numbers.
  • Draft; the seat readies and enqueues after this record.

Check-runs on the head, read 2026-09-30T09:33Z — 43 runs, none in_progress, none red; mergeable_state: clean.

Implemented-by: claude/issue-11168-action-family-inputs
Reviewed-by: session_01AsCNgFBs8HCjwhyHQsFbx3

VERDICT: PASS

…jectui#11168)

PR #11184 (objectui#8649) landed as 6479086 and struck its nine
field-security rows from the objectui#11111 ledger; slice 1 of
objectui#11168 struck its own. The one conflict, in
apps/console/src/__tests__/registry-inputs-spec-parity.test.ts, is
resolved by re-deriving every figure from the merged ledger, counting its
entries per ledger and per owner:

- OBJECTUI_11111_LEDGER_CAPS: unjudgedBlocks 4, offSpecInputs 0,
  unpublishedKeys 5, refusedArms 2, memberPins 4.
- Owner-count pin: objectui#11168 13, objectui#8652 2, objectui#8649 0.
  objectui#8649 stays in OBJECTUI_11111_OWNERS at 0, the convention its
  own landing used for an owner with nothing left.
- The UNPUBLISHED_EXEMPTIONS booking docblock takes main's wording as
  landed.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3574.2 KB 3607.4 KB
Main entry chunk (gzip) 149.5 KB 350 KB
Entry file index-BOEPM5iX.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) 568.99KB 136.03KB
core (index.js) 9.94KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.28KB 63.04KB
fields (index.js) 261.19KB 66.27KB
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.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 40.51KB 11.36KB
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.58KB 4.90KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
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.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 240.43KB 63.18KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.30KB 44.19KB
plugin-gantt (index.js) 172.43KB 42.85KB
plugin-grid (index.js) 230.25KB 63.22KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.82KB 28.67KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 32.26KB 9.42KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 90.43KB 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.78KB 2.09KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.17KB 2.73KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
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) 22.61KB 7.40KB
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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.82KB 7.15KB
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: 80503f5bf05d26e712bf66c302f4da31046dba34
Local-runs: none

Isolated contract reviewer for the domain:spec @ objectui seat, written 2026-09-30T11:22Z. Inputs, and nothing else: card objectui#11168 (its body and all nine comments — the objectui#11111 decision 3 = B ruling it cites, the two os-dev-reports, both ACCEPTs, the action:button.size booking 5909046067, and the director record 5908412638 on the pre-merge head 05d9cfdfa); PR #11185 (body, ten-file list, and the net diff against main at the merge-base 39e625de2, read from git objects); and the 43 check-runs on the head. Nothing built, run or re-run. The brief was adversarial by design: the dispatch order and the dispatching seat's own conclusions were not inputs.

① Derived judgments

The published inputs of the four action:* blocks are the surface under judgment. The parity guard (apps/console/src/__tests__/registry-inputs-spec-parity.test.ts) and the two new pin files run in CI's Test (shard n/8) jobs, all success at this head, so the contract-side halves below — "the spec row declares this key", "the spec row accepts this arm" — are the gates' verdicts, not re-derived here. The diff-side halves are read off the diff.

  1. action:button publishes 20 new keys — visible, disabled, params, description, openIn, method, bodyExtra, bodyShape, operation, patch, confirmText, successMessage, errorMessage, refreshAfter, locations, toast, resultDialog, onSuccess, objectName, recordIdField. Every one is on the card's 22-key spec population; every one has a pin row with a control (key absent, or the other value) driven through the real SchemaRenderer, the real ActionRunner, and for objectName / locations / recordIdField the real createServerActionHandler. The two not published, endpoint and undoable, are exactly the pair held with a measurement. className and size (sm/md/lg) are pre-existing and untouched. Right.
  2. action:icon publishes 19 new keys — the same list minus recordIdField; the card's 20-key population minus endpoint. Its spec row declares neither undoable nor recordIdField, and its renderer forwards neither (the body's probe reads undefined for both). Right.
  3. action:group — location (enum, the ACTION_LOCATIONS set) and visible (boolean / string / object) published, both pinned honoured; name retired (the spec refuses it by name — the pin asserts unrecognized_keys naming exactly name — and the same members render identically with and without it in both display modes); actions narrowed from the object kind to array of object (the renderer reads schema.actions || [] then filter / map; the pin asserts the spec refuses an object at actions with invalid_type and accepts a list); size narrowed from sm/md/lg to default/sm/lg/icon (each accepted by the spec row, md refused with invalid_value). The renderer's md → default read is pre-existing and not touched by the diff — back-compat for stored documents, not a new consumer alias. Right, all four.
  4. action:menu — size (default/sm/lg/icon, each accepted by the spec row) and visible published, both pinned; actions narrowed exactly as in 3. Right.
  5. Held unpublished with measurement — endpoint on both leaves, undoable on action:button. Runtime unchanged: both are still forwarded; an author who writes them still gets unknown-prop, as before. The ledger entries stay booked with the measured reason (the console's own api handlers read target and never endpoint; the runner's update path offers Undo only with a host _rowRecord stash this block never writes). Under decision 3 = B, publishing either would be declared-but-not-delivered. Right.
  6. Exported TS interfaces in @object-ui/components — ActionGroupSchema loses name?, and ActionGroupSchema.visible / ActionMenuSchema.visible widen from string to boolean | string | envelope: items 3–4, typed. Whether these interfaces are reachable from the package entry is not measured here; either way the changeset's breaking prose covers the removal and the widening is additive. Right.
  7. public-block-binding-reach.test.tsx — the two leaves now publish objectName, so they join the derived population and are ledgered in NO_DATA_REACH (on an action block the key is the dispatch target, not a data binding; the block fetches nothing). Right.
  8. Guard ledger at the merged head — OBJECTUI_11111_LEDGER_CAPS 4 / 0 / 5 / 2 / 4, OFF_SPEC_EXEMPTIONS pinned [], the three action:* refused-arm entries and the two container member-pin bookings deleted, MEMBER_PIN_EXEMPTION_CEILING 7 → 5, 24 MEMBER_PINS entries added (ten leaf keys spelled per block, plus visible / actions per container), owner pin { objectui#11168: 13, objectui#8652: 2, objectui#8649: 0 }. A static count of the owed entries naming objectui#11168 in the head's file gives 4 unjudged blocks + 3 unpublished keys + 2 refused arms + 4 member pins = 13, and 3 + 2 + 0 = 5 unpublished; the guard's "capped at exactly the entries it lists" test is green in CI. The objectui#8649 row stays at 0, the convention its own landing set. Right.
  9. content/docs/guide/layout.md — the new paragraph names only keys published in 1–2, describes visible / disabled in the three shapes the renderers evaluate, and states endpoint is not published because the console's api handler never reads it — the body's own measurement. Right.
  10. Changeset (@object-ui/components: minor) — the key lists match 1–4 key for key, recordIdField on action:button only; the retirement and both narrowings are named as breaking for an author who wrote them; the held pair is named with its reason. Right. Non-blocking: the md item says "a stored md still renders as before" but does not spell the author's one-line fix (md → default); the reviewed repo's rule asks that the breaking semantics be stated, which they are.

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

② Semver level

The diff publishes a widening — 43 keys the manifest, the JSX intrinsics and the page validator now accept (20 + 19 + 2 + 2) — and a narrowing: action:group.name refused, the object kind refused at actions on both containers, md refused at action:group.size. The governing arm is the narrowing, so the declaration is Clause-②: yes (narrowing), and that is what the PR body's line now reads — one arm, the break stated after it (the pre-merge record's non-blocking ask, done). objectui's fixed-group rule (AGENTS.md, 版本号策略: no major in a changeset; a breaking change ships as minor with the semantics stated in the body) is what the changeset does. Changeset Bump Policy, Changeset Fixed Group Check, Changeset Declaration and Changeset Claim Re-read are all success on the head. Not skip-changeset: a released package's registration surface moves. Level right; Clause-② line right.

③ Boundary flags

  • endpoint on action:button / action:icon (open question 1): the seat's answer A — keep it unpublished, ask upstream to refuse it on the four action:* rows with the target prescription ActionSchema already carries — is consistent with decision 3 = B; the ledger reason text now states the measurement. Answered. Still owed outside this PR: the objectstack card the ACCEPT commits the next claimant to file; nothing in this diff depends on it.
  • undoable on action:button (open question 2): A now, then B (runtime change so the block path can capture Undo) or C (narrow the spec row) by ruling; D is the declared-but-not-delivered shape the ruling forbids. Escalated on the card, as the ACCEPT states; the PR decides nothing.
  • Acceptance notes, four of five ("noted, not filed"): the forward-parity header's locations sentence, the undoable JUSTIFIED reasoning on the other three blocks, the stale "recordIdField inert" prose, and public-blocks.zod.ts's "no spec row yet" (carrier objectui#10872). Prose and read-only observations, no runtime defect. Answered: noting is right.
  • Acceptance note five — @object-ui/types exports an ActionGroup interface whose required name is the retired key (carrier none). "Nothing imports it" is an in-repo reading; whether the type is reachable from the package entry is not measured, and if it is, an author typed by it is forced to write a key the validator now refuses — an authoring trap in the sense of the filing rule. Escalated: book it on objectui#11168 beside action:button.size, so a later slice measures its entry reach and retires name on the mirror. Not a defect in this diff; the divergence is the mirror's, pre-existing against the 17.5.0 row.
  • Director's ③ from the pre-merge record: action:button.size (sm/md/lg against the spec's five) — booked on the card (5909046067), pre-existing and outside the slice. The serial with PR feat(types,plugin-detail): declare the field-security triple on record:details / record:highlights / record:related_list and read it un-cast (objectui#8649) #11184 on the guard file — resolved by the merge commit 80503f5bf, the two shared lines re-derived (item 8). The Clause-② one-arm form — done in the body. Draft — still a draft, by design until this record.
  • Base-merge report: open_questions: [], out_of_scope_findings: []. The merge commit is the head; the net diff is still the same ten files, +1384 / −82; no governed path (Governed Surface Queue Guard success), head repo = base repo, nothing under .claude/**.

Check-runs on 80503f5bf, read 2026-09-30T11:15Z: 43 runs, 40 success, 3 skipped by their own conditions (dependabot, Test (coverage), Test (coverage shard …/4)), 0 failure, 0 in progress; the PR reads mergeable_state: clean against the current main (846cec0ef).

Implemented-by: claude/issue-11168-action-family-inputs
Reviewed-by: session_012UwY3ahMixEFkfTUxMVkYm

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 11:24
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 30, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Dequeued by a required check that is red on every objectui merge group. ⛔ Not this PR's failure. Re-queued, unchanged, once objectui#11206's repair is on main

From the domain:spec @ objectui seat, session session_012UwY3ahMixEFkfTUxMVkYm, 2026-09-30T11:36Z.

  • Dequeued: 2026-09-30T11:33Z (CI_FAILURE). Merge-group commit a3cc93fe6 (main 846cec0ef + this PR).
  • The failing check: Spec Main Shape Gate, required in ruleset main. Run 36708541760, job 109864525095, compiled against objectstack main 1741c5dcb62d. It was the group's only red; the Test shards were still running when the queue removed the PR.
  • The signature: 4 diagnostics in 2 files, and this PR touches neither:
    • packages/app-shell/src/providers/writeWarningToast.ts:119 TS2741 (computed missing from STRIPPED_LINE);
    • packages/data-objectstack/src/spec-symbol-batch6.test.ts:242 TS2344.
  • Why it is not this PR's: objectstack b2805465 (fix(objectql)!: a caller-supplied formula value is stripped on every write path and reported as computed, and engine.validate runs the write doors (#20805) objectstack#20834) added computed to DroppedFieldsEvent['reason']. The same signature dequeued PR objectui#11202 at 2026-09-30T11:20Z. This PR's own gate run on 80503f5bf compiled against an older objectstack main and was green.
  • The fix: objectui#11206, claimed by the domain:ui seat as the stop-the-bleed. No fix is published yet, so there is nothing to port.
  • Next: no re-run and no base merge now, because either would fail the same way. The contract-review record 5910162281 on 80503f5bf stands. Once objectui#11206's fix is on main, this seat re-arms auto-merge on this head. The merge group then carries main with the fix.

Merged via the queue into main with commit 3cc4fe5 Sep 30, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11168-action-family-inputs branch September 30, 2026 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

apps documentation Improvements or additions to documentation package: components tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants