Skip to content

feat(app-shell): register record:approval_decision, armed in @object-ui/types (objectui#12342) - #12362

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-12342-register-approval-decision
Oct 14, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-12342-register-approval-decision

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #12342
Clause-②: yes

What

@objectstack/spec 18 declares the page block record:approval_decision. ApprovalDecisionRenderer existed, but no registration mounted it, so a page naming the block passed the spec and drew nothing. This PR registers it and lands everything a registered, page-authorable type needs in this repo.

  • Registration (@object-ui/app-shell). New side-effect leaf views/record-approval-decision-renderer.tsx registers ApprovalDecisionRenderer as record:approval_decision (namespace record, skipFallback: true, so no bare approval_decision). It joins the entry's side-effect imports and both halves of the package sideEffects array, the same path record:approvals and record:attachments take. The renderer itself is unchanged and still not exported from the entry.
  • Arm (@object-ui/types). New RecordApprovalDecisionBlockSchema (zod, zod/record-approval-decision.zod.ts) plus a TS twin of the same name (record-approval-decision.ts), and a new member of AnyComponentSchema (zod and TS). The properties bag restates the spec row: the spec builds it inline as an empty strict object and exports it under no name, the same situation as element:divider. So the only bag it accepts is {}. body / children are refused by name.
  • CLI. known-schema-types.ts regenerated (node scripts/regenerate-known-schema-types.mjs): plus record:approval_decision.
  • Studio palette. Not offered (card ruling). Only the PALETTE_EXCLUSIONS reason changed, from "not yet registered" to "renders (registered by @object-ui/app-shell), not yet offered".
  • Docs: an app-shell README section for the block. Changeset: .changeset/12342-register-approval-decision.md (app-shell and types minor, cli patch).

Why it lands outside app-shell too

The card read this as registration plus pins. Measurement showed that registration alone would raise a ratchet pin that must never rise. registered-types-validate-ratchet-10859 (@object-ui/cli) counts registered namespaced types that objectui validate refuses at type. On main, validateAuthoredDocument answers invalid_union at type for record:approval_decision, and also for record:approvals and record:attachments (probe run on base 065a1cf). Registering the block without an arm would take NAMESPACED_REFUSED_AT_TYPE from 308 to 309. The panel's own docblock already planned for the registration, its side-effect import, its known-schema-types.ts line and its zod arm to land together. The arm follows the cloud: widgets' precedent (its own module, wired into AnyComponentSchema, TS twin plus mirror-parity pair). It is not in public-blocks.zod.ts, because that union is pinned to the ADR-0080 curated list and this block is not curated.

Premise checks (dispatch section 2)

  • A1 holds. The renderer is at views/approval-decision/ApprovalDecisionPanel.tsx, and its test pinned ComponentRegistry.has('record:approval_decision') === false. The pin is flipped to true plus get(...) === ApprovalDecisionRenderer, and a new row renders { type: 'record:approval_decision' } through the real SchemaRenderer.
  • A2 holds for the path, but not for the arm. Same registration path and sideEffects handling as the two siblings (check:side-effects-array green). The siblings are registered without arms, so copying them exactly is what the ratchet refuses (see above).
  • A3: no double render. RecordDetailView only skips useRecordApprovals on a sys_approval_request page, and its comment names this block as the place where decisions are drawn. It mounts no panel by any route of its own; the panel appears only where page metadata places the block. RecordDetailView.tsx is not edited, so this PR has no interaction with feat(print): render a page that declares print — print CSS, printable blocks whole, record print action (objectui#11958) #12356.
  • A4 holds. The installed @objectstack/spec 18.0.0-next.0 carries ComponentPropsMap['record:approval_decision'] as emptyProps(...), a strict object with no member. The new arm test re-reads that row on every run: its key set is empty, and the arm's bag accepts and refuses exactly what the row does.

Pins on a real render

apps/console/src/services/approvalRequestsDataSource.decisionPage.test.tsx (real RecordDetailView, real DeclaredActionsBar, routed approvals source):

  • This file used to register the renderer itself. That registration is removed, and a row asserts the package's registration is what the page reaches.
  • New: a decided request (status approved, no tally, can_act: false) gets the panel with no progress bar and no decision. A viewer who may not decide (the submitter, pending) gets the tally and the recall control, and no approve or reject. CONTROL: the deciding viewer gets both decisions and no recall. The pending-request rows already present still pass.

Verification (final head e7e004c)

  • vitest, one run on e7e004c under the verify lock: packages/types/ + packages/cli/ in full, packages/app-shell/src/views/approval-decision/, packages/app-shell/src/views/metadata-admin/previews/__tests__/, the product-neutral ratchet, the two console files, scripts/__tests__/known-schema-types-derivation-5115.test.ts -- Test Files 427 passed (427), Tests 11161 passed (11161).
  • type-check: @object-ui/types, @object-ui/app-shell, @object-ui/cli all exit 0 on 3a9b5d5, after building their dependency closure (--filter '@object-ui/app-shell^...' --filter '@object-ui/cli^...'). @object-ui/types was re-run on e7e004c and exits 0. The only change from 3a9b5d5 to e7e004c is one string literal in the arm's refusal text. The app-shell tsconfig.test.json covers src/**/*.test.tsx, so the uncast SchemaRenderer row is type-checked against the built @object-ui/types.
  • Ablation 1 (registration): removing the barrel import in packages/app-shell/src/index.ts through ablation-replace.mjs (anchor 1 to 0, blob changed) turns the console file red: Tests 7 failed | 4 passed (11), starting with the registration row. Restore was proven by blob == HEAD.
  • Ablation 2 (decided-case non-vacuity): flipping the decided row's can_act to true turns a decided request red (expected button to be null). Restore was proven by blob == HEAD.
  • Gates, each exit code read directly on e7e004c: check:phantom-deps, self-import, unreferenced-sources, doc-example-readers, handler-key-reads, side-effects-array, element-data-source-declaration, spec-symbols, action-forward-parity, designer-field-key-parity, icon-record-names, i18n-keys, i18n-drift, i18n-designer-parity, registry-bare-names, control-bytes, new-line-citations all 0. check-changeset-fixed.mjs, check-changeset-presence.mjs and check-changeset-no-major.mjs are 0. check:sdui-registration-pins is 0 after a full console build (approval_decision 1 chunk(s), 16 of 16 present; measured on 3a9b5d5, and e7e004c changes no registration or array). check:spec-floors is 1, with "The scan collapsed: 0 package(s) declare @objectstack/spec in a consumer-facing field". That is the known failure on main (objectui#12334), and this diff touches no dependency field.
  • Lint, narrowed: eslint --format json over the 17 changed .ts/.tsx files gives 17 results, 0 errors and 0 warnings. The package lint scripts are eslint . (types, app-shell, console) and eslint src (cli). eslint.config.js sets no project / projectService, so linting is not type-aware and this diff cannot change the verdict on any untouched file. The full pnpm lint run belongs to CI.

Acceptance notes

  • Accept set and exports widen (the dispatch declared Clause-②: no): @object-ui/types/zod exports RecordApprovalDecisionBlockSchema, @object-ui/types exports the TS type of the same name, and AnyComponentSchema / StrictAnyComponentSchema / objectui validate now accept a record:approval_decision node. All three follow from the spec row. They are reported here and in the dev report; no Clause-② line is written.
  • The arm does not spread NODE_ENVELOPE (responsiveStyles). Like the cloud: arms, it declares no node-level key without a measured producer, and Studio does not offer the block.
  • record:approvals and record:attachments remain registered without arms. objectui validate still refuses both at type while the spec accepts them, and they stay inside the namespaced ratchet count. Not addressed here; noted in the dev report.

Generated by Claude Code

…ui/types (objectui#12342)

Spec 18 declares the block; the decision panel was written but no
registration mounted it, so a page naming it passed the spec and drew
nothing. Register it from a side-effect leaf in the app-shell entry, arm it
in @object-ui/types/zod from the spec's empty strict row (the namespaced
registered-types ratchet never rises), regenerate the CLI's known types,
and pin a real render for a pending request, a decided one and a viewer
who may not decide.

Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
Co-authored-by: Claude <noreply@anthropic.com>
…er tier's warning (objectui#12342)

The family-D refusal sentence carries the corrected objectui#10928 clause,
as content-channel-parser-tier-clause-10928 requires of every member.

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

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 2 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/11573-any-component-emit-headroom.md

  • names packages/types/src/__tests__/any-component-emit-headroom-11573.test.ts → packages/types/src/__tests__/any-component-emit-headroom-11573.test.ts — edited by this change

    packages/types/src/__tests__/any-component-emit-headroom-11573.test.ts reads the declaration's size with the compiler's own counter, fails when it crosses a tenth of the ceiling below it, and lists every member as named or inline.

.changeset/11806-login-server-unreachable.md

  • names packages/app-shell/README.md → packages/app-shell/README.md — edited by this change

    packages/app-shell/README.md documents the two new states in its sign-up table. Its useSignUpOffer recipe for hosts now asks decideSignUpOffer only once the read has answered, and answers unreachable for a failed read.

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 2312bd9a6 (merge-base with origin/main): 19 file(s) changed outside .changeset/, read against 430 pending declaration(s) that publish a body (446 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 69 chunks) 3052.9 KB 3119.4 KB
Main entry chunk (gzip) 73.8 KB 350 KB
Entry file index-B7tgJIYu.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) 20.90KB 7.67KB
app-shell (runtime-config.js) 22.59KB 7.89KB
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.15KB 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) 41.19KB 11.12KB
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.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 592.14KB 142.83KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.41KB 69.48KB
fields (index.js) 283.00KB 72.12KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
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.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.26KB 11.71KB
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) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
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.04KB 3.92KB
plugin-calendar (index.js) 55.05KB 16.09KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.72KB 48.07KB
plugin-dashboard (index.js) 143.63KB 38.79KB
plugin-designer (index.js) 233.77KB 50.02KB
plugin-detail (index.js) 259.78KB 68.65KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 178.88KB 46.47KB
plugin-gantt (index.js) 178.63KB 45.19KB
plugin-grid (index.js) 258.97KB 72.23KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 123.64KB 31.63KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.99KB 23.23KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
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) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
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.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
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) 1.13KB 0.65KB
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) 5.78KB 2.70KB
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 (node-slots.js) 7.18KB 2.34KB
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-approval-decision.js) 0.20KB 0.20KB
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) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
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

