Skip to content

fix(components,plugin-detail,app-shell,i18n): a record action disabled by its predicate says why it is unavailable (objectui#11811) - #11841

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-11811-disabled-action-reason
Oct 8, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-11811-disabled-action-reason

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11811

Clause-②: yes

A record action greyed out by its declared disabled predicate now says why. It shows the generic reason "Not available for this record" as a tooltip, on hover and on keyboard focus, and the same text is its accessible description (aria-describedby). Before, the button carried no tooltip, no title and no description.

Where the card's defect actually lives

The claim's original Responsibility line named DeclaredActionsBar as the code that draws the showcase Task page's Archive. It does not: DeclaredActionsBar renders only sys_approval_request actions (record page approvals bar, approvals inbox). Measured on objectstack main (the card's 879bd38c is its ancestor), the Task page (showcase_task_detail, kind: 'full') draws Archive in two other renderers:

  • the header: a page:header node whose actions are ids, drawn by PageHeaderRenderer's renderButton (@object-ui/components);
  • the section bar: record:quick_actions at location: 'record_section', drawn by QuickActionButton (@object-ui/plugin-detail).

The seat recorded the correction in claim amendment 6049093782 and added the header file to the surface. DeclaredActionsBar has the same defect class, so it gets the same fix.

What changed

  • page:header (packages/components/src/renderers/layout/containers.tsx): the inline button gets a tooltip trigger plus an sr-only description. In the ⋯ overflow menu the reason is a visible second line under the label and is the item's aria-describedby; the item's name stays the label (aria-labelledby). A tooltip there would be out of keyboard reach: the menu's roving focus skips a disabled item (focusable: !disabled) and the menu traps Tab. Only an authored action's declared disabled gets the reason. The host-injected Edit and Delete (RecordContext.headerSystemActions, a host-computed boolean) and the inline-edit lock are unchanged. Authored and host actions are told apart by key, from the dedupe that lets an authored action win.
  • record:quick_actions (QuickActionButton) and DeclaredActionsBar: the same tooltip trigger and description on the inline button.
  • One new key, actions.notAvailableForRecord, with real translations in all ten packs.

Why a wrapper span. A natively disabled button fires no pointer or focus events, and the Button primitive adds disabled:pointer-events-none, so a tooltip or a native title on the button itself never opens. The span is the tooltip trigger, the pattern Radix documents for a disabled trigger. It takes the hover, and tabIndex={0} lets a keyboard user focus it. Both the button and the span carry aria-describedby pointing at a persistent sr-only copy, so a screen reader gets the reason with the tooltip closed. The repo had no disabled-tooltip pattern. Its title-on-disabled-Button sites cannot show at all; a Chromium probe found that the hit target at a pointer-events: none disabled button's centre is its parent.

Not changed: a button greyed out only while its own action runs (spinner) shows no reason. No export, prop or type member is added. The author-written reason beside the predicate is a spec key for objectstack, not part of this PR.

Tests (head e165c34)

  • New pins, three files: page-header-disabledReason-11811.test.tsx (7), record-quick-actions.disabledReason-11811.test.tsx (7), DeclaredActionsBar.disabledReason-11811.test.tsx (7).
    • Disabled by the predicate: the button is disabled and described by the reason; hover opens role=tooltip; Tab reaches the trigger, which opens it.
    • Controls: a predicate that does not hold gives no reason; a running or loading button gives no reason; the header's host Edit and Delete with disabled: true give no reason.
    • Overflow: the item shows the reason and is described by it.
    • Text: the zh pack and the en pack.
    • Fixture: the predicate is the served CEL envelope. The bare string goes to the legacy evaluator, where has() faults and a fail-soft disabled greys out both rows, so it would pin the wrong reason.
  • vitest run packages/components/ plus the 36 app-shell, console and example files that name page:header, PageHeaderRenderer or headerSystemActions, at e165c34: Test Files 401 passed | 1 skipped (402).
  • vitest run packages/plugin-detail/ packages/i18n/ plus the 27 app-shell and console files that name DeclaredActionsBar, record-quick-actions or the key, at e2a498f: Test Files 356 passed | 1 skipped (357). e165c34 changes no file in those packages.
  • type-check: @object-ui/components exit 0 at e165c34; @object-ui/i18n, @object-ui/plugin-detail and @object-ui/app-shell exit 0 at e2a498f, on dists rebuilt from this branch.
  • Ablations, run after commit through objectstack's scripts/ablation-replace.mjs: the anchor if (!disabledReason) return button; is replaced by an unconditional return button;.
    • Header: Tests 4 failed | 3 passed (7). Red: description, hover, focus, zh. Green: overflow and both controls.
    • Quick actions and DeclaredActionsBar: Tests 10 failed | 4 passed (14). Red: description, hover, focus, zh, en on each. Green: the controls.
    • Each restore was proven: blob equals HEAD and git diff HEAD is empty.
  • Gates at e165c34, all exit 0:
    • i18n: check:i18n-keys, check:i18n-drift (1 key added, no en value changed), check:i18n-dead-keys (report; the new key is not a candidate), check:i18n-designer-parity;
    • changeset: check-changeset-presence (4 released packages, 1 changeset), check-changeset-no-major, check-changeset-fixed, check:changeset-claims, check:pending-changeset-literals;
    • other: check:control-bytes, check:test-path-roots, check:new-line-citations (0 new); eslint on touched files (0 errors).
  • Not run locally: the full app-shell suite. The named-file sets above cannot see a suite that draws these renderers through a page tree without naming them; CI runs the full farm.

Changeset

.changeset/11811-disabled-action-reason.md: @object-ui/i18n and @object-ui/components are minor, @object-ui/app-shell and @object-ui/plugin-detail are patch. The fixed group publishes all of them at minor, never major. The body names the three surfaces and the one key.

Acceptance notes

The session behind this change is https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8.


Generated by Claude Code

claude added 4 commits October 7, 2026 22:42
… why it is unavailable

A declared action greyed out by its `disabled` predicate now carries the
generic reason "Not available for this record" as a tooltip (on hover and
on keyboard focus, through a focusable wrapper span, the Radix idiom for a
disabled trigger) and as a persistent accessible description
(`aria-describedby` onto an `sr-only` copy). A button disabled only while its
own action runs stays as it was.

Surfaces: `record:quick_actions` (plugin-detail, the record section bar) and
`DeclaredActionsBar` (app-shell). One new key, `actions.notAvailableForRecord`,
in all ten language packs.

Part of objectui#11811.

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
…r, focus and description; add the changeset

Both surfaces: the predicate-disabled action shows the reason on hover and on
keyboard focus and is described by it; a predicate that does not hold and a
button greyed out only while it runs show none; the text comes from the en and
zh packs.

Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8
Co-authored-by: Claude <noreply@anthropic.com>
…e served CEL envelope

The bare predicate string takes the legacy evaluator, where `has()` faults and
a fail-soft `disabled` greys the action out on both rows; the pins now use the
`{ dialect: 'cel', source }` shape the spec normalizes the authored string to,
so the disabled case and its control each reach a real verdict.

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

The record header's inline action button gets the same reason as the other
two surfaces: a tooltip on hover and keyboard focus through a focusable
wrapper span, plus a persistent sr-only description. In the overflow menu the
reason is a visible second line and the item's description, because the menu
skips a disabled item and traps Tab, so a tooltip there is out of keyboard
reach. Only an authored action's declared `disabled` earns it; the host's Edit
and Delete (a host-computed boolean) and the inline-edit lock are unchanged.
The changeset now names @object-ui/components and the three surfaces.

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

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 336 chunks) 3523.7 KB 3551.8 KB
Main entry chunk (gzip) 157.0 KB 350 KB
Entry file index-Co8futYQ.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) 17.82KB 6.58KB
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) 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.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) 581.21KB 139.87KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 235.41KB 65.46KB
fields (index.js) 266.88KB 67.46KB
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) 38.37KB 10.31KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.50KB 11.82KB
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.39KB 15.52KB
plugin-charts (index.js) 84.26KB 23.05KB
plugin-chatbot (index.js) 199.63KB 47.46KB
plugin-dashboard (index.js) 144.82KB 39.17KB
plugin-designer (index.js) 231.46KB 48.87KB
plugin-detail (index.js) 248.57KB 65.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 176.62KB 45.75KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 239.75KB 65.92KB
plugin-kanban (index.js) 52.17KB 16.37KB
plugin-list (index.js) 116.85KB 29.12KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.10KB 11.81KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.07KB 22.93KB
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) 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.08KB 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.25KB
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: e165c3492feca7f880651fc7dba374e6e47c5f39
Local-runs: none

Inputs read: card #11811 (body and all five comments: triage 6043323597, claim 6048244335, dev report 6049038332, claim amendment 6049093782, dev addendum 6049354721), PR #11841 (body, 17-file list, net diff main...head at the head above: merge-base 32124ac, the claim's base commit; 4 commits, +751/-28), and the 43 check-runs on the head: 40 success, 3 skipped by design (Test (coverage), its coverage shard matrix, dependabot), 0 failure, 0 in progress; the one commit status (Vercel, ignored build step) success. Head repo is the base repo. The four package manifests, .changeset/config.json, packages/i18n/src/index.ts and the three edited sources were read at this head with gh api only; nothing built, run or re-run. No governed path in the file list (.claude/**, skills/**, docs/adr/**, AGENTS.md, CLAUDE.md untouched), so this review is owed by the claim's Clause-②: yes (6048244335, standing as amended in 6049093782) alone. No earlier ## Contract review record exists on the PR or the card.

① Derived judgments

Accept-set changes (what an end user on a record page gets):

  1. The three renderers that draw a declared action — page:header's inline button and its ⋯ overflow item (PageHeaderRenderer, @object-ui/components), record:quick_actions' QuickActionButton (@object-ui/plugin-detail) and DeclaredActionsBar's button (@object-ui/app-shell) — give a button disabled by its declared disabled predicate the generic reason "Not available for this record", as the triage direction asks: a tooltip on hover and on keyboard focus, plus a persistent accessible description (aria-describedby onto an sr-only copy). RIGHT. Each surface is pinned on every arm (description, hover, Tab focus, and the pack text through a real I18nProvider), with the predicate in the served { dialect: 'cel', source } envelope so the disabled case and its control each reach a real verdict; the first fixture's bare string had gone green for the wrong reason (fail-soft), and the dev caught and fixed it in e2a498f.
  2. Scope of the reason: only the DECLARED predicate's verdict (disabledByPredicate on the two bars; disabledReasonFor on the header, which requires the key to be in authoredKeys and resolveDisabled to hold). A button greyed out only while its own action runs (loading / running), the header's inline-edit lock (disableDuringInlineEdit) and the host-injected Edit / Delete (RecordContext.headerSystemActions, a host-computed boolean) give no reason. RIGHT against amendment 6049093782 ("only an authored action's declared disabled predicate"; host Edit and Delete unchanged), and every exclusion is pinned by a control. The authored/host split is read off the plan memo's dedupe, where authored entries enter first and win on a key clash, so authoredKeys is exact for the surviving list.
  3. resolveDisabled is unchanged: a boolean is honoured as written, a string or envelope is evaluated with fallback: true (objectui#11212's closed fail direction), an absent or empty gate is "not disabled". So an authored disabled: true literal and a faulting predicate both earn the reason. RIGHT: both are declared, and the action is unavailable either way; the faulting case is the Acceptance-notes residual in ③.7.
  4. The wrapper: a span with tabIndex={0} as the Radix TooltipTrigger around the natively disabled Button, the idiom Radix documents for a disabled trigger; the Button primitive's disabled:pointer-events-none is why a tooltip or a title on the button itself could never open (the card's "hovering or focusing it shows nothing"). RIGHT, and it is why the repo's one existing idiom (title on a disabled Button) could not be reused. The span carries data-disabled-reason="" as the test hook; a DOM marker, not a prop or type. The new tab stop exists only while the reason applies (control: no wrapper when the predicate does not hold).
  5. The overflow item: a visible second line under the label (text-xs text-muted-foreground) that is also the item's aria-describedby, with aria-labelledby keeping the name on the label alone. RIGHT as a reading of the direction's "tooltip and accessible description": the installed @radix-ui/react-dropdown-menu (^2.1.24 in components' manifest) skips a disabled item in roving focus and traps Tab, so a tooltip there would be out of keyboard reach; an always-visible reason inside an already-opened menu is the stronger form. Pinned (item data-disabled, described by a visible element, name stays the label).
  6. headerActionsRaw becomes headerActionPlan.actions, with authoredKeys beside it. RIGHT and internal: the memo is a closure inside PageHeaderRenderer's body, headerActionsRaw keeps its name for its consumers, and the memo's dependency list is unchanged.
  7. No REST path, query set, metadata schema, spec key or @objectstack/* import changes; nothing narrows. RIGHT. The claim's two ⛔ hold: no file under packages/components/src/ui/, and no author-written reason key (the changeset and all three sources say it is objectstack's to declare).

Public-surface changes (what a published package's accept set gains or claims):

  1. @object-ui/i18n: one new key, actions.notAvailableForRecord, in all ten packs, under the existing actions namespace beside notAvailableHere (which means "not on this page", so the claim's reading that no existing key fits is right). PUBLIC (./locales and ./locales/* are in the package's exports) and RIGHT: additive only; the key carries no placeholder in any pack, so placeholder parity holds trivially; the nine non-en packs are translations, not copies; the en comment carries no model identifier. Lint, which runs the i18n gates, is success on the head.
  2. @object-ui/plugin-detail: record-quick-actions.tsx newly imports useSafeTranslate from @object-ui/i18n and the Tooltip family from @object-ui/components. RIGHT on the manifest: @object-ui/i18n is already in the package's dependencies (workspace:*), @object-ui/components is in its peerDependencies and devDependencies, and useSafeTranslate is exported from i18n's root entry (src/index.ts). No export, prop or type member is added; the default export is unchanged.
  3. @object-ui/components: no export, prop or type member is added; the Tooltip family and useSafeTranslate were already imported in containers.tsx; @radix-ui/react-tooltip and @object-ui/i18n are existing dependencies; the @objectstack/spec floor (^17.0.0) correctly does not move, because no spec symbol is newly imported anywhere in the diff. The floor gap the first record on fix(plugin-list,components,i18n): list filter starts on the first visible column, hides hidden fields, one empty check where empty and null match the same records (objectui#11810) #11836 caught does not recur here. RIGHT.
  4. @object-ui/app-shell: DeclaredActionsBar.tsx adds the Tooltip family from @object-ui/components (an existing dependency); t is the component's existing useObjectTranslation. The package exports . and ./styles.css only; nothing in its published face changes. RIGHT.
  5. No existing string, key, export or type member is removed or renamed; no migration text and no ADR-0087 disposition is owed. RIGHT.

Nothing in the diff is judged wrong.

② Semver level

  • .changeset/11811-disabled-action-reason.md declares @object-ui/i18n minor, @object-ui/components minor, @object-ui/app-shell patch, @object-ui/plugin-detail patch. All four are in the one fixed group of 40 (.changeset/config.json at this head), so the group versions together at minor; no major (Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Fixed Group Check, Changeset Overwrite Report all success). skip-changeset would be wrong here and is not used.
  • The level matches what the diff publishes: the i18n key is the public addition that makes the clause yes (①.8) and takes minor; components' minor is the amendment's own instruction ("joins the changeset under the fixed group's minor") and the console(marketplace): Installed Apps shows a package the runtime refused to load as installed — render the listing's not-loaded marker (the console half of objectstack-ai/objectstack#21822) #11645 / list filter builder: "Add filter" defaults to the hidden Organization field, lists hidden system fields first, and offers both "Is empty" and "Is null" #11810 precedent, though its code adds no surface (①.10); patch is right for app-shell and plugin-detail, whose parts are behaviour with no surface change.
  • The Clause-②: line: the PR body carries Clause-②: yes at the start of line 3; the changeset body writes Clause-②: yes (widening), the optional arm, and it is the right arm: additive, nothing narrows. It matches the claim (6048244335) as it stands after the amendment (6049093782, which re-affirmed it before the PR opened) and matches the diff.
  • The changeset body names the behaviour, the three surfaces, the one key and the card, says what stays unchanged, and carries no model identifier. Not breaking, so no FROM-to-TO mapping is owed.

③ Boundary flags

From report 6049038332:

  1. open_questions[0] — the header half (containers.tsx) is outside the claim's File surface; A (amend the surface, same local idiom, no new export) / B (hoist and export one wrapper) / C (rule the header out). The seat answered A (6049093782). I judge A right: C would ship a fix the card's own reproduction still fails on, and B's new public export is the family card's decision. This head implements A: the header file and its test (packages/components/src/__tests__/) are in the 17 files, and no export, prop, type member or second key appears. The sub-question (should the host's Edit / Delete get the reason?) was answered no, and ①.2 shows it held. Closed.
  2. deviations — no PR opened in round 1, under the stop-on-breach rule: the rule worked as written (report first, surface amended, then the PR). Process; closed.
  3. deviations — the changeset front matter (i18n minor, the two bars patch): judged in ②. Closed.
  4. deviations — lock refusals and queue time-outs NOT MEASURED locally: the head's check-runs are the verdicts (Test (shard 1/8) through (8/8), Test, Test (dist pins), Type Check, Lint, Build & E2E all success). Closed.
  5. out_of_scope_findings — the action:button / action:icon / action:group / action:menu family: filed by the seat as objectui#11839 (6049093782); this diff leaves packages/components/src/renderers/action/* untouched. Closed.
  6. out_of_scope_findings — InlineEditSaveBar's title on a disabled Button cannot show (pointer-events), carrier none, kept in the PR's Acceptance notes; the approval-lock band shows that reason on screen. The dev correctly did not file. ESCALATED to the dispatching seat to decide whether ONE card is filed: the mechanism is the one this PR measured in Chromium, so the finding is grounded, but it is outside this card's step. Not a condition.
  7. Acceptance note — a faulting (fail-soft) predicate also shows the generic reason: ACCEPTED as a bounded residual, see ①.3; the sentence is literally true (the action is unavailable), and per the comment on resolveDisabled the fault is already reported once on its own path.

From report 6049354721:
8. deviations — the overflow item gives the reason as a visible line, not the tooltip wrapper the amendment's "same local wrapper" names: ACCEPTED, see ①.5. The deviation is measured (the menu's roving focus and Tab trap in the installed version), reported in the report, the PR body, the changeset and the code comment, adds no surface, and the triage's "accessible description" half holds exactly. Closed.
9. deviations — the plan memo's shape ({ actions, authoredKeys }): internal, see ①.6. Closed.
10. deviations — worktree removed: not a boundary.
11. "NOT RUN locally: the full app-shell suite; CI owns it" and "CI in_progress, not waited on": CLOSED BY CI. All 43 check-runs completed on this head, 0 red, 0 pending; the console performance-budget comment on the PR (6049386776) reads the eager closure at 3523.7 KB against a 3551.8 KB budget, PASS.
12. The PR's Acceptance note on the overlap with #11836 (same ten packs; "whoever lands second merges main"): #11836 merged at 2026-10-08T00:09:43Z (1e1f09e, now main's head), so this PR is the second-lander. Its pack hunks sit in the actions block (en.ts near line 190); #11836's sit in the filterBuilder block (near lines 3900 to 4400). Read after that merge, GitHub reports this head mergeable: true, state clean, against the current main, so there is no textual conflict and nothing for the dev to resolve. The check-runs above ran before #11836 landed; the merge-group run on the combined tree owns that re-check, and nothing in this diff touches what #11836 changed. Not a condition of this verdict.
13. The claim's serial note on objectui#11810 / #11836 is the same overlap, and report 2's "main moved to 87f7b6c without touching these files, no merge" is answered by the same clean merge state.

Flags the reports did not raise, judged here:
14. Scope against the claim: the 17 files are exactly the File surface as amended — DeclaredActionsBar.tsx, the section bar's renderer the dev named (record-quick-actions.tsx), the ten packs, containers.tsx, the three tests beside them, and the changeset. No file is outside it, no governed path, nothing under packages/components/src/ui/**.
15. Model identifiers: none in the diff, the PR title and body, the changeset, the code comments or the four commits (each ends with the session-URL trailer and the plain Co-authored-by: Claude line).
16. An authored action with neither name nor id gets no reason even when its predicate holds (disabledReasonFor returns early on an empty key). A bounded residual: such an action also escapes the dedupe, so it is malformed metadata, not a shape this card measured. Noted; not a condition.

Nothing left unanswered; nothing further to escalate for this head beyond ③.6.

Implemented-by: claude/issue-11811-disabled-action-reason
Reviewed-by: session_01CGZy1BGCjdN5cXqL9cnvB8

VERDICT: PASS

Rendered 2026-10-08T00:28Z on the inputs named above.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 8, 2026 00:30
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 8, 2026 00:30
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 8, 2026
Merged via the queue into main with commit 455c646 Oct 8, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11811-disabled-action-reason branch October 8, 2026 00:48
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.

record actions: a button disabled by its disabled predicate gives the user no reason — no tooltip, title or description

2 participants