Skip to content

Commit 3806a5a

Browse files
docs(skills): a param's helpText takes a sentence or an inline locale map in the ui actions rule (#31617)
<!-- claude-projects-attribution: {"github_login":"zhuangjianguo"} --> _Requested by **Steedos** · [project thread](https://claude.ai/code/project/chan_018ETKaNwMZCNRYD7D8Q9Ubb?thread=cmsg_018ETKaNwMZCNRYD7D8Q9UbbQ7P4Kce6oMVkWfoAmTFBvQ)_ Fixes #31487 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:技能文档 `skills/objectstack-ui/rules/actions.md` 新增一条说明:动作参数的提示文字 `helpText` 和 `description` 一样,可以写一句话,也可以写按语言的对照表;页面按钮的内联动作用对照表写多语言提示,两种写法都能带记录占位。 - 为什么改:PR #31485 之后 schema 已接受对照表,但技能还只教“写一句话”,AI 照着写页面按钮时提示只会有一种语言。 - 风险与回滚:只改技能文档,不动运行时和 schema;为守住 token 上限删了同文件里六处重复说明。回滚即 revert 本 PR。 - 先看:`skills/objectstack-ui/rules/actions.md` 第 26 行新增的那条,以及为付 token 删掉的几处(disabled 一句、successMessage 一句、两处代码注释、两处括注)。 - 两分钟验证:`node scripts/check-skills-token-ratchet.mjs` 中 actions.md 一行显示 2258 / 2259,且 `pnpm --filter @objectstack/spec check:skill-examples` 退出 0。 - 需要你做的:审阅并批准这个 Tier H PR(skills/** 受管面)。 ## What changes - `skills/objectstack-ui/rules/actions.md`: a new bullet under the copy-holes line says a param's `helpText`, like `description`, is a sentence or an inline locale map (example `{ en: 'Why ... is paused', 'zh-CN': '暂停原因' }` with a record-name hole in the `en` value), that holes work in either form, and that the map is how a page button's inline `action` (no bundle reaches it) writes its hint per locale. The copy-holes line keeps `helpText` in its list of copy that reads `record`; its sentence example moved into the new bullet's map. - The file has no worked inline-action example, so no example block changed. - Token payment, all inside the same file, deletion only (no re-wrap): the `disabled` bullet's "Use this (not `visible`) ..." sentence (the hidden vs greyed-out contrast is already drawn by the two bullets); "Set `successMessage` for non-obvious outcomes;" (the default-toast clause stays); the `// name of the flow` and `// registered handler` code comments; the "(params, then a server body)" and "(SSO-like handlers)" parentheticals, which restate their example or heading. - `.changeset/31487-ui-skill-helptext-locale-map.md`: `'@objectstack/skills': patch`. Matched against `packages/spec/src/ui/action.zod.ts` (`actionParamHelpText`, `localeMap: true`) and `content/docs/protocol/objectui/actions.mdx` L519 as #31485 left them. ## Tests - `pnpm --filter @objectstack/spec check:skill-examples`: exit 0, 308 marked examples across 118 files type-check. - Schema probe against the built spec: `ActionParamSchema.safeParse` accepts `helpText` as the taught map (with a hole in `en`) and as a sentence. ## Acceptance notes - `skills/**` readings: `actions.md` 180 → 181 lines, 9034 → 9031 bytes, 2259 → 2258 tokens (ceiling 2259, headroom 1). All `SKILL.md` in the catalog: 4592 → 4592 lines (none touched). - No ceiling moved. ## Verification - Derived gate sweep (`dispatch-gates --commands`, 44 families): 43 exit 0; `check:dual-build-cjs-loads` exit 3 (PREREQUISITE NOT MET, needs a full `pnpm build`), NOT MEASURED locally, CI runs it. `--ran` reconciliation: 44 accounted for. --- _Generated by [Claude Code](https://claude.ai/code/session_01QsR2jSvaXAXqBaw3WmE3gy)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 09df571 commit 3806a5a

2 files changed

Lines changed: 18 additions & 7 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): the objectstack-ui actions rule teaches that a param's `helpText`, like `description`, takes a sentence or an inline locale map, and that the map is how a page button's inline `action` writes its hint per locale
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
The skill taught a param's `helpText` as a sentence only, so an agent writing a page button's inline `action`, which no translation bundle reaches, left its hint in one language. `{{ record.* }}` holes work in either form, as `content/docs/protocol/objectui/actions.mdx` and the action param schema say.

‎skills/objectstack-ui/rules/actions.md‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616
## Visibility, Disable & Feedback
1717