objectstack-fleet Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor Author

Standing down on one red: Test at head e7e004c, not this PR's · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-14T11:31Z

  • Failing check: Test (aggregate) in CI run 38538610357. It is red because Test (shard 8/12) and Test (shard 10/12) were cancelled at the job's timeout-minutes: 20. The other ten shards took 7–12 min.
  • Why it is not this PR's, measured by the dev in the patch round (report on objectui#12342):
    • The exact CI tree (e7e004c merged with base 2312bd9), run with CI's own pnpm test --shard=8/12 and --shard=10/12, is green in about 12 min each.
    • The same 421 files per shard on base 2312bd9 take the same time: 731s and 733s, against 711s and 749s on head.
    • Summed per-file cost moves by 0.97× and 1.02×, and no file in this PR's surface slowed.
    • The CI logs show both legs still completing files at the cancel, at about position 296 and 302 of 421. That is half rate throughout, not a hang.
    • Every same-hour CI run had all 12 shards at 6–12 min.
  • Fix ported: none exists. No in-repo cause was found; the reading is a slow runner on that one run.
  • Next: one re-run of the failed jobs on this exact head. A second slow 8/10 pair on the same partition is new evidence and goes back to root-causing by file.

Spec Main Shape Gate was red on this head with main's old signature. It is a separate, advisory workflow, and the re-run above does not touch it. main has been green since 524136e (objectui#12325), so this PR's next push or main merge will clear it. (Corrected in place: the first wording said the CI re-run would clear it.)


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e7e004c2e9c295b734c250106529af07c67934ca
Local-runs: none

