Repository navigation
feat(spec): a greyed action says why — disabledReason beside disabled - #26786
Conversation
…led` ActionSchema gains an optional, display-only I18nLabel `disabledReason` (blank refused), translated at objects.OBJECT._actions.ACTION beside confirmText / successMessage, overlaid by translateAction and emitted by os i18n extract. Never a server admission input, never read as a predicate. Claude-Session: https://claude.ai/code/session_01WGufzfHuAwrecfjUDtkwcm Co-authored-by: Claude <noreply@anthropic.com>
…ema; regenerate artifacts Claude-Session: https://claude.ai/code/session_01WGufzfHuAwrecfjUDtkwcm Co-authored-by: Claude <noreply@anthropic.com>
…the cli test Claude-Session: https://claude.ai/code/session_01WGufzfHuAwrecfjUDtkwcm Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 4 package(s): 9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 154 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 288882c00660338824e8b044e427022744a9545b && git checkout 288882c00660338824e8b044e427022744a9545b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8b276385e6f1e2b94a4061160c148cfa56f81588 c9d6d356f73771753b20bde48dbef24d9120692e && git checkout -B drift-repro 8b276385e6f1e2b94a4061160c148cfa56f81588 && git merge --no-ff c9d6d356f73771753b20bde48dbef24d9120692e
node scripts/docs-audit/affected-docs.mjs --json 8b276385e6f1e2b94a4061160c148cfa56f81588
|
Contract reviewServed-tier: Ruling executed: 6442737825 on #26619, letter A. Inputs read: the card body and all five comments (triage 6440296436, brief 6441887048, ruling 6442737825, claim 6444476085, os-dev-report 6446453447), the PR body, its 24-file list, the net diff Head provenance. Check-runs on the head at review time (two REST reads, 15:50Z and ~16:05Z): 48 runs — 22 ① Derived judgments
② Semver levelChangeset
③ Boundary flags
④ Design judgment
Must-fix (one, two sentences wide, found under ②/①.3/①.9): the changeset body and Should-fix: the present-tense console sentence in the Implemented-by: VERDICT: FAIL |
…the console reads it Contract review on the spec half: - The changeset and the actions page said an empty locale map or a blank map value is refused; only a blank string is. Both now say so. - The changeset said an author gets the planned-property warning, but the liveness row set no authorWarn. The row now carries authorWarn plus an authorHint, and a lint pin reads the warning off the shipped ledger. - The two .describe() texts no longer say the console shows the key yet. CI pins that move with the new key: the metadata-form catalog positive control (686 -> 687, the action form's new row) and the /meta/types action key set (47 -> 48, now sampled by name). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzfHuAwrecfjUDtkwcm
Contract reviewServed-tier: This is a DELTA record over the full review at Check-runs on ① Derived judgmentsEvery ①-judgment of 6446707981 (1–11) stands at
② Semver levelUnchanged from 6446707981 and still RIGHT: Body matches what the diff publishes — the two sentences the full record failed are now true (①.1, ①.2). The remaining PR-body claims were re-read against the tree: " NEW — MUST-FIX: a tracker id in customer-facing ③ Boundary flags
④ Design judgment
Must-fix (one, new, two strings wide): remove Should-fix: the present-tense console sentence at Implemented-by: VERDICT: FAIL |
…text The two .describe() texts carried an issue id, which check-doc-authoring refuses in customer text; the adjacent comments keep it. The actions page now says the console does not show the key yet. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzfHuAwrecfjUDtkwcm
…-yxepsj # Conflicts: # packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzfHuAwrecfjUDtkwcm
席位核对 · ACCEPT ·
|
Requested by Yibo Wang · project thread
Part of #26619 — the spec half. The console half (the greyed action's hover title and accessible description read the key) is the follow-up PR, which closes the card.
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: #26790
维护者速读
disabled旁新增一个可选键disabledReason:按钮置灰时说明原因的那句话(可翻译,字符串或内联多语言映射)。翻译位在objects.OBJECT._actions.ACTION.disabledReason,translateAction会套用译文,os i18n extract会生成这个键。空字符串和纯空白被拒绝。Studio 动作表单加了一行,四种语言的表单文案已补。packages/spec/src/ui/action.zod.ts(新键与空白拒绝),packages/spec/src/system/i18n-resolver.ts(translateAction套用译文),packages/cli/src/utils/i18n-extract.ts(生成翻译键)。pnpm --filter @objectstack/spec exec vitest run src/ui/action-disabled-reason.test.ts,预期 16 个用例全部通过,其中一条断言像 CEL 的字符串原样保留、不会被当成表达式。What changes
ActionSchema.disabledReason(packages/spec/src/ui/action.zod.ts), an optional key besidedisabled. It is the sentence a greyed action shows to say why it is unavailable. An action that does not declare it parses exactly as before.disabledReason, the card's candidate, kept for three reasons. It pairs with the key it explains (disabled→disabledReason), the way an author reads the two together. It names the content (why), not one place it lands: the console shows it as a button's hover title, as its accessible description, and as a menu item's second line, sodisabledTooltipwould name one of three. And it is the console's own word already (useDisabledReason,disabledReasonFor,DisabledReasoninrenderers/action/disabled-reason.tsx), so spec and renderer speak one vocabulary. Rejected:disabledMessage(reads as thesuccessMessage/errorMessagefamily, copy shown after a run),disabledTooltip,disabledHint. All four, withdisabledText, are renamed onto the key, on the action and in a translation bundle.z.union([z.string().refine(NON_BLANK_STRING), InlineLocaleMapSchema]). The string arm refuses''and whitespace-only, with a message that names the default it would replace and says to omit the key instead. The refinement is the sharedNON_BLANK_STRING, whichrefinement-projection.tsdeclares, so the publishedjson-schema/ui/Action.jsoncarries it (minLength: 1,pattern: "\\S") and the dropped-refinement ledger gains no row. A first cut with asuperRefineadded 12 ledger rows; this one adds none. The map arm isInlineLocaleMapSchemaunchanged, the I18nLabel every other copy key takes.requiredPermissions). It is not an expression: a predicate-looking value comes out of the parse as the same string, wheredisabledis lowered to its CEL envelope.os validatereadsdisabledand notdisabledReason, and the lint's literal-member census (validate-expressions.test.ts, receiveraction) stays['disabled', 'name', 'objectName', 'visible'].actionTranslationSchema(packages/spec/src/system/translation.zod.ts) declaresdisabledReasonat both addresses,objects.OBJECT._actions.ACTIONandglobalActions.ACTION, besideconfirmText/successMessage.translateAction(i18n-resolver.ts) overlays it the way it overlaysdescription: the bundle's sentence for the locale chain, else the authored value. The address lists in both files' docblocks name it.collectExpectedEntries(packages/cli/src/utils/i18n-extract.ts) emits the key for a top-level object action, a global action and an action declared inline on an object. It ispushOptional, likedescription: an action that gives no reason seeds nothing and is not a coverage gap, because the console has its own default sentence.action.form.tsgains adisabledReasonrow underdisabledin the Placement section (the plain rowerrorMessageuses, same I18nLabel node). The fourmetadata-formsbundles in@objectstack/platform-objectscarry its label and help (en, zh-CN, ja-JP, es-ES).liveness/action.jsonrowdisabledReason:plannedwithauthorWarnand anauthorHint, soos validatewarns an author who declares it that nothing shows it yet. The framework read points are the evidence, and the four console readers are named as the follow-up that flips it toliveand drops the warning.content/docs/protocol/objectui/actions.mdxgains the key in the interface block and a "Saying why:disabledReason" section;content/docs/ui/translations.mdxnames disabled reasons on the_actionsrow.authorable-surface/ui.json(ui/Action:disabledReason),liveness/state-counts/action.md, and fourcontent/docs/references/**pages, bycheck:generated --fix..changeset/26619-action-disabled-reason.md:@objectstack/spec,@objectstack/cliand@objectstack/platform-objectsatminor(AGENTS.md step 3: a kind other thannonetakes at leastminoron a package the diff moves).@objectstack/lintchanges a comment and a test only, so it publishes nothing.Tests
packages/spec/src/ui/action-disabled-reason.test.ts(16): the parse (string, locale map, optional), the not-a-condition pin, the I18nLabel refusals shared withlabel, the blank refusal ('', whitespace: path and message), the four near-miss renames, the inline action still refusing the key, the translation slot at both addresses, the bundle-side rename, andtranslateAction(translated, global address, fallback to authored, no key added).packages/cli/test/i18n-extract-action-disabled-reason.test.ts: the three emit sites, nothing seeded for an action without a reason, the declared text carried into the bundle and gated until translated, and one fixture through schema → extract → translation schema.packages/lint/src/validate-expressions.test.ts: a bare-field source that is an error ondisabledproduces no finding ondisabledReason.packages/lint/src/lint-liveness-properties.test.ts: the shipped ledger warns ondisabledReasonat the action, with the row's hint; an action without it draws nothing. Two pins move with the new key:packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts(the metadata-form catalog, 688 → 689 after themainmerge; main's ADR-0142queue/assignment_rulerows had moved it 686 → 688) andpackages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts(the action key set, 47 → 48, withdisabledReasonadded to the named sample).Local runs (on
aa552ced, before themainmerge atc81d8630e, which touched none of these packages; the lint fix and the cli typing fix came after and were re-run on1d2b0252):Test readings
pnpm --filter @objectstack/spec test(underos-verify-lock): Test Files 733 passed (733), Tests 21985 passed, 1 todo.pnpm --filter @objectstack/spec typecheck: exit 0 (check:test-typecheck OK).pnpm --filter @objectstack/lint test: Test Files 167 passed (167), Tests 7423 passed. The first run reded one pin,validate-translation-references.test.ts"classifies every key the action translation shape declares": its coverage table needed the newdisabledReasonrow (leaf-copy, prose), added in1d2b0252.pnpm --filter @objectstack/lint typecheck: exit 0.pnpm --filter @objectstack/cli exec vitest run --project unit test/i18n-extract test/i18n-coverage: Test Files 10 passed, Tests 100 passed; the new file 6/6 after the typing fix.pnpm --filter @objectstack/cli typecheck: exit 0 after1d2b0252(the first run named 4 TS18048 in the new test file, fixed with optional chaining rather than a debt-ledger row).pnpm check:i18n: OK, 10 packages in sync.pnpm --filter @objectstack/spec check:generated: 2 of 14 stale on the first run (check:docs,check:liveness), regenerated with--fix, committed inaa552ced.bdac02acf(contract-review and CI fixes):@objectstack/spectest 733/733 files (21985 tests);@objectstack/linttest 167/167 files (7425 tests) and typecheck exit 0;@objectstack/platform-objectstest 96/96 files (1361 tests);@objectstack/metadata-protocoltest 269 passed, 3 skipped (21580 tests);check:livenessOK;check:generated --fixregeneratedcheck:docsonly (the.describe()texts).c9d6d356f(delta-review fix51685f13c,mainmerged at5b5e6275d, reference docs regenerated on the merged tree inc9d6d356f):@objectstack/platform-objectstest 96/96 files (1361 tests);@objectstack/metadata-protocoltest 269 passed, 3 skipped (21616 tests), the action key-set pin still 48 after the merge;@objectstack/spectest 735/735 files (22034 tests);@objectstack/linttest 168/168 files (7433 tests);@objectstack/spec check:generatedall current;node scripts/check-doc-authoring.mjsexit 0 (no internal issue id in customer text).Acceptance notes
plannedand the author-warning lint says so. The docs section says the console does not show the key yet, names the default reason every greyed action shows until the readers ship, and saysos validatewarns an author who declares it. Direction of the window: spec accepts ahead of the renderer, never the reverse.skills/**is Tier H. Putting the skills lines in this PR would make the spec half wait for a maintainer approval; the owed files are listed on theSkills-docs:line for the seat to file.skills/objectstack-ui/rules/actions.mdand its eval file are at 0 token headroom, so that card pays under the ruling 「A:先削重复,余额按实测提高」.InlineLocaleMapSchemaas every I18nLabel takes it; refusing a blank map value would need a refinement the projection's closed list cannot publish, or a second locale-map schema. The ruling names the empty string, which is refused.Verification
Derived gates (`dispatch-gates.mjs --commands`, 109 families, run serially on `c81d8630e`)
pnpm --filter @objectstack/spec run check:skill-examples,pnpm check:dual-build-cjs-loads,pnpm check:i18n-coverage. NOT MEASURED locally; CI runs them.dispatch-gates --ran: "109 derived famil(ies) accounted for — 106 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3)."!in the title and no(narrowing)arm.Human Minimum
packages/specholds no runtime logic.pnpm lintpasses (eslint over the 10 changed source and test files: exit 0; the repo-wide run is CI's).mainmerged in at5b5e6275d(no rebase), current withmainatf7c95cd19.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