1818
- `visible` — a bare CEL predicate over the current record (prefer the `P\`...\`` template; `record.<field>` resolves identically on every surface, never `${…}`- or `{…}`-wrapped — see `objectstack-formula`); when false the action is **hidden**.
19-
- `disabled` — `boolean` **or** a CEL predicate; when true the action **shows but greys out**. Use this (not `visible`) when the action should stay discoverable but locked in the current state.
19+
- `disabled` — `boolean` **or** a CEL predicate; when true the action **shows but greys out**.
2020
- `disabledReason` — an I18nLabel (a 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). Omit it to keep the console's default "Not available for this record"; a blank one is refused, and `disabledMessage` / `disabledTooltip` / `disabledHint` / `disabledText` are refused naming this key. The reason goes here, not in `description`.
2121
- `pressed` — a CEL predicate over the same scope as `visible` that marks the action as already done by this reader: while TRUE the record header draws it pressed (`aria-pressed`). Display-only, never a gate; a boolean literal is refused (a constant is a style, not a reader's state), and `active` / `activeWhen` / `pressedWhen` are refused naming this key. A per-reader fact (the reader's vote) is a block the object declares under `attachedOnRead`, filled per caller by a service or `afterFind` hook, read as `has(record.viewer) && record.viewer.vote == 'helpful'`.
2222
- `confirmText` — set for any destructive or irreversible operation; the confirm dialog of a param-less action. A registered action is refused it beside a non-empty `params` (put the question on `description`); a view's `bulkActionDefs` entry pairs the two by its own contract.
2323
- `submitText` — the verb on the param dialog's submit button ("Send", "Log Call") in place of "Confirm": an I18nLabel, refused blank and refused without a non-empty `params` (no dialog opens); `submitLabel` / `confirmLabel` are refused naming this key.
24-
- `successMessage` / `errorMessage` — toast copy on success / failure. Set `successMessage` for non-obvious outcomes; without it the UI shows a generic "Action completed" toast.
25-
- Copy holes are `{{ }}`: `confirmText`, `description` (`'Choose the new owner of {{ record.name }}.'`) and a param's `helpText` (`'Why {{ record.name }} is paused'`) read `record`; `successMessage` and each `outcomeMessages` entry (`resent: 'Invitation resent to {{ result.email }}'`) read `result` (the handler's answer) and `record`; `errorMessage` takes none. A `bulkActionDefs` entry's `confirmText` reads `{{ count }}` and its params by bare name (`{{ tier }}`). In all of them `${record.name}`, `{name}`, logic in a hole and a blank sentence fail `os validate`.
24+
- `successMessage` / `errorMessage` — toast copy on success / failure; without `successMessage` the UI shows a generic "Action completed" toast.
25+
- Copy holes are `{{ }}`: `confirmText`, `description` (`'Choose the new owner of {{ record.name }}.'`) and a param's `helpText` read `record`; `successMessage` and each `outcomeMessages` entry (`resent: 'Invitation resent to {{ result.email }}'`) read `result` (the handler's answer) and `record`; `errorMessage` takes none. A `bulkActionDefs` entry's `confirmText` reads `{{ count }}` and its params by bare name (`{{ tier }}`). In all of them `${record.name}`, `{name}`, logic in a hole and a blank sentence fail `os validate`.
26+
- A param's `helpText`, like `description`, is a sentence or an inline locale map (`{ en: 'Why {{ record.name }} is paused', 'zh-CN': '暂停原因' }`), holes in either; the map is how a page button's inline `action` (no bundle reaches it) writes its hint per locale.
2627
- `undoable: true` — an **Undo** in the success toast (and `Ctrl+Z`). Snapshot: runtime on `operation: 'update'`, console on `type: 'api'`; else refused.
2728

2829
<!-- os:check -->
@@ -62,7 +63,7 @@ export const ConvertLeadAction = defineAction({
6263
objectName: 'lead',
6364
icon: 'arrow-right-circle',
6465
type: 'flow',
65-
target: 'crm_lead_conversion', // name of the flow
66+
target: 'crm_lead_conversion',
6667
locations: ['record_header', 'list_item'],
6768
visible: P`record.status == "qualified" && record.is_converted == false`,
6869
confirmText: 'Are you sure you want to convert this lead?',
@@ -72,7 +73,7 @@ export const ConvertLeadAction = defineAction({
7273
});
7374
```
7475

75-
**Script-typed action** (params, then a server body):
76+
**Script-typed action**:
7677

7778
<!-- os:check -->
7879
```typescript
@@ -142,7 +143,7 @@ export const PrintA3Action = defineAction({
142143

143144
### `opensInNewTab` + `newTabUrl` — async / computed redirect (SSO)
144145

145-
A redirect URL **computed after a fetch** (SSO-like handlers): `opensInNewTab:
146+
A redirect URL **computed after a fetch**: `opensInNewTab:
146147
true` pre-opens the tab synchronously on click (no popup blocker), then navigates
147148
it to the handler's `redirectUrl`. `newTabUrl` skips the round-trip — a backend
148149
path starting with `/` (`{recordId}` placeholder; an external URL is refused, use
@@ -155,7 +156,7 @@ export const OpenInvoicePdfAction = defineAction({
155156
label: 'Open PDF',
156157
objectName: 'invoice',
157158
type: 'script',
158-
target: 'crm_open_invoice_pdf', // registered handler
159+
target: 'crm_open_invoice_pdf',
159160
opensInNewTab: true,
160161
newTabUrl: '/api/v1/invoice/{recordId}/pdf',
161162
locations: ['record_header'],

0 commit comments

Comments
 (0)