Inputs read: card #12342 body, the first grade (6162019039), the claim (6163674287, its Clause-② corrected in place from no to yes), both os-dev-reports (6164685074, 6167336736), the regrade (6164691387); PR #12362 body (line 2 Clause-②: yes, the seat's), its 20-file list and net diff; the seat's stand-down comment (6167357099); the head's 33 check-runs with filter=latest; the Spec Main Shape Gate run's 21 annotations; main's check-runs at 524136e; package.json and src/index.ts of @object-ui/types (plus src/zod/index.zod.ts), @object-ui/app-shell, @object-ui/cli at head and at base 2312bd9; the sibling record-approvals-renderer.tsx and cloud.zod.ts at head; the spec row in objectstack packages/spec/src/ui/component.zod.ts and page.zod.ts at 18.0.0-next.0; the 11930-workspace-timezone-notice.md changeset as the precedent for an arm landing with a registration.

① Derived judgments

  • Checks. Test (aggregate, 115834019740) is success at 11:44Z after the failed-jobs re-run on this exact head; Test (shard 8/12) and Test (shard 10/12) are success from the 11:32Z re-run; every other ci.yml job, Lint, Type Check, Repo Gates, the four changeset gates, Governed Surface Queue Guard and Line Citation Gate are success. The one failure is Spec Main Shape Gate, not re-run. Its annotations name three files only: packages/react/src/hooks/useViewData.ts (lines 124, 125, TS2367), packages/core/src/adapters/resolveDataSource.ts (lines 59, 61, 62, TS2678 and TS2339), and the .github echoes of the same six, all "compiled against @objectstack/spec built from objectstack-ai/objectstack@a7a135dc". None of the three is in this PR's 20 files, and main at 524136e has that gate success twice (10:08Z, 11:04Z). The seat's reading holds: an advisory workflow red on main's old signature, cleared on main, nothing of this PR's.
  • Registration: record:approval_decision mounts ApprovalDecisionRenderer, namespace record, skipFallback: true, no bare approval_decision. Right. The new leaf is a line-for-line twin of record-approvals-renderer.tsx (category: 'record', inputs: [{ name: 'className' }], a default export of the renderer), imported for its side effect from src/index.ts and added to both halves of sideEffects (check:side-effects-array inside Repo Gates, green). The renderer stays unexported from the entry; index.ts gains the import line and nothing else.
  • Arm: RecordApprovalDecisionBlockSchema = BaseSchema + type literal + optional properties: z.strictObject({}) + body / children tombstoned; a new AnyComponentSchema member. Right, and the derivation from the spec row is sound. The spec builds ComponentPropsMap['record:approval_decision'] inline as emptyProps('record:approval_decision'), a strictObject with no member, exported under no name, so it cannot be imported by reference; a restated empty strict bag is the only faithful form, the same situation as element:divider. Unlike a free-hand restatement, record-approval-decision-arm-12342.test.ts reads the INSTALLED row on every run and pins the arm's key set and accept set to it ({} accepted, any key refused, a non-object refused), so a prop the spec adds later turns the file red. That is a derivation with a gate, which is the right shape.
  • The arm is not in public-blocks.zod.ts. Right: that union is pinned to the ADR-0080 curated list and this block is not curated (the palette still does not offer it, by the card's own ruling). The cloud: arms are the precedent for a module of its own, and the shape matches CloudPlanStatusSchema (BaseSchema.extend, properties: z.strictObject(...).optional()).
  • Why the arm lands at all (the measured premise). Right. The dev showed registration alone would move NAMESPACED_REFUSED_AT_TYPE in registered-types-validate-ratchet-10859 from 308 to 309, a pin that never rises; the panel's own docblock at base already said the registration, its barrel import, its known-schema-types.ts line and its arm land together. The ratchet file gains one row pinning refusedAtType('record:approval_decision') === false and BARE_KEYS without approval_decision.
  • Published surface, measured on the faces. @object-ui/types exports is unchanged; the . entry gains one type export (RecordApprovalDecisionBlockSchema) and AnySchema gains that member; ./zod gains one value export of the same name and AnyComponentSchema / StrictAnyComponentSchema / safeValidateSchema accept one more type literal. @object-ui/app-shell exports is unchanged; sideEffects (one of the eight contract fields) gains two paths. @object-ui/cli exports and src/index.ts are byte-identical at head and base; KNOWN_SCHEMA_TYPES (not on the entry) gains one line, so objectui check knows the type and objectui validate stops refusing it at type. The dev's Clause-② measurement is right on every face.
  • Mirror parity, emit headroom, console parity. EXPECTED_MIRROR_PAIRS 195 to 196 with the pair declared in both MIRRORS and Declared; one inline arm row in the emit-headroom ledger; UNJUDGED_SPEC_BLOCKS reason moved to "registered". All consistent with the diff.
  • Pins on a real render. The console decisionPage test drops its own registration (ablation 1: removing the barrel import turns 7 rows red, so the package's registration is what the page reaches) and adds the decided request (ablation 2 non-vacuous), the submitter and a control. The panel test flips has(...) to true, asserts get(...) === ApprovalDecisionRenderer, and renders { type: 'record:approval_decision' } through the real SchemaRenderer. RecordDetailView.tsx is untouched, so no interaction with feat(print): render a page that declares print — print CSS, printable blocks whole, record print action (objectui#11958) #12356.

② Semver level

  • .changeset/12342-register-approval-decision.md: @object-ui/app-shell minor, @object-ui/types minor, @object-ui/cli patch.
  • types minor: right, two new exports and a wider accept set on two published faces, nothing removed. app-shell minor: right, a new registered block and a sideEffects move, nothing removed. Under objectui AGENTS.md §9 nothing here could be major in any case, and Changeset Bump Policy / Changeset Declaration / Changeset Fixed Group Check are green.
  • cli patch: understated against the repo's own precedent. 11930-workspace-timezone-notice.md, the last arm-with-registration landing, declared @object-ui/cli: minor for exactly this shape ("objectui check knows ... as a registered type"), and this PR's body says in its own words that objectui validate "no longer refuses the type at type", an accept-set widening of the published bin. The reading that it is a fix (spec 18 declared the type, the CLI lagged) is defensible, which is why this is a flag and not the verdict: the fixed group publishes every package at the same minor regardless, so the released version is identical and only the CHANGELOG heading differs. Recommend the seat amend the one frontmatter line to minor before the queue, for consistency with 11930; the body text needs no change.
  • Clause-②: yes. The seat's in-place correction of the claim from no to yes and the line it added to the PR body are right; the dev's acceptance note that "no Clause-② line is written" is now stale and harmless.

③ Boundary flags

  • Deviation (file surface): the landing spans @object-ui/types, @object-ui/cli, apps/console tests and the app-shell README, beyond the claim's views/approval-decision/, the registration leaf and block-types.ts. Answered: the ratchet measurement is the reason, the panel's docblock planned it, and the seat accepted it by correcting the claim. No breach.
  • A2 false for the arm (the siblings are unarmed). Answered: copying the siblings exactly is what the ratchet refuses.
  • Out-of-scope finding: record:approvals and record:attachments are registered without arms and objectui validate refuses both at type while spec 18 accepts them (inside the 308). Escalated as the dev proposed: fold into the objectui#10872 namespaced-ratchet family closure, no single-point card. Not this PR's to fix.
  • Open question (patch round): how to get a green Test. Answered by the seat: option A was taken, the failed jobs were re-run on this head and are green; the stand-down comment's measurement (CI tree reproduced locally, both shards green at about 12 min, per-file cost 0.97x / 1.02x, no file in this PR's surface slower) is consistent with a slow runner. A second slow 8/10 pair on the same partition would be new evidence, as the seat wrote.
  • Out-of-scope finding: the half-rate shard pair, "noted, not filed". Answered: no repro, class a needs one.
  • check:spec-floors exit 1. Answered: the known main failure (objectui#12334); this diff moves no dependency field, and Repo Gates is green on the head.
  • changeset-claim-re-read (advisory, exit 0) named two pending changesets that cite files this PR edits (11573-any-component-emit-headroom.md, 11806-login-server-unreachable.md). Answered on reading: the 11573 sentence (the test "lists every member as named or inline") stays true with one more row; the 11806 sentence is about the README's sign-up table, which this PR's new section does not touch.

Implemented-by: claude/issue-12342-register-approval-decision
Reviewed-by: session_01XVp19E1MYUTFM3dnkwz8gB

VERDICT: PASS


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants