Skip to content

fix(app-shell): the approval decision panel draws a localized notice off an approval request page (objectui#12072) - #12073

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-12072-decision-panel-notice
Oct 9, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-12072-decision-panel-notice

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #12072

Clause-②: yes

What this does

Placed anywhere other than an approval request's record page, the decision panel used to draw nothing. Its renderer (ApprovalDecisionRenderer in @object-ui/app-shell, the renderer for the proposed record:approval_decision, still module-internal) now draws a short, localized notice there instead of null. A block an author places on the wrong page is now visible to the author instead of silently empty.

  • The "outside" test is the existing ctx?.objectName === SYS_APPROVAL_REQUEST_OBJECT comparison. It stays one comparison, written as !== with an early return of the notice, and row is read from ctx.data after it. There is no second copy.
  • Off a request page (another object's record page, or a page with no record context at all): the notice. On a request page whose row has not loaded yet: null, as before. On a request page with its row: the panel, unchanged.
  • One new language-pack key, approvalsInbox.decisionPanelOffRequestPage, in all ten packs. No other key.
  • Not here: the registration, the index import, the known-types line and the zod arm, which belong to objectui#12045's page-mount step. No lint, save-door or schema rule (triage's choice).
  • The module docblock's "renders nothing" sentence now describes the notice.

Changesets: @object-ui/i18n minor (one new pack key, so the exported en pack and the TranslationKeys type read off it gain one member) and @object-ui/app-shell patch, in .changeset/12072-decision-panel-notice.md.

Zone 2 hypotheses, measured

  1. How the sibling renderers translate. The approval module's siblings, DecisionProgressIndicator and RecordApprovalsPanel, call useObjectTranslation from @object-ui/react and read approvalsInbox.* rows by literal key, with an inline defaultValue. The notice does the same: t('approvalsInbox.decisionPanelOffRequestPage', { defaultValue }). The default equals the en value, which check:i18n-keys verifies. The key is approvalsInbox.decisionPanelOffRequestPage.
  2. The notice's look. No notice component exists for a record block's "not applicable here" state. The nearest sibling state is record:path's "no stages configured" placeholder (plugin-detail record-path.tsx, the stages.length === 0 branch), a muted, italic, dashed-border box: text-xs text-muted-foreground italic px-3 py-2 border border-dashed rounded. The notice reuses that class string verbatim; record:path's copy is a literal, this one comes from the packs. Nothing under packages/components/src/ui/** is touched. The node's className and designer attributes stay on the wrapper, as they do on the panel.
  3. No record context. Outside a provider useRecordContext() returns null, so ctx?.objectName is undefined, which is not the request object, and the notice draws. That matches the card ("any page other than an approval request's") and the claim ("the context names another object, or none"). Pinned below.

On the real request page, RecordDetailView's RecordContextProvider names the object from the route on its first render, so no notice flashes while the request row loads. The console's approvalRequestsDataSource.decisionPage.test.tsx, which mounts the real request page with the renderer registered in-test, is 7/7 green on this head.

Pins (ApprovalDecisionPanel.test.tsx)

The claim's four, plus the no-context case and a designer-attribute check:

  • the block on a non-request record page renders the notice, whose text is the en pack value, with no panel and no actions bar;
  • the block with no record context at all renders the same notice;
  • the block on a request page renders the panel, and no notice;
  • the notice string is in the i18n bundle: all ten packs define the key with a non-empty value, a zh provider renders the zh pack value, and a control shows an en provider renders the en value and that the two packs differ;
  • on a request page whose row is not yet loaded, the node still renders nothing;
  • the notice keeps the node's className, data-obj-id and data-obj-type.

Gates, on head 7d10b7c05

  • pnpm exec vitest run packages/app-shell/src/views/approval-decision/: 1 file, 11 passed.
  • pnpm exec vitest run apps/console/src/services/approvalRequestsDataSource.decisionPage.test.tsx: 1 file, 7 passed.
  • pnpm exec vitest run packages/i18n/: 82 files, 1,316 passed, 13 skipped.
  • pnpm --filter @object-ui/app-shell type-check and pnpm --filter @object-ui/i18n type-check: exit 0, both script names echoed. They ran after turbo run build --filter='@object-ui/app-shell^...' (28 tasks), so the types are read from fresh dist builds. tsc -p tsconfig.test.json --listFilesOnly lists the panel test, so the test is type-checked too.
  • pnpm exec eslint on the 12 touched code files: 0 errors, 0 warnings.
  • Exit 0: check:i18n-keys, check:i18n-drift ("0 en value(s) changed (1 key(s) added …)"), check:i18n-dead-keys (the new key is not listed), check:i18n-designer-parity, check:control-bytes, check:test-path-roots, check:changeset-claims, check:pending-changeset-literals, check:new-line-citations, check:vi-mock-inherit, check:action-forward-parity and check:installed-pin-claims. Also exit 0: the changeset presence, no-major, fixed and overwrite scripts.
  • The eslint run covered only the touched files. Three facts show that this narrowing excludes nothing:
    • the population is eslint.config.js's files: ['**/*.{ts,tsx}'], which covers all 12 code files;
    • the JSON output counts 12 files;
    • the config has no parserOptions and no projectService, and no rule under eslint-rules/ reads the filesystem. Linting is therefore per-file, and this diff cannot move a verdict on any untouched file.

Ablation (predictions written before either leg ran)

Both legs ran through ablation-replace in wrap mode. Each anchor hit exactly once, and each mutation was confirmed on disk by the anchor count and a blob change. After each leg, the file's blob equals its HEAD blob and git diff HEAD is empty. The test resolves @object-ui/i18n to source through the vitest alias, so no dist is in the path.

  • Leg A: the renderer returns null off a request page again (if (ctx?.objectName !== SYS_APPROVAL_REQUEST_OBJECT) return null;, with the notice block made dead code). Predicted 5 failed and 6 passed; observed 5 failed and 6 passed, the same five: both notice cases, the designer-attribute check, the zh provider and the en control.
  • Leg B: the key's line deleted from the zh pack. Predicted 2 failed and 9 passed; observed 2 failed and 9 passed: "all ten packs define the key" and "the zh provider renders the zh value" (it fell back to en).

First-load bytes

No entry imports the panel module: git grep for ApprovalDecisionPanel outside tests returns zero hits, and as a control the console test does import it. The en catalogue is in the eager closure, though, so the new en value adds bytes there. At the source level, en.ts grows by 102 B raw and 36 B gzip -9. check:eager-closure is NOT MEASURED here, because it needs a full console build; the CI Bundle Analysis job runs it.

Acceptance notes

  • objectui#12045's pending changeset (.changeset/12045-approval-decision-panel.md) says that outside a sys_approval_request record page the panel "renders nothing". If both changesets release together, this change makes that clause false. That file is outside this claim's file surface and is not edited here. Instead, this PR's changeset says it replaces that clause. The seat may add an append-only "Superseded in part" paragraph there, as amendment 4 did for objectui#12032.
  • The dispatch reading "the notice should move no first-load bytes" holds for the renderer code, but not for the en value; see First-load bytes above.

NOT MEASURED, left to CI: the full app-shell suite (per the dispatch), check:eager-closure and check:eager-locale-catalogues (both need a console build; no catalogue is added, so the eager composition is unchanged by construction), and the repo-wide pnpm lint.

Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 (the dispatching seat's session; this dev ran as its subagent).


Generated by Claude Code

…off an approval request page (objectui#12072)

Off a sys_approval_request record page (another object's record page, or
a page with no record context) the record:approval_decision renderer now
draws a short notice that it works only on approval request pages,
instead of null. The outside test is the existing objectName comparison,
kept as one test. On a request page whose row has not loaded it still
draws nothing; with its row it draws the panel unchanged.

One new language-pack key, approvalsInbox.decisionPanelOffRequestPage,
in all ten packs. The panel stays module-internal: no registration,
index import, known-types line or zod arm.

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

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 290 chunks) 3168.0 KB 3204.6 KB
Main entry chunk (gzip) 73.7 KB 350 KB
Entry file index-BvRmWNQP.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) 19.75KB 7.29KB
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.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
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) 587.83KB 141.44KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 269.42KB 68.20KB
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) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.43KB 15.54KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.52KB 47.99KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.53KB 49.80KB
plugin-detail (index.js) 249.09KB 65.63KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.11KB 45.88KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 249.62KB 69.16KB
plugin-kanban (index.js) 52.77KB 16.56KB
plugin-list (index.js) 120.10KB 30.28KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.06KB 11.80KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.93KB 23.24KB
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.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
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-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

…ctui#12072

Append-only rider on the pending objectui#12045 changeset: outside a
sys_approval_request record page the decision panel now draws a localized
notice instead of nothing, so that entry's "renders nothing" clause no
longer holds when both changes release together. Front matter and every
existing line unchanged (claim amendment 6090288136).

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

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 290 chunks) 3168.0 KB 3204.6 KB
Main entry chunk (gzip) 73.7 KB 350 KB
Entry file index-BvRmWNQP.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) 19.75KB 7.29KB
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.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
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) 587.83KB 141.44KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 269.42KB 68.20KB
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) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.43KB 15.54KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.52KB 47.99KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.53KB 49.80KB
plugin-detail (index.js) 249.09KB 65.63KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.11KB 45.88KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 249.62KB 69.16KB
plugin-kanban (index.js) 52.77KB 16.56KB
plugin-list (index.js) 120.10KB 30.28KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.06KB 11.80KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.93KB 23.24KB
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.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
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-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

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 043680ff85ef50f1bfd8931056ea16adca4f3f6a
Local-runs: none

① Derived judgments

Read, and nothing else: card objectui#12072 (body, claim 6090014515, dev report 6090270186, claim amendment 6090288136), PR objectui#12073 (body, its 14 files, the diff against main at c0c0a0d5, which is still main's tip: the head is 2 ahead and 0 behind, so the PR diff is the net diff), and the 42 check-runs plus one commit status on the head, the last of which completed 2026-10-09T22:47:10Z. Nothing was built, run or re-run.

