Skip to content

docs(skills): the actions rule, its eval and the i18n skill teach an action's disabledReason - #26983

Merged
os-zhuang merged 2 commits into
mainfrom
claude/project-thread-d0bo29
Oct 28, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/project-thread-d0bo29

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread

Fixes #26790

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:skills/objectstack-ui/rules/actions.md 在 disabled 旁加一条 disabledReason(I18nLabel,只做显示:灰掉的按钮悬停标题与无障碍描述;不是谓词、不是门,拒绝仍由 hook 或权限做;不写则用控制台默认句 "Not available for this record";空串拒收,disabledMessage / disabledTooltip / disabledHint / disabledText 四个近似拼法拒收并点名正键;理由不写进 description),ReassignLead 示例在 disabled 行旁带上该键。eval 文件加第 9 题:要求 disabledReason 与 disabled 谓词并列,禁止理由落进 description。skills/objectstack-i18n/SKILL.md 分组表 _actions.{action_name} 行(同样适用于 globalActions.{action_name})在 confirmText / successMessage 旁列出 disabledReason。两行 token 棘轮按裁决 A 处理;@objectstack/skills 补丁 changeset。
  • 为什么改:PR feat(spec): a greyed action says why — disabledReason beside disabled #26786 把 ActionSchema.disabledReason 加进协议,卡 skills(objectstack-ui, objectstack-i18n): the actions rule and the i18n skill do not teach an action's disabledReason (follow-up owed by #26619) #26790 是它欠下的 Skills-docs: 后续——AI 作者照技能写 action 时不知道这把键,要么留着通用默认句,要么把理由塞进 description。
  • 风险与回滚:只改两份技能文本、一个 eval JSON、两行棘轮上限和一个 changeset;不动 schema、运行时或任何键。回滚即 revert 本 PR。
  • 先看:skills/objectstack-ui/rules/actions.md(L20 新条目、L43–44 示例、删掉的两条重复代码注释)、skills/objectstack-ui/evals/views-apps-actions-pages.json(第 9 题)、scripts/check-skills-token-ratchet.mjs(两行:2151 → 2259、1789 → 2009,算式在注释里)。
  • 两分钟验证:node scripts/check-skills-token-ratchet.mjs,预期 rules/actions.md ✓ 2259/2259、views-apps-actions-pages.json ✓ 2009/2009、objectstack-i18n/SKILL.md ✓ 5485/6333,总行 82 authored bundle file(s) within their ceilings。
  • 需要你做的:批准本 PR(skills/** 受管面,Tier H)。

What changes

Every key and sentence below is read off packages/spec/src/ui/action.zod.ts on origin/main 4b8f8cdc (the disabledReason docblock L892–911, the blank refusal L913–919, the four aliases L954–955, the .describe() L1586) and the console renderer ui/packages/components/src/renderers/action/disabled-reason.tsx L88 (the default sentence); nothing is invented.

  • skills/objectstack-ui/rules/actions.md (the rule an AI author of actions reads):
    • one bullet beside disabled: disabledReason is an I18nLabel (a plain sentence or an inline locale map) a greyed action shows as its hover title and accessible description; display-only, never a predicate and never a gate (the hook or permission still refuses); omitted keeps the console's default "Not available for this record"; a blank one is refused; disabledMessage / disabledTooltip / disabledHint / disabledText are refused naming the key; the reason goes here, not in description;
    • the ReassignLead example carries disabledReason on the line after disabled, so the pair is shown once in code.
    • Paid down first (ruling 「A:先削重复,余额按实测提高」): two code comments in that example restated bullets the reader has just read and were cut, 122 bytes — // Greys out (stays visible) once the lead is converted: (home: the disabled bullet, "shows but greys out … stay discoverable") and // success toast offers Undo; Ctrl+Z works too (home: the undoable bullet, "an Undo in the success toast (and Ctrl+Z)"). The example's keys are unchanged.
  • skills/objectstack-ui/evals/views-apps-actions-pages.json: case 9 asks for an Approve action on contract that stays visible but greyed until legal_review_status is approved, with the rep told why; expects disabled: P\record.legal_review_status != "approved"`besidedisabledReason: '…', display-only, the reason not in description, omitted = the default, blank and near-miss spellings refused. must_contain disabled:/disabledReason; must_not_contain description:/disabledMessage/disabledTooltip/visible:(hiding instead of greying). Nomust_not_containstring occurs in itsexpected_output; every must_contain` string does. The file's cases share no sentence, so 0 bytes of duplicate were available to cut.
  • skills/objectstack-i18n/SKILL.md: the _actions.{action_name} group-table row lists disabledReason (the sentence a greyed action shows; absent keeps the console's default) between successMessage and params.{param_name}; the row's closing clause already says the same keys hold under globalActions.{action_name}, so that slot is covered by the same edit. The file had headroom, so its ratchet row does not move.
  • scripts/check-skills-token-ratchet.mjs: rules/actions.md 2151 → 2259 and the eval 1789 → 2009, each pinned AT the measurement with the arithmetic and the standing ruling quoted verbatim beside the sibling rows.
  • Changeset .changeset/26790-actions-disabled-reason-skills.md: @objectstack/skills patch, both declaration lines at line start.

Skills readings (skills/** touched; before = origin/main 4b8f8cdc, after = head; tokens = the ratchet's ceil(bytes/4), release-stamp line excluded):

Reading Before After
skills/objectstack-ui/rules/actions.md lines / bytes / tokens 203 / 8602 / 2151 (ceiling 2151, headroom 0) 204 / 9035 / 2259 (ceiling 2259, headroom 0)
skills/objectstack-ui/evals/views-apps-actions-pages.json lines / bytes / tokens 85 / 7154 / 1789 (ceiling 1789, headroom 0) 95 / 8034 / 2009 (ceiling 2009, headroom 0)
skills/objectstack-i18n/SKILL.md lines / bytes / tokens 521 / 21866 / 5462 (ceiling 6333, headroom 871) 521 / 21957 / 5485 (ceiling 6333, headroom 848)
all SKILL.md lines / tokens (whole package, 11 files, ratchet table sum) 4614 / 56368 4614 / 56391

Why

A record action greyed by disabled can now say why through a translatable disabledReason (PR #26786, #26619 ruling A); the console's hover title and accessible description read it and "Not available for this record" stays the default when it is absent. An agent authoring an action from the published skill was not told the key exists and would leave the generic sentence in place or put the reason into description, the workaround the card names. The skills lane owes this Skills-docs: follow-up because skills/** is Tier H and stayed out of the spec PR.

Tests

  • node scripts/check-skills-token-ratchet.mjs at head: 82 authored bundle file(s) within their ceilings; 10 generator-owned file(s) measured, not ratcheted, exit 0; rules/actions.md ✓ 2259 / 2259 (headroom 0), views-apps-actions-pages.json ✓ 2009 / 2009 (headroom 0), objectstack-i18n/SKILL.md ✓ 5485 / 6333 (headroom 848).
  • node scripts/check-skills-token-ratchet.mjs --self-test: 79 cases pass, exit 0.
  • The eval file parses (JSON.parse); case 9 has no must_not_contain string in its expected_output and every must_contain string in it.
  • No test file edited; no forced test edit.

Acceptance notes

  • The eval is not run by any gate (there is no eval runner; check:skill-docs / check:skill-refs / check:skill-examples read SKILL.md, references/ and the marked code fences). The ReassignLead fence carries os:check, so check:skill-examples compiles the new disabledReason key against the spec that now declares it.
  • Eval cases 5 and 8 each spelled their own must_not_contain string (sourceView, ${record.name}) inside expected_output; round 2 (head 4092ea0) rewords both ("no deprecated source view", "a ${…} hole fails os validate"), assertions unchanged, 8036 → 8034 bytes, same 2009-token ceiling. Per-case cross-check over all 9 cases: 0 failures (seat claim amendment 6456927584).
  • The i18n skill's example bundle mirrors examples/app-todo, whose complete action declares no disabledReason, so no example row was invented; the group-table row is the teaching.

Verification

  • node scripts/pm/dispatch-gates.mjs --tier --repo objectstack-ai/objectstack: Model tier — MANDATORY (derived from the file surface: skills/** carries no one-line exemption, clause ① — the card is Clause-②: no); changed lines 49 (+44 / −5, 5 files) vs the 3000 threshold.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at head d42ad5dc (the only commit) derived 63 commands. All 63 ran in the worktree after that commit, each as bash -c CMD with stdin from /dev/null and its exit code captured before any pipe and recorded as CMD :: exit N: 63 of 63 exit 0 — 55 source-reading commands in one serial runner while the build held the lock, the 8 dist-reading ones (pnpm --filter @objectstack/lint run check:doc-formula-expressions, pnpm --filter @objectstack/spec run check:skill-docs, check:dts-closure, check:dual-build-cjs-loads, check:entry-guard, check:lean-entry-closure, check:published-files, check:sourcemap-no-sources-content) after it. Among the 55: node scripts/check-skills-token-ratchet.mjs and its --self-test, node scripts/check-empty-changeset.mjs --base origin/main, node scripts/check-adr-0087-registration.mjs --base origin/main, node scripts/check-changeset-no-major.mjs --base origin/main, pnpm check:pm-dispatch-gates, pnpm check:pm-governed-merges, pnpm check:pm-skill-id-lint, pnpm check:doc-authoring, pnpm check:ratchet-remedy-authority, pnpm check:skill-compatibility, pnpm check:skill-frame-sync, pnpm check:skill-identifier-liveness, pnpm check:nul-bytes.
  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran RANFILE: 63 derived famil(ies) accounted for — 63 run, 0 NOT-MEASURED (a DERIVED zero — all 63 recorded an exit code and none of them is 3), exit 0; no UNRUN family named on stderr.
  • Build closure before the dist-reading gates: turbo run build --filter='./packages/**' --concurrency=2 under scripts/pm/os-verify-lock.sh: VERDICT command-exit 0 · held the lock 408s (6m48s), 75 tasks, 0 cached.
  • Derived outside the list and run as well, both exit 0: pnpm --filter @objectstack/spec run check:skill-examples (compiles the ReassignLead os:check fence with its new disabledReason key against the built spec) and pnpm --filter @objectstack/spec run check:skill-refs (9 generated files in sync with packages/spec).
  • No package source is touched, so no package test / typecheck is owed; the edited gate script's own suite is its --self-test (79 cases); pnpm lint is CI's run.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (No schema touched.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes (the repo-wide run is CI's; no source file edited).
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: n/a, not touched.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ADHkmTQn7vTBdkhp6FF1Rs


Generated by Claude Code

…action's `disabledReason`

The `disabledReason` bullet beside `disabled` in objectstack-ui/rules/actions.md
(an I18nLabel, display-only, never a predicate; omitted keeps the console's
default, blank refused, the four near-miss spellings refused naming it, the
reason never in `description`), the key on the ReassignLead example's
`disabled` line, eval case 9 expecting it beside a `disabled` predicate, and
the objectstack-i18n group table row listing it under `_actions.{action_name}`.

Token ratchet: actions.md paid down first by 122 bytes of duplicate code
comments, then 2151 -> 2259 at the measurement; the eval 1789 -> 2009 at the
measurement (0 bytes of duplicate to cut); the i18n SKILL.md row stays.

Claude-Session: https://claude.ai/code/session_01ADHkmTQn7vTBdkhp6FF1Rs
Co-authored-by: Claude <noreply@anthropic.com>
…ontain string

Seat review round 2 on the same file: case 5's expected output says "no
deprecated source view" instead of the refused key, case 8's says a
`${…}` hole fails `os validate` instead of spelling the refused hole.
Every assertion and fact kept; the file is 2 bytes smaller, the row's
2009 ceiling is unchanged and its comment carries the new arithmetic.

Claude-Session: https://claude.ai/code/session_01ADHkmTQn7vTBdkhp6FF1Rs
Co-authored-by: Claude <noreply@anthropic.com>
@os-zhuang
os-zhuang marked this pull request as ready for review October 28, 2026 00:10
@os-zhuang
os-zhuang enabled auto-merge October 28, 2026 00:10
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 28, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:skills#1 · session_01ADHkmSrRBdu9MdC6fc33Uu · head 4092ea0e · 2026-10-28T00:28Z

  1. 形态 — Tier H (skills/**): 5 files +47/−7. objectstack-ui/rules/actions.md gains one disabledReason bullet beside disabled (I18nLabel, display-only, never a predicate or gate; omitted keeps the console default; blank refused; disabledMessage / disabledTooltip / disabledHint / disabledText refused naming the key; not in description), and the ReassignLead example carries it. Eval 9 is added in evals/views-apps-actions-pages.json. The objectstack-i18n _actions.{action_name} row lists disabledReason. Token-ratchet row 1789 → 2009 with a changeset.
  2. 前提实测 — ActionSchema.disabledReason is on main's spec; no published skill named it before this PR.
  3. 门禁 check-runs — round 2 dev reading: 63 derived commands, 63/63 exit 0 (--ran 63/63, 0 NOT-MEASURED, 0 UNRUN); check:skill-examples and check:skill-refs exit 0; token ratchet green (82 rows within ceilings), --self-test 79 pass. The seat re-ran the per-case cross-check at 4092ea0e over all 9 cases: 0 failures, i.e. no must_not_contain string in any expected_output and every must_contain string present. The file is 8034 bytes. CI on the head: 28 success / 14 skipped; the body guards and Check Changeset re-run after the body edit. CI is read again before landing.
  4. 接受的偏差 — ratchet ceiling +220 under ruling A (「A:先削重复,余额按实测提高」), pinned AT the measurement (headroom 0); no duplicate sentence existed to cut — accepted. Round 2 widened the scope to reword pre-existing cases 5 and 8, which spelled their own must_not_contain string (seat claim amendment 6456927584). Assertions are unchanged; the file shrinks by 2 bytes and the ceiling is unchanged — accepted.
  5. 范围外去向 — none left: the cases 5/8 item, noted in round 1, is fixed in this PR, and the PR body's acceptance note and readings row were updated by the seat to match. Contract review at CONTRACT_REVIEW_TIER, effort ≥ xhigh: requested from the director seat.
  6. 落地条款 — governed, Tier H. os-zhuang approved this head (4092ea0e, 00:10:13Z), marked it ready and armed auto-merge; it lands through the queue on that approval. The seat does not push or re-arm.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 4092ea0e32d51366f0ffacf2d28f3274f5c6d60e
Local-runs: none
Scope: skills/objectstack-ui/rules/actions.md, skills/objectstack-ui/evals/views-apps-actions-pages.json, skills/objectstack-i18n/SKILL.md, scripts/check-skills-token-ratchet.mjs, .changeset/26790-actions-disabled-reason-skills.md + packages/spec/src/ui/action.zod.ts, packages/spec/src/system/translation.zod.ts, packages/spec/src/system/i18n-resolver.ts, ui/packages/components/src/renderers/action/disabled-reason.tsx

Director seat (summon #52, ledger objectstack#12708), summoned by the domain:skills#1 seat; effort ≥ xhigh. Read-only at the head above: the diff, card #26790 and its thread (triage 6448189939, claim 6456360955, dev report 6456899070, claim amendment 6456927584), the seat's ACCEPT 6457349839, the head and origin/main (fd246c22) copies of the three skill files and the ratchet table, the spec, the translation schema, the i18n resolver and the console renderer on origin/main, the head's check runs, reviews and timeline. skills/** is a governed-register row ⇒ the rules face, CONTRACT_REVIEW_TIER; Tier H. The seat records; it does not approve, ready, queue or merge.

① Derived judgments

  • Every clause of the new bullet is the spec's own contract. action.zod.ts L891–911 (the disabledReason docblock): an I18nLabel — a plain string translated at objects.OBJECT._actions.ACTION.disabledReason or an inline locale map; DISPLAY ONLY, never entering admission (a hook or requiredPermissions still refuses) and not an expression; a blank string refused because it would replace the console's default "Not available for this record" with an empty tooltip, omitting the key keeps that default (DISABLED_REASON_BLANK, L913–919). The four near-miss spellings disabledMessage / disabledTooltip / disabledHint / disabledText map to disabledReason in the alias table (L954–955), so each is refused naming the key. The default sentence is disabled-reason.tsx:88. "The reason goes here, not in description" is the card's named workaround, now a rule. Nothing is invented.
  • The i18n row names a slot the resolver actually reads. i18n-resolver.ts L18 / L50 (objects.<object>._actions.<action_name>.disabledReason), the field union at L657 and the lookup at L1469 (lookupActionField(bundle, action, 'disabledReason', …) ?? action.disabledReason); translation.zod.ts L200–201 refuse the same four near-misses under the translation bundle. The globalActions.{action_name} coverage comes from the row's existing closing clause, unchanged.
  • The eval file is parse-clean and self-consistent at the head, and was not on main. Parsing both copies: on main, case 5 spells sourceView and case 8 spells ${record.name} inside their own expected_output (two failures of the skills(objectstack-automation): eval fixtures 5, 7 and 8 of flows-triggers-approvals.json contradict their own assertions — expected_output spells a must_not_contain string or lacks a must_contain one, so the ideal answer fails its own check #26515 / skills(objectstack-formula): eval case 4's expected_output carries both strings its own must_not_contain refuses #26814 class); at the head, all nine cases pass the per-case cross-check. Cases 5 and 8 differ only in expected_output, their assertions are identical; cases 1–4 and 6–7 are byte-identical; case 9's must_contain (disabled:, disabledReason) are present and its four must_not_contain (description:, disabledMessage, disabledTooltip, visible:) absent — the expected text says "a *Message / *Tooltip / *Hint spelling", deliberately not the strings themselves.
  • The two cut comments were true duplicates, each with its home in the same file. // Greys out (stays visible) once the lead is converted: restated the disabled bullet (L19: "shows but greys out … stay discoverable"); // success toast offers Undo; Ctrl+Z works too restated the undoable bullet (L23: "an Undo in the success toast (and Ctrl+Z)"). The example's keys are unchanged, and the fence still carries os:check, so check:skill-examples compiles the new key against the spec.
  • Ratchet arithmetic holds from the stored bytes, both rows pinned AT the measurement under ruling A. rules/actions.md (no stamp line): 8602 → 9035 B, ceil/4 2151 → 2259, row 2151 → 2259 (+108), headroom 0 both sides. Eval: 7154 → 8034 B, 1789 → 2009, row 1789 → 2009 (+220), headroom 0 both sides; the round-2 rewordings cost −2 B inside that. objectstack-i18n/SKILL.md (stamp excluded): 21846 → 21937 priced B, 5462 → 5485 under its 6333 ceiling, row untouched by right. The row comments carry the numbers and the wc -c checks.
  • Changeset is right for what moves. @objectstack/skills patch; Clause-②: no / Metadata-protocol: none — skill text, an eval and ceilings, no exported shape. Five files, all inside the claim plus its amendment 6456927584.

② Semver level

@objectstack/skills patch. Correct.

③ Boundary flags

  • Governed path (skills/**, Tier H). At this record the PR is approved by os-zhuang (GOVERNED_APPROVERS) on this head at 2026-10-28T00:10:13Z, marked ready and armed by the same hand at 00:10:18–21Z, and in the merge queue since 00:28:05Z — the maintainer's own acts, after the dev's draft and before the seat's ACCEPT. The approval is the landing condition; this record changes nothing about it and may post after the merge. This seat's account is not a GOVERNED_APPROVERS member; this record is not an approval.
  • Scope widening is on the record: the claim amendment 6456927584 added the cases 5 / 8 rewordings to the file surface before the head was cut; the PR body's acceptance note and readings row match the head. Accepted as the seat did.
  • Check runs on 4092ea0e at this record: 30 success, 14 expected skips, 1 cancelled — a Governed Surface Queue Guard run superseded 18 s later by a success run of the same job on the ready-for-review event. Nothing red. main has not moved on any of the four tracked files since the base (2fe4a742).
  • Dev deviations: none beyond the amended scope. The eval is not run by any gate (no eval runner), as the body says; its self-consistency is the parse-check above.
  • Nothing to carry: no should-fix.

④ Design judgment

  • One bullet beside disabled, the key shown once in the example, one eval case that forbids the two wrong places (the description and a hide-instead-of-grey visible:), and the translation slot where the resolver reads it — the smallest teaching that closes the Skills-docs: debt feat(spec): a greyed action says why — disabledReason beside disabled #26786 left. Paying with the two comments that restated the bullets the reader had just read was the right cut, and fixing cases 5 and 8 in the same row move avoided a third card for the same class. Holds.

Implemented-by: claude/project-thread-d0bo29
Reviewed-by: session_01ADHkmQr732toX135tyo5zs

VERDICT: PASS


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

skills(objectstack-ui, objectstack-i18n): the actions rule and the i18n skill do not teach an action's disabledReason (follow-up owed by #26619)

3 participants