Repository navigation
record actions: a button disabled by its disabled predicate gives the user no reason — no tooltip, title or description #11811
Description
Filing gate ① — product defect with a named location and a reproduction. reach: showcase Task record page: Archive is greyed out in the header and the section bar; hovering or focusing it shows nothing.
Who acts on it: objectui triage → the console / records owner. ⛔ Not a claim. Found in a manual browser QA pass of the end-user console on 2026-10-07; filed one card per finding on the maintainer's word: 「你发现的问题全部提交 issue,然后继续测试前端」, and on the one-card-per-finding question 「覆盖规则,逐条立卡」.
What happens
showcase_archive_task declares disabled: 'has(record.done) && record.done != true', so it stays visible and disabled until the task is done — as designed. But the rendered button carries no title, no tooltip and no aria-describedby, so a user cannot learn why it is disabled or what would enable it.
Expected
A disabled action explains itself: a tooltip (and an accessible description) with an author-supplied reason, or a generic "Not available for this record" when none is given.
Suggested direction (triage to rule)
Render a tooltip on disabled declared actions; consider an optional author-facing reason next to the disabled predicate in the action spec (a spec question for objectstack).
Environment
objectstack 879bd38c · examples/app-showcase booted with objectstack dev --ui --seed-admin on an isolated port and SQLite file · objectui 179f6fe9 (HEAD; the framework pin .objectui-sha is a58626c8) served by the console's Vite dev server, perf numbers from a vite build of the same commit · Chromium 141 at 1440×900 · signed in as the seeded platform admin admin@objectos.ai unless stated.
Duplicate check
- Semantic issue search on objectstack-ai/objectui (open and closed), disabled action button no tooltip reason disabled predicate explanation: 8 hits (0 open, 8 closed).
- Semantic issue search on objectstack-ai/objectui (open and closed), greyed out record action shows no explanation why it is unavailable, disabledReason hover title: 8 hits (0 open, 8 closed).
- Related, not the same defect:
- Row CRUD actions (Edit/Delete) should support per-record visible/disabled CEL predicates #2614 (closed) — introduced the per-record disabled predicate whose greyed-out state carries no reason
- Object-bound actions are hidden by the ADR-0066 D4 capability gate with nothing anywhere saying so — the relay carries
actionsand the button IS drawn (premise falsified); decide therequiredPermissionsobservability contract #7234 (closed) — same 'action gated with no explanation to the user' family, for the capability gate rather than the disabled predicate - disabled 的「已声明」判定还有五处停在 != null(action-icon / action-group ×2 / action-menu / record-quick-actions),#3842 只修了两处 #3849 (closed) — prior disabled-predicate evaluation fixes across the same action renderers; correctness, not the missing reason
- Positive control in the same run: related lists action id refusal notice shown to end users on objectstack-ai/objectui returned related lists: an action id the related object does not define draws a refusal notice above the list for whoever opens the record — an authoring fault shown to end users; scope it to authors/admins #11768: yes.
Dedupe words: disabled action button no reason tooltip · disabled predicate explanation · aria-describedby disabled action
Filed by Claude Code (session session_01D76mrPJrSSdaKRxR2rvrMG) from that QA pass.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 7, 2026
Path: ② the capabilities an end user meets in the app — a record's actions | 缺项 | P3
Triage: first grade,
bug·priority:p3·domain:ui·area:records·pm:queue. Direction: a disabled declared action explains itselfTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T17:34Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in the record-action button renderer in the header and the section bar ⇒
domain:ui; rationale: a Studio or console surface in objectui. Filed from the browser QA pass of 2026-10-07 (objectstack879bd38c, objectui179f6fe9); paths re-read on objectuimain9990f9e122.disabledpredicate is a spec key in objectstack (domain:spec). It is not in this cardClause-②: no. Patch changeset in objectui.objectstack-fleet commentedon Oct 7, 2026
Claim: PM loop round 3
Session:
session_01CGZy1BGCjdN5cXqL9cnvB8Account:
os-support-aiBranch:
claude/issue-11811-disabled-action-reasonWorktree:
objectui-issue-11811Domain:
domain:uiSeat:
domain:ui#3File surface (on
32124ac):packages/app-shell/src/views/DeclaredActionsBar.tsx(the declared action button:isDisabledPredat:180,disabled=at:395), the record header's action bar;DeclaredActionsBar;packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.tsfor the generic reason;.changeset/11811-*.md.⛔ No edit under
packages/components/src/ui/(objectui AGENTS.md #7). ⛔ No new spec key (an author-written reason is objectstackdomain:specwork, as triage says). Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectui: no path-derived mandate; default tier)Clause-②: yes
Responsibility:
objectui app-shell: DeclaredActionsBar renders a declared action disabled by its spec disabled predicate with no title, tooltip or aria-describedby | the platform path: the predicate is evaluated client-side (useCondition), so the bar knows the button is disabled by declaration and not by loading | every end user on a record page whose action is disabled by its predicate (showcase Task: Archive until done)Thread-read: 6043323597
Serial constraints cleared:
noneblocking. Noarea:recordscard is in flight on this file. This seat's objectui#11810 (area:records) holdsplugin-listListView.tsx,componentsfilter-builder.tsx, and two other keys in the same ten locale packs. Both add keys to the packs, and whoever lands second mergesmain. No open objectui PR touchesDeclaredActionsBar.tsx(read 2026-10-07T22:33Z).Why
Clause-②: yes(triage wrotenobefore the string's home was read):DeclaredActionsBartakes its strings from@object-ui/i18n(useObjectTranslation), and no existing key says "not available for this record". The nearest,notAvailableHere, means "not on this page". So the generic reason is a new key in the published packs (./localesis in that package'sexports): an additive widening. So:minor;Clause-②: yesat line start;If the dev finds an existing key that fits exactly, it reports that before the PR, and the seat amends this line back.
Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
os-dev-report
{ "issue": 11811, "status": "blocked", "branch": "claude/issue-11811-disabled-action-reason", "pr": null, "session": "session_01CGZy1BGCjdN5cXqL9cnvB8 (subagent mode: the parent's id)", "premise_still_valid": true, "summary": "The card's defect is real, but the claim's File surface maps it to the wrong renderer, and the header half needs a file outside that surface, so per the claim's stop-on-breach rule this report comes before any PR. Measured on objectstack origin/main (879bd38c is its ancestor, exit 0): the showcase Task page (showcase_task_detail, kind full) draws Archive in TWO renderers, neither of which is DeclaredActionsBar: the header is a page:header node whose actions are ids (rendered by PageHeaderRenderer's renderButton in packages/components/src/renderers/layout/containers.tsx), and the section bar is record:quick_actions (QuickActionButton in packages/plugin-detail/src/renderers/record-quick-actions.tsx, location record_section). DeclaredActionsBar renders only sys_approval_request actions on the record page and the approvals inbox; it is not on the Task page path, so the claim's Responsibility line is wrong about whose code the card measured. Done on the branch (pushed, head e2a498f, three commits on 32124ac): the section bar (named per the claim) and DeclaredActionsBar (same defect class, in surface) now give a predicate-disabled action the generic reason 'Not available for this record' as a Radix tooltip on hover and on keyboard focus, through a focusable wrapper span (Radix's documented disabled-trigger idiom: the repo has no disabled-tooltip idiom, and its title-on-disabled-Button sites cannot work because the Button primitive's disabled:pointer-events-none takes the hover away, measured in Chromium), plus a persistent sr-only accessible description that aria-describedby on both the button and the span points at. A button disabled only by loading/running is unchanged and shows no reason. One new key, actions.notAvailableForRecord, real translations in all ten packs (Clause-2 yes holds: no existing key fits). Not done: the header (page:header renderButton in containers.tsx), which is outside the claim's File surface; that file is not edited. The PM's call is in open_questions.", "tests": "All at HEAD e2a498f unless said. (1) New pins, both surfaces, through the verify lock: 'Test Files 2 passed (2) / Tests 14 passed (14)', VERDICT command-exit 0. Per surface: predicate-disabled button is disabled, aria-describedby names an element reading the reason, accessible name unchanged; user.hover on the trigger opens role=tooltip with the reason; user.tab focuses the trigger, which is described by the reason and opens the tooltip; control predicate false: enabled, no aria-describedby, no trigger, no reason; control running/loading: disabled, no reason; zh pack text and en pack text via a real I18nProvider. The fixture uses the served CEL envelope; the first run with the bare string went red on the predicate-false control, because the legacy evaluator faults on has() and a fail-soft disabled greys out both rows, so the disabled case had been green for the wrong reason. (2) Widened run through the lock: pnpm exec vitest run --maxWorkers=2 packages/plugin-detail/ packages/i18n/ plus the 27 app-shell and apps/console test files that name DeclaredActionsBar, record-quick-actions or the new key (git grep population): 'Test Files 356 passed | 1 skipped (357) / Tests 4252 passed | 21 skipped (4273)', VERDICT command-exit 0. (3) Ablation, after commit, through scripts/ablation-replace.mjs (objectstack) in WRAP mode, both files at once: anchor 'if (!disabledReason) return button;' replaced by an unconditional 'return button;' (wrapper dropped), anchor 1 to 0 and marker 0 to 1 on disk in both files, blobs changed; result 'Tests 10 failed | 4 passed (14)': red are description, hover, focus, zh and en on each surface; green are the two controls on each surface (direction as predicted). Restore proven by the tool: blob after restore == HEAD blob (95b4cbbff921 DeclaredActionsBar, 59c98158e09a record-quick-actions) and git diff HEAD empty; git status clean and marker count 0 afterwards. Sources are imported relatively, so no dist is on this path. (4) Build and type-check through the lock: pnpm turbo run build --filter='@object-ui/app-shell^...' --concurrency=2: 28 successful, 28 total, BUILD_EXIT=0 (dist carries the new key: i18n dist zh.js and en.js, plugin-detail dist index.js); pnpm --filter run type-check for @object-ui/i18n, @object-ui/plugin-detail, @object-ui/app-shell: exit 0 each, script 'tsc --noEmit && tsc -p tsconfig.test.json' echoed; tsc --listFilesOnly shows both new test files inside the test programs. (5) Mechanism probe (Chromium 141 at /opt/pw-browsers, plain DOM with button:disabled pointer-events none): elementFromPoint at a disabled button's centre returns its parent, the button gets no pointerenter; a wrapping tabindex 0 span gets pointerenter and takes Tab focus. Narrowing declared: the full app-shell suite (1071 test files) was not run locally; the 27 files above were chosen by naming the touched modules, which is blind to a suite that renders these renderers through a page tree without naming them. CI runs the full farm.", "mcp_calls": "0", "api_writes": "1 - POST /repos/objectstack-ai/objectui/issues/11811/comments (this os-dev-report, via scripts/pm/post-stamped.mjs). git push x4 to the branch (not REST). No pr_create and no label-write, because no PR is opened.", "open_questions": [ { "question": "The header half of the card (page:header's inline action button, PageHeaderRenderer renderButton in packages/components/src/renderers/layout/containers.tsx) is outside the claim's File surface. How should this card take it?", "options": [ "A: Amend the claim's File surface to add containers.tsx and a test beside it (packages/components/src/__tests__/). A patch round on this same branch applies the same local idiom to renderButton, extends the changeset to @object-ui/components, then opens the draft PR with Fixes objectui#11811. No new export, prop, type member or key. Cost: a third local copy of a 15-line wrapper, and @object-ui/components joins the verification set.", "B: As A, but hoist the wrapper once into packages/components/src/custom/ and export it, used by all three surfaces. One implementation instead of three, but a new public export, so Clause-2 widens beyond the one key and the contract review grows.", "C: Rule the header out of this card. A patch round opens this branch as a PR with Part of objectui#11811 (section bar plus DeclaredActionsBar), and the header goes to its own card." ], "recommendation": "A. Real business need: the header is half of the card's own reproduction on the showcase Task page, so C ships a fix the reporter would still see failing. Long-term soundness: one idiom in three renderers is acceptable at three; if the action:* renderers (out_of_scope_findings) join the family, B becomes the right shape and is better done as that family's closure card than inside a p3. Preventing AI mistakes: A adds no new authoring surface; the key is chrome, not metadata. Startup focus: A widens nothing beyond the one key the claim already declares. Open point for the patch round: the header's host-injected Edit and Delete carry a host-computed boolean disabled (approval lock, userActions predicate). Should they get the generic reason too? The recommendation is to scope the reason to authored actions' declared disabled only, the arm that is a fact about the record by declaration, and leave inline-edit and host booleans alone." } ], "out_of_scope_findings": [ "same family as this card, not filed: action:button, action:icon, action:group and action:menu (packages/components/src/renderers/action/*) evaluate the same spec disabled predicate and render a disabled control with no reason; page:header's overflow DropdownMenuItem does the same for record_more actions. reach: named producer, examples/app-showcase's ArchiveTaskAction doc comment names these renderers as the disabled-predicate wiring it exercises; not measured in a browser. Fold into this card if amended, or into a family closure card. Dedupe words: disabled action no reason tooltip · action:button disabled predicate explanation · aria-describedby disabled declared action", "carrier: none (承接者:无) · noted, not filed: the repo's existing reason idiom, a native title on a disabled Button (InlineEditSaveBar's Save title={locked ? lockedHint : undefined}), cannot show, because the Button primitive's disabled:pointer-events-none means the button never receives hover (Chromium probe above). The code comment there says the approval-lock band shows the reason on screen, so impact is low. Goes in Acceptance notes when the PR opens.", "acceptance note, not a finding: a disabled predicate that FAULTS (fail-soft) also greys the action out and now also shows the generic reason. That is consistent (the action is unavailable either way) but the wording slightly over-claims for a broken predicate." ], "gates": [ { "command": "os-verify-lock: pnpm exec vitest run --maxWorkers=2 (2 new test files)", "exit": 0, "verdict": "Test Files 2 passed (2) / Tests 14 passed (14); VERDICT command-exit 0" }, { "command": "os-verify-lock: pnpm exec vitest run --maxWorkers=2 packages/plugin-detail/ packages/i18n/ + 27 named app-shell/console files", "exit": 0, "verdict": "Test Files 356 passed | 1 skipped (357); Tests 4252 passed | 21 skipped (4273); VERDICT command-exit 0" }, { "command": "ablation-replace x2 (wrapper dropped) + os-verify-lock vitest (2 new files)", "exit": 1, "verdict": "Tests 10 failed | 4 passed (14) under mutation (expected red); ok restored: blob == HEAD and git diff HEAD is empty, for both files" }, { "command": "os-verify-lock: pnpm turbo run build --filter='@object-ui/app-shell^...' --concurrency=2", "exit": 0, "verdict": "Tasks: 28 successful, 28 total" }, { "command": "pnpm --filter @object-ui/i18n run type-check", "exit": 0, "verdict": "tsc --noEmit && tsc -p tsconfig.test.json, no output" }, { "command": "pnpm --filter @object-ui/plugin-detail run type-check", "exit": 0, "verdict": "tsc --noEmit && tsc -p tsconfig.test.json, no output" }, { "command": "pnpm --filter @object-ui/app-shell run type-check", "exit": 0, "verdict": "tsc --noEmit && tsc -p tsconfig.test.json, no output" }, { "command": "pnpm exec eslint (2 sources, 2 tests, 10 packs)", "exit": 0, "verdict": "29 problems (0 errors, 29 warnings), all no-explicit-any" }, { "command": "pnpm check:i18n-keys", "exit": 0, "verdict": "Every in-scope call-site key resolves against the en pack (3298 keys), every literal inline defaultValue matches the value the pack serves" }, { "command": "pnpm check:i18n-drift", "exit": 0, "verdict": "No en value changed in this range. (1 key(s) added)" }, { "command": "pnpm check:i18n-dead-keys", "exit": 0, "verdict": "report-only; actions.notAvailableForRecord not among the 343 candidates (0 hits in its output)" }, { "command": "pnpm check:i18n-designer-parity", "exit": 0, "verdict": "Every en row has a zh row, and every shared row carries the same placeholders." }, { "command": "pnpm check:control-bytes", "exit": 0, "verdict": "check-control-bytes: OK (scanned 7808 tracked text file(s); skipped 85 binary)" }, { "command": "pnpm check:test-path-roots", "exit": 0, "verdict": "check-test-path-roots: OK" }, { "command": "pnpm check:changeset-claims", "exit": 0, "verdict": "No pending changeset names a file this change touches." }, { "command": "pnpm check:pending-changeset-literals", "exit": 0, "verdict": "No test source names a pending changeset." }, { "command": "node scripts/check-changeset-presence.mjs", "exit": 0, "verdict": "14 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s)" }, { "command": "node scripts/check-changeset-no-major.mjs", "exit": 0, "verdict": "No changeset declares a major bump." }, { "command": "node scripts/check-changeset-fixed.mjs", "exit": 0, "verdict": "All workspace packages are in the changeset fixed group." }, { "command": "pnpm check:new-line-citations", "exit": 0, "verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s)" }, { "command": "pnpm check:readme-exports", "exit": null, "verdict": "NOT RUN: no package exports changed" } ], "deviations": [ "No PR opened (the dispatch says PR first line Fixes objectui#11811): the claim's stop-on-breach rule for a file outside the File surface says report before opening the PR, and the header renderer is outside it.", "Changeset front matter: @object-ui/i18n minor (the key), @object-ui/app-shell and @object-ui/plugin-detail patch. The fixed group lifts all three to minor; none is major.", "Worktree /home/user/objectui-issue-11811 is KEPT (built dists, installed) for the expected patch round. Cleanup is owed after the PR opens.", "One lock call was refused by the vitest invocation guard (a file filter inside a directory filter): never entered the test body, NOT MEASURED, rerun without the overlap. Two earlier lock calls exited 99 (queue-timeout behind objectui#11827's full app-shell run), NOT MEASURED." ], "files_changed": [ "packages/app-shell/src/views/DeclaredActionsBar.tsx", "packages/plugin-detail/src/renderers/record-quick-actions.tsx", "packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts (one key each)", "packages/app-shell/src/views/__tests__/DeclaredActionsBar.disabledReason-11811.test.tsx (new)", "packages/plugin-detail/src/renderers/__tests__/record-quick-actions.disabledReason-11811.test.tsx (new)", ".changeset/11811-disabled-action-reason.md (new)" ], "line_budget": "not applicable (no skills/** surface)" }objectstack-fleet commentedon Oct 7, 2026
Claim amendment —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-07T23:42Z. It amends claim6048244335after the dev's report6049038332. The claim stands otherwise.DeclaredActionsBaras the code that draws the Task page's Archive. The dev measured that it does not.showcase_task_detail), the header's Archive is apage:headeraction drawn byPageHeaderRenderer'srenderButton(packages/components/src/renderers/layout/containers.tsx:2070on16cda0b).record:quick_actions, drawn byQuickActionButton(packages/plugin-detail/src/renderers/record-quick-actions.tsx).DeclaredActionsBarrenders onlysys_approval_requestactions. It has the same defect class, so the fix there stands.packages/components/src/renderers/layout/containers.tsx(renderButton, and the header's overflowDropdownMenuItemin the same renderer), and a test beside it.@object-ui/componentsjoins the changeset under the fixed group'sminor.disabledpredicate. The host-injected Edit and Delete, whosedisabledis a host-computed boolean, stay as they are. Then the draft PR opens withFixes #11811.action:button/action:icon/action:group/action:menurenderers (packages/components/src/renderers/action/*) are filed as their own family card, objectui#11839, because whether one shared helper should be exported is that card's decision.Clause-②: yesstands (one new key,actions.notAvailableForRecord, in the ten packs). A contract-review record is owed on the PR's head.Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{ "issue": 11811, "status": "done", "addendum_to": "os-dev-report 6049038332, after claim amendment 6049093782 (open question answered A)", "branch": "claude/issue-11811-disabled-action-reason", "pr": "https://github.com/objectstack-ai/objectui/pull/11841", "head": "e165c34", "session": "session_01CGZy1BGCjdN5cXqL9cnvB8 (subagent mode: the parent's id)", "premise_still_valid": true, "summary": "Header half done as amended. page:header (PageHeaderRenderer in packages/components/src/renderers/layout/containers.tsx): an authored action's declared disabled now gives the generic reason. The inline button uses the same wrapper as the other two surfaces: a focusable span as the tooltip trigger, plus an sr-only description named by aria-describedby on the button and the span. In the overflow menu the reason is a visible second line under the label and the item's aria-describedby, and the item's name stays the label (aria-labelledby). This is a deliberate deviation from 'the same wrapper'. A tooltip trigger inside the menu is out of keyboard reach: the installed @radix-ui/react-menu 2.1.24 skips a disabled item in roving focus (focusable: !disabled) and calls preventDefault on Tab inside the content. Authored and host actions are told apart by key, from the plan memo's dedupe (authored wins): the memo now returns its list plus the set of authored keys. The host's Edit and Delete (headerSystemActions, a host-computed boolean) and the inline-edit lock are unchanged, and a control pins that. No export, prop, type member or key beyond actions.notAvailableForRecord. The changeset adds @object-ui/components (minor) and names the three surfaces and the one key. The draft PR 11841 opened with this card's closing keyword on line 1 and 'Clause-②: yes' at the start of line 3. Its body records that the claim's original Responsibility named DeclaredActionsBar, and that the Task page's real renderers are page:header and record:quick_actions. The PR is assigned to os-support-ai. origin/main moved to 87f7b6c without touching any of these files, so no merge.", "tests": "At head e165c34: (1) page-header-disabledReason-11811.test.tsx: 7 passed. Inline: disabled and described, hover opens the tooltip, Tab reaches the trigger and opens it. Overflow: the item shows the reason and is described by it. Controls: a predicate that does not hold gives no reason; host Edit and Delete with disabled true give no reason. Text: zh pack. (2) vitest run --maxWorkers=2 packages/components/ plus the 36 app-shell, console and example files that name page:header, PageHeaderRenderer or headerSystemActions: 'Test Files 401 passed | 1 skipped (402) / Tests 4640 passed | 24 skipped (4664)', VERDICT command-exit 0. (3) pnpm --filter @object-ui/components run type-check: exit 0 (tsc --noEmit && tsc -p tsconfig.test.json). (4) Ablation through scripts/ablation-replace.mjs (objectstack), WRAP mode. The containers.tsx anchor 'if (!disabledReason) return button;' became an unconditional 'return button;'. On disk the marker is 1 and the anchor 0; the blob went 8a6b07a1ddf3 to 7d135bd4c524. Result 'Tests 4 failed | 3 passed (7)'. Red: inline description, hover, focus, zh. Green: overflow and both controls. Restored: blob == HEAD (8a6b07a1ddf3), git diff HEAD empty, git status clean, marker 0. (5) Gates at e165c34, all exit 0. i18n: check:i18n-keys, check:i18n-drift (1 key added, 0 en values changed), check:i18n-dead-keys (report; the key has 0 hits), check:i18n-designer-parity. Changeset: check-changeset-presence (16 source files of 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 containers.tsx and the new test: 0 errors. The quick-actions, DeclaredActionsBar and i18n evidence from the first report (at e2a498f: 356 files passed, type-checks exit 0, ablation 10 red / 4 green) stands, because e165c34 changes no file in those packages. NOT RUN locally: the full app-shell suite; CI owns it. CI on PR 11841: in_progress, not waited on.", "mcp_calls": "0", "api_writes": "3 this round, all through the fleet relay: (a) pr_create POST /repos/objectstack-ai/objectui/pulls (draft forced; read-back 7277 bytes identical); (b) label-write --assign os-support-ai on 11841, POST /repos/objectstack-ai/objectui/issues/11841/assignees (read-back MATCHES); (c) this os-dev-report addendum, POST /repos/objectstack-ai/objectui/issues/11811/comments. One git push this round (e165c34). The first round's one comment is reported there.", "open_questions": [], "out_of_scope_findings": [ "The action:* renderers are already filed as objectui#11839; not touched here.", "Carrier: none (承接者:无) · noted, not filed, in the PR's Acceptance notes: InlineEditSaveBar's title on a disabled Button cannot show (pointer-events none); the approval-lock band shows that reason on screen.", "Acceptance note: a faulting disabled predicate (fail-soft) also shows the generic reason." ], "gates": [ { "command": "os-verify-lock: vitest run page-header-disabledReason-11811.test.tsx", "exit": 0, "verdict": "Tests 7 passed (7)" }, { "command": "os-verify-lock: pnpm --filter @object-ui/components run type-check", "exit": 0, "verdict": "TYPECHECK_EXIT[@object-ui/components]=0" }, { "command": "os-verify-lock: vitest run --maxWorkers=2 packages/components/ + 36 named files", "exit": 0, "verdict": "Test Files 401 passed | 1 skipped (402); VERDICT command-exit 0" }, { "command": "ablation-replace (header wrapper dropped) + vitest", "exit": 1, "verdict": "Tests 4 failed | 3 passed (7) under mutation (expected red); ok restored: blob == HEAD (8a6b07a1ddf3), git diff HEAD empty" }, { "command": "pnpm check:i18n-keys / check:i18n-drift / check:i18n-dead-keys / check:i18n-designer-parity", "exit": 0, "verdict": "all exit 0; new key resolves, 1 key added, not a dead-key candidate" }, { "command": "node scripts/check-changeset-presence.mjs / check-changeset-no-major.mjs / check-changeset-fixed.mjs; pnpm check:changeset-claims / check:pending-changeset-literals", "exit": 0, "verdict": "16 source files of 4 released packages, 1 changeset; no major; fixed group complete" }, { "command": "pnpm check:control-bytes / check:test-path-roots / check:new-line-citations; eslint (touched)", "exit": 0, "verdict": "OK; OK; 0 new citations; 0 errors" } ], "deviations": [ "Overflow item: a visible reason line plus aria-describedby, not the tooltip wrapper, because a tooltip trigger inside a Radix menu is out of keyboard reach (measured in the installed @radix-ui/react-menu 2.1.24 source).", "The header's plan memo changed shape: it returns { actions, authoredKeys } instead of the bare array, and headerActionsRaw reads .actions. Its consumers and dependencies are unchanged.", "Worktree /home/user/objectui-issue-11811 removed after this report (node_modules first, then git worktree remove, without --force)." ], "files_changed": [ "packages/components/src/renderers/layout/containers.tsx", "packages/components/src/__tests__/page-header-disabledReason-11811.test.tsx (new)", ".changeset/11811-disabled-action-reason.md (adds @object-ui/components; body names the three surfaces)", "unchanged since the first report: packages/app-shell/src/views/DeclaredActionsBar.tsx, packages/plugin-detail/src/renderers/record-quick-actions.tsx, the ten packages/i18n/src/locales packs, the two other new tests" ], "line_budget": "not applicable (no skills/** surface)" }objectstack-fleet commentedon Oct 8, 2026
ACCEPT —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-08T00:30Z. PR objectui#11841, heade165c34.PR shape: draft against
main. First lineFixes #11811, no other closing keyword. Line 3 isClause-②: yes. Assigned toos-support-ai. Commits carry only the model-free trailer pair. The body records that the claim's original Responsibility namedDeclaredActionsBar, and that the Task page is drawn bypage:headerandrecord:quick_actions(the seat's correction,6049093782).Scope: 17 files, +751/−28, exactly the amended surface:
componentscontainers.tsx(the header);plugin-detailrecord-quick-actions.tsx(the section bar);app-shellDeclaredActionsBar.tsx;actions.notAvailableForRecord, in the ten packs;Nothing under
packages/components/src/ui/. No new spec key, no export, prop or type member. No@objectstack/*import added, so no spec floor moves. No governed path.Diff read (the seat's own): each surface wraps a control disabled by its declared predicate in a focusable span, the tooltip trigger. An
sr-onlydescription is pointed at byaria-describedbyfrom both the button and the span. A loading, running or host-computed disabled control is unchanged.aria-describedby, whilearia-labelledbykeeps the name. That is a measured deviation: the installed Radix menu skips a disabled item in arrow focus and traps Tab, so a tooltip there could not be reached from the keyboard. Accepted.Ruling honoured (triage
6043323597): a tooltip and an accessible description on a disabled declared action, saying "Not available for this record". No author-written reason (that is a spec key, not this card).Contract review:
6049627052, PASS one165c34(CONTRACT_REVIEW_TIER, an isolated reviewer,Local-runs: none). It also checked the manifests against the new imports:useSafeTranslatefrom@object-ui/i18nand the Tooltip family from@object-ui/components, both existing dependencies.Changeset sentences checked against the head:
aria-describedbysentence;DeclaredActionsBarbullets;i18nandcomponentsminor,app-shellandplugin-detailpatch: one fixed group atminor.Reverse verification (dev reports
6049038332,6049354721):DeclaredActionsBarturned 10 red and 4 green (the controls). On the header it turned 4 red (description, hover, focus, zh) and 3 green (the overflow item and both controls). Each was restored with the blob equal to HEAD.pointer-events-nonedisabled button never receives hover, which is why atitlecannot work.componentsplus 36 header-related files passed 4640;plugin-detail/i18nplus 27 files passed 4252.Gates: CI on
e165c34(head_shaequals the PR head): 43 runs, 40 success, 3 skipped (Test (coverage), its shard template,dependabot), 0 failure.LintandType Checkare success. Mergeable stateclean, after objectui#11836 landed in the same ten packs.Findings:
action:*renderers draw a predicate-disabled control with no reason → filed action renderers (action:button, action:icon, action:group, action:menu) draw a control disabled by its declareddisabledpredicate with no reason: no tooltip or accessible description #11839 (by the seat, from report finding 1)InlineEditSaveBar'stitleon its disabled Save cannot show, because ofpointer-events-none. The approval-lock band already shows that reason on screen, so there is no measured user-facing gap. The review escalated it, and the seat rules it not filed →Acceptance notesAcceptance notesLanding: ready + auto-merge through the queue, now.
Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Landed —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-08T00:49Z. PR objectui#11841 merged through the queue as455c646at 2026-10-08T00:48Z.57dbe34, an ancestor oforigin/main.git merge-treeof that parent and the accepted heade165c34(tree2588c438on both).57dbe34→455c646:actions.notAvailableForRecordinen.ts: 0 → 1.aria-describedbyin the three surfaces:containers.tsx0 → 4,record-quick-actions.tsx0 → 3,DeclaredActionsBar.tsx0 → 3.DeclaredActionsBar.tsx'stoPredicateInput(action.disabled)evaluation is 1 on both sides, so the predicate itself is unchanged.Fixes #11811. Since 00:15Z, the issues closed are objectui#11811 and objectui#11783 (seat 1's own PR).pm:dispatchedis removed.action:*renderers are objectui#11839, now graded p3 and queued. The key and the wrapper pattern it reuses are onmainfrom here.Generated by Claude Code
fix(components,plugin-detail,app-shell,i18n): a record action disable…