Public surface, @object-ui/i18n: WIDENED. Right. One key, approvalsInbox.decisionPanelOffRequestPage, is added to each of the ten packs under packages/i18n/src/locales/. The package entry exports en (export { default as en } from './locales/en.js') and TranslationKeys is typeof en, so the exported en object and the TranslationKeys type each gain one member; the nine other packs publish through the ./locales and ./locales/* export doors and gain the same key. Nothing is removed or renamed, and no existing value changes (the dev's check:i18n-drift reads "0 en value(s) changed, 1 key(s) added"). Additive only.

Public surface, @object-ui/app-shell: UNCHANGED. Right. The renderer stays module-internal: packages/app-shell/src/index.ts at the head imports nothing under views/approval-decision/, and the package's exports map is . and ./styles.css only. The pin "loading it registers no component type" is kept. The one new import, useObjectTranslation, is an existing export of @object-ui/react (its src/index.ts at the head), a dependency app-shell already carried for useRecordContext, so there is no new dependency edge. The ApprovalDecisionRendererProps interface is untouched.

Accept-set: UNCHANGED. Right. No registration, no src/index.ts side-effect import, no known-types line, no zod arm, and no lint, save-door or schema rule anywhere in the diff: what a parser, validator or save door accepts is exactly what it accepted on main. That is the card's "no new lint or save-door rule" and the claim's "not on it" list; DeclaredActionsBar, DecisionProgressIndicator and packages/components/src/ui/** are untouched.

Renderer behaviour against the card's acceptance: right on every clause.

  • Off a sys_approval_request record page the node now returns a wrapper carrying the node's className and designer attributes around a muted dashed box whose copy is t('approvalsInbox.decisionPanelOffRequestPage', { defaultValue }), the defaultValue equal to the en value. The "outside" test is the existing ctx?.objectName !== SYS_APPROVAL_REQUEST_OBJECT comparison with an early return: one comparison, no second copy, and row is read from ctx.data after it.
  • "None" (no record context, useRecordContext() null) also draws the notice, which the card's "any page other than an approval request's" and the claim's "another object, or none" both ask for.
  • On a request page whose row is null, not an object, or has no id, it returns null as before; with its row it mounts ApprovalDecisionPanel unchanged.
  • useRecordContext and useObjectTranslation are called unconditionally before the early return, so hook order holds.
  • The module docblock's "renders nothing" sentence follows the behaviour.

Pins, ApprovalDecisionPanel.test.tsx: the card's three and the claim's fourth all present, plus two. The notice on another object's record page; the notice with no record context; the panel and no notice on a request page; the key is a non-empty string in all ten packs, a zh provider renders the zh value, and an en provider renders the en value with the two packs differing (control); null on a request page whose row has not loaded; and className, data-obj-id and data-obj-type kept on the notice wrapper. The former it.each that pinned "renders nothing on another object" is rewritten into the notice pin, not dropped.

Check-runs on the head: every gate family concluded success. Lint; Type Check; Build & E2E; Test shards 1/8 through 8/8 and Test; Test (dist pins); Build Docs; Bundle Analysis (Console Performance Budget PASS, eager closure 3168.0 KB of 3204.6 KB); README Export Check; Changeset Declaration; Changeset Claim Re-read; Changeset Bump Policy; Changeset Fixed Group Check; Changeset Overwrite Report; Governed Surface Queue Guard; Line Citation Gate; Control Byte Scan; Inert vi.mock Specifier Check; Pre-Install Import Graph Check; Spec Main Shape Gate; Action Ref Convention; Shell Escape Residue Scan; the doc and skill checks; label; Live E2E (informational). Three concluded skipped by their own conditions (Test (coverage), the coverage shard matrix, dependabot). None failed, none neutral, none cancelled. The one commit status, Vercel, is success ("Canceled by Ignored Build Step").

② Semver level

Clause-②: yes

.changeset/12072-decision-panel-notice.md declares @object-ui/i18n: minor and @object-ui/app-shell: patch, with a "Clause-②: yes (widening)" paragraph naming the one key. That matches what the diff publishes: the i18n widening in ① is additive on a published surface (en, TranslationKeys, the ten packs), which is minor; app-shell changes the behaviour of an unexported module and no export, which is patch. Nothing is removed or changed in meaning, so major is not in question. Both packages sit in the changeset fixed group (.changeset/config.json), so the group bumps together. The card's Clause-②: no was overridden to yes by the claim 6090014515 on the precedent of objectui#12047, the amendment 6090288136 restates the grades, and the PR body's Clause-②: yes agrees. The dev's check-changeset-presence read 12 source files of 2 released packages against 1 changeset, and the Changeset Declaration, Bump Policy and Fixed Group check-runs concluded success.

The second changeset file, .changeset/12045-approval-decision-panel.md, is objectui#12045's pending entry: the diff appends one blank line and one "Superseded in part (objectui#12072)" paragraph at the end, 2 additions and 0 deletions, front matter and every existing line unchanged, which is exactly the append-only shape the amendment 6090288136 ruled (answer A). Its declared packages are unchanged, and the Changeset Overwrite Report check-run (report-only) concluded success.

③ Boundary flags

  • open_questions[0], the stale "renders nothing" clause in objectui#12045's pending changeset: ANSWERED by the seat, amendment 6090288136, answer A; implemented on this head by the second commit, the append-only rider verified in ②. Closed.
  • out_of_scope_findings[0], the same clause: the same answer. Closed.
  • check:readme-exports not run locally: the README Export Check check-run on the head concluded success. Closed.
  • First-load bytes ("the notice should move no first-load bytes"): the renderer code is reached by no entry, and the en value adds about 36 B gzip to the eager en catalogue, as the dev flagged. Bundle Analysis on the head concluded success, eager closure 3168.0 KB against a 3204.6 KB budget. An en key cannot be added without that growth, and no catalogue is added. Closed, nothing to escalate.
  • NOT MEASURED locally (the full app-shell suite, check:eager-closure, check:eager-locale-catalogues, repo-wide pnpm lint): Test shards 1/8 through 8/8 and Test, Bundle Analysis, and Lint concluded success on the head. Closed.
  • The dev's eslint run covered the 12 touched files only, with a narrowing argument: the Lint check-run on the head is the repo-wide verdict, success. Closed.
  • Gates and pins beyond the claim's list: additive, all green. Noted.
  • The commit trailer form (the repo's model-free trailer pair rather than the harness reminder's) and the PR footer form: a process note consistent with the repo's AGENTS.md and with this record's own rule against naming a model. Noted, not a contract matter.
  • The ablations ran objectstack's ablation-replace.mjs against the objectui worktree: a tooling note; predictions were written before each leg, both legs matched, and each restore was proven by blob equality. Noted.
  • The dev report 6090270186 and the PR body's gates section name head 7d10b7c05; this record is on 043680ff, whose only addition over 7d10b7c05 is the 2-line changeset rider, and the check-runs read above are on 043680ff. Nothing to escalate.
  • The PR is still a draft, as the dev filed it; marking it ready is the seat's act, not this record's. Noted for the seat.
  • No write was refused by the dev, and no permission check refused any step of this review.

Implemented-by: claude/issue-12072-decision-panel-notice
Reviewed-by: session_01CGZy1BGCjdN5cXqL9cnvB8

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 9, 2026 22:56
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 9, 2026 22:56
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit 20c6d35 Oct 9, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12072-decision-panel-notice branch October 9, 2026 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

console: a record:approval_decision block placed on any page other than an approval request's renders nothing — draw a visible notice instead of null

2 participants