Repository navigation
feat(console): a greyed action says the reason its author declared - #26869
Conversation
The record header, the record section bar, the declared actions bar and the four action:* renderers now show an action's declared disabledReason as the hover title and accessible description (a menu item's second line) while its declared disabled predicate is true. "Not available for this record" stays the default when none, or a blank one, is declared. useActionTextLocalizer resolves the key like the other action copy, through a new actionDisabledReason resolver on useObjectLabel: the bundle's _actions.ACTION.disabledReason first, then the authored I18nLabel collapsed to the active language. UIActionSchema declares the key, derived from the spec. The liveness row flips to live with the console readers as evidence, drops authorWarn/authorHint, and its lint pin becomes a silence pin. The key's describe() texts and the actions page no longer say nothing shows it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzfHuAwrecfjUDtkwcm
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzfHuAwrecfjUDtkwcm
📓 Docs Drift Check2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 147 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin cebef39ca4aff172805243a49ba19be7fb37d7c7 && git checkout cebef39ca4aff172805243a49ba19be7fb37d7c7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 96e7a4335c0aa611a706664da6df339f672ec1ab 4b5b87d21cb159072eabbd32bc58fc27c1827d6d && git checkout -B drift-repro 96e7a4335c0aa611a706664da6df339f672ec1ab && git merge --no-ff 4b5b87d21cb159072eabbd32bc58fc27c1827d6d
node scripts/docs-audit/affected-docs.mjs --json 96e7a4335c0aa611a706664da6df339f672ec1ab |
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzfHuAwrecfjUDtkwcm
Contract reviewServed-tier: Ruling executed: 6442737825 on #26619, letter A, second half (the console readers). Inputs read: the card body and all seven comments (triage 6440296436, brief 6441887048, ruling 6442737825, claim 6444476085, os-dev-report 6446453447 for PR 1, landing note 6449918956, os-dev-report 6451252645 for this PR); PR #26786's two contract records (6446707981, 6447429917) and its ACCEPT (6448592048) as background for what this PR owes; the PR #26869 body, its 28-file list, the net diff Head provenance. Check-runs on the head at review time (19:19:28Z, two REST reads): 58 runs, 19 ① Derived judgmentsWhat PR 1 promised this PR would do, judged one by one.
The readers, each one asked whether it renders the declared reason.
② Semver levelChangeset
③ Boundary flags
④ Design judgment
Must-fix: none. Should-fix (non-blocking): (1) the two PR-body sentences counting "16 changed ui files" read 17 at this head (② gives the replacement); (2) the Implemented-by: VERDICT: PASS |
… one Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzfHuAwrecfjUDtkwcm
席位核对 · ACCEPT ·
|
Requested by Yibo Wang · project thread
Fixes #26619 — the console half. The spec half,
ActionSchema.disabledReason, landed in #26786.Clause-②: no
Metadata-protocol: none
Skills-docs: #26790
维护者速读
disabledReason(悬停提示、读屏描述、菜单项第二行),没写或写了空白时仍显示「对此记录不可用」。覆盖记录页头(含 ⋯ 溢出菜单)、记录区块按钮栏、声明动作栏,以及action:button/action:icon/action:group/action:menu四个渲染器。原因文案走和动作其他文案相同的翻译路径:先取翻译包objects.对象._actions.动作.disabledReason,再取作者的内联多语言映射,最后取作者原文。disabledReasonbesidedisabled#26786 落地,这个 PR 让控制台真正读它,hotclm 的送审 / 发起签署 / 生效能说清缺什么。disabled判断为真时才显示原因;动作执行中、宿主禁用的按钮仍不给原因。纯展示,不参与服务端准入。回滚就是 revert 本 PR。ui/packages/react/src/hooks/useActionTextLocalizer.ts(解析原因文案),ui/packages/components/src/renderers/action/disabled-reason.tsx(四个渲染器的共用钩子),packages/spec/liveness/action.json(存活账本改为 live)。cd ui && pnpm exec vitest run packages/components/src/renderers/action/__tests__/action-disabled-reason-26619.test.tsx,预期全部通过:写了原因显示原因,中文环境取内联映射的中文,不写时仍是默认句。What changes
The readers. While an action's DECLARED
disabledpredicate is TRUE, each surface shows its declared reason where it showedactions.notAvailableForRecord. That generic sentence stays the answer when the action declares none, or a blank one. The surfaces where nothing earns a reason are unchanged: a button greyed while its own action runs, the host's forwardeddisabled, the host's Edit / Delete, and the legacyenabledleg.ui/packages/components/src/renderers/layout/containers.tsx#disabledReasonFor: the record header, inline and in the ⋯ overflow.ui/packages/plugin-detail/src/renderers/record-quick-actions.tsx(QuickActionButton): the record section bar.ui/packages/app-shell/src/views/DeclaredActionsBar.tsx(DeclaredActionButton): the declared actions bar, button and override-menu item. The reason comes off the same localized copy as the label.ui/packages/components/src/renderers/action/disabled-reason.tsx#useDisabledReason: the fouraction:*renderers. The hook takes the declared reason as a second argument, andaction-button.tsx,action-icon.tsx,action-group.tsx(inline and dropdown) andaction-menu.tsxpass it.Translation. The reason is an
I18nLabeland is resolved like the action's other copy:useObjectLabel(@object-ui/i18n) gainsactionDisabledReason(objectName, actionName, fallback?), theactionSuffixespattern:objects.<o>._actions.<a>.disabledReason, orglobalActions.<a>.disabledReasonwith no key object. It reads the same two addressestranslateActionandos i18n extractuse.useActionTextLocalizer(@object-ui/react) collapses a declared reason's locale map to the active language and asks the bundle, but only when the action declares the key. That is theconfirmTextrule: a bundle cannot give a reason to an action whose author wrote none, so that action keeps the default. The record header, the section bar and the declared actions bar already run their actions through it.action:*renderers read the action as authored (they do not localizelabelthrough the bundle either), so the inline locale map is the translation they honour.useDisabledReasoncollapses it withpickLocalized.Types.
UIActionSchema.disabledReason(@object-ui/types), derived from the spec (SpecAction['disabledReason']), besidedisabled.Spec side.
packages/spec/liveness/action.jsondisabledReason:planned→live,evidenceScope: in-repo, with the readers above as evidence.authorWarnandauthorHintare dropped in the same edit, so a warnedliverow cannot redcheck:liveness.liveness/state-counts/action.mdwas regenerated.packages/lint/src/lint-liveness-properties.test.ts: the [Decision] A greyed-out record action can only say 对此记录不可用 — re-ask for an authored disabled reason now that a second app needs it (re-file per #24034 B′) #26619 block becomes a silence pin. The key is authored beside a property that still warns (object.externalSharingModel,dead) in the same call, so the silence is a reading. The block also asserts the shipped row islive, carries no warning, and citescontainers.tsx#disabledReasonFor..describe()texts (action.zod.ts,translation.zod.ts) andcontent/docs/protocol/objectui/actions.mdxno longer say that nothing shows the key. Fourcontent/docs/references/**pages were regenerated bycheck:generated --fix.Changeset.
.changeset/26619-console-disabled-reason.md, every moved package atpatch:Metadata-protocol: none, because nothing an author can write changes (AGENTS.md step 3).@objectstack/lintchanges a test only.Tests
ui/packages/react/src/hooks/__tests__/useActionTextLocalizer.disabledReason-26619.test.tsx(6): authored string, bundle outranks authored, locale map collapsed (zh), theglobalActionsaddress, no key added when the action declares none even with a bundle entry, a nameless action's map collapsed.ui/packages/components/src/renderers/action/__tests__/action-disabled-reason-26619.test.tsx: for the three button renderings, the declared string as description and tooltip, the zh map, the default (en and zh), a blank map value falling back to the default, and the predicate-false control. For the two menu renderings, the declared second line, the default, and the control.ui/packages/components/src/__tests__/page-header-disabledReason-26619.test.tsx: inline, overflow, the zh bundle outranking the authored text, the default, and the control.ui/packages/plugin-detail/src/renderers/__tests__/record-quick-actions.disabledReason-26619.test.tsxandui/packages/app-shell/src/views/__tests__/DeclaredActionsBar.disabledReason-26619.test.tsx: the declared string, the zh bundle, the default, and the control.-11811/-11839sibling suites, which pin the mechanics and the no-reason controls, pass unchanged.Test readings (local, on the pushed head)
d96cc5bbb(f6a479a30plus the i18n pin), branch restarted frommainataeb9cf0dd.pnpm --filter @objectstack/spec test: Test Files 735 passed (735), Tests 22041 passed, 1 todo.pnpm --filter @objectstack/lint test: Test Files 168 passed (168), Tests 7432 passed.pnpm --filter @objectstack/spec check:liveness: OK.check:generated --fix: 2 of 14 stale on the first run (check:docs,check:liveness), regenerated and committed.node scripts/check-doc-authoring.mjs: exit 0.pnpm --filter @object-ui/{types,i18n,react,components,plugin-detail,app-shell} type-check: all exit 0, after building@object-ui/app-shell^....cd ui && pnpm exec vitest run packages/types/ packages/i18n/ packages/react/ packages/components/ packages/plugin-detail/ packages/app-shell/src/views/__tests__/: Test Files 1442 passed, 1 failed, 2 skipped; Tests 21406 passed, 1 failed. The one failure was a pin that moves with the new resolver:useObjectLabel-identity-5564.test.tsxcountsuseObjectLabel's members (27 → 28). It is updated ind96cc5bbband passes (5/5).ui/packages/corewas not run locally. Onmain, itsactionKeys.pin.test.tsreds on the new spec key (SPEC_ACTION_KEYS lacksdisabledReason). The seat's hotfix fix(ui): the action key inventory lists disabledReason #26859 fixes that, and this branch takes it by amainmerge once it lands. This PR does not add the row.no-explicit-any).Acceptance notes
action:*renderers honour the inline map, not the bundle. They renderlabelas authored too. Giving them a bundle lookup would also need a key object they do not carry, and that is a wider change than this card. A registered action that reaches them through a surface that localizes (the record header, the section bar, the declared actions bar) already arrives with the bundle's sentence.disabledReasonbesidedisabled#26786's acceptance note).Human Minimum
packages/specholds no runtime logic.mainafter feat(spec): a greyed action says why —disabledReasonbesidedisabled#26786 merged, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_01WGufzfHuAwrecfjUDtkwcm
Generated by Claude Code