Repository navigation
Commit 5ec398d
feat(app-shell): the Studio option editor authors an option's lifecycle kind (#26931)
<!-- claude-projects-attribution: {"github_login":"os-warren"} -->
_Requested by **Warren** · [project
thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ8DR4ycmjDiMqujsSBS8TVCg)_
Fixes #26729
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:Studio 字段检查器的选项编辑器,在单选 select / radio 字段的每个选项上新增「生命周期类别」选择(未设 /
进行中 / 成功结束 / 失败结束),写入 spec 已发布的 `kind`。
- 为什么改:对象把这个字段选为 Lifecycle 字段时,spec 要求每个选项都声明 `kind`,而 Studio
原来没有任何地方能写它,保存一律 422,只能走 API。
- 风险与回滚:只动 Studio 编辑器与 i18n 文案,不改 spec、不改引擎;多值字段不画控件也不写
`kind`,改成多值类型时会去掉 `kind`;回滚即 revert 本 PR。
- 先看:`ObjectFieldInspector.tsx` 的 `patchOptions` 与 `changeType`(写入与去除
`kind` 的两处),其次 `classifyOption`(非法 `kind` 走异常行)。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.optionKind-26729.test.tsx`,预期
16 个测试全过。
- 需要你做的:无,席位落地。
## What changes
- **`kind` becomes an owned key of the picklist option editor.**
`Option` gains `kind?: SelectOptionKind`; `classifyOption` moves it out
of the `rest` bag into its own slot (the docblock's "the three parts
move together": `Option`, `readOptions`, `patchOptions`). The vocabulary
is read from the installed contract
(`SelectOptionSchema.shape.kind.unwrap().options`), not re-listed.
- **A picker per option row** (`data-testid="option-kind"`, the shared
Radix `Select`): "No kind" / Open / Won / Lost. "No kind" removes the
key outright — absent stays absent, which is what lets `ObjectSchema`
tell "declared open" from "forgot the key" under `stageField`.
- **Only where the spec accepts it.** The picker is drawn, and
`patchOptions` writes `kind`, only on a single-value `select` / `radio`
(`takesOptionKind`: `SINGLE_OPTION_TYPES` and `multiple !== true`, the
same test `FieldSchema` applies). On a multi-value field no picker is
drawn and no `kind` is written; a kind authored there by other means
(which `FieldSchema` already refuses) is not written back. A retype to a
type that cannot carry one drops the kinds, as a retype already drops a
`picklist` the new type cannot name; a retype to `radio` keeps them.
- **An out-of-vocabulary `kind`** (`kind: 'closed'`) is a malformed row
(`kind-not-valid`), carried verbatim like the other malformed shapes,
rather than silently dropped.
- **The lifecycle hint.** When the object names this field as its
`stageField` and an option still has no kind, the inspector shows one
held-hint line saying so; it clears once every option has one.
- **i18n.** New `designer.field.*` rows in the `studio` namespace, `en`
and `zh`: `optKind`, `optKind.unset` / `.open` / `.won` / `.lost`,
`optMalformed.kindNotValid`, `hint.stageKinds`. No
`engine.studio.settings.*` row. Patch changeset for `@object-ui/i18n`
(app-shell is private).
- Not touched: `ObjectSettingsPanel.tsx` (the Lifecycle picker itself),
the spec, the engine.
## Why
The Lifecycle field picker refused a Studio-authored select (`Option
'new' of field status declares no kind`), and the option editor had no
`kind` control: the triage grade asks for the control for the published
option `kind` (#23809).
## Tests
New `ObjectFieldInspector.optionKind-26729.test.tsx` (16 tests), every
case ending at `ObjectSchema` / `FieldSchema` or the written document's
keys:
- CONTROL: the Studio-built select is refused as the stage field at
`fields.status.options.{0,1,2}.kind`.
- Picking Open / Won / Lost writes the three kinds, and `ObjectSchema`
then accepts the object with `stageField: 'status'`; with one option
left unset, the refusal names only that option.
- "No kind" removes the key; an existing kind round-trips through an
edit of its own row and another row; the picker shows the stored kind.
- multiselect / checkboxes / `select` with `multiple: true`: no picker,
no `kind` written, `FieldSchema` accepts.
- A kind authored on a multiselect is not written back; an
out-of-vocabulary kind is a malformed row kept verbatim.
- A retype to Multi-Select drops the kinds; a retype to Radio keeps
them; `FieldSchema` accepts both.
- The lifecycle hint appears while an option lacks a kind and clears
once all have one; CONTROL: no hint when the object names another stage
field.
- zh: the picker's name and the kind label come from the studio
catalogue (`await loadStudioCatalogue('zh')` at import).
Ablation (one-shot, through `scripts/ablation-replace.mjs`, mutation
committed-then-restored): deleting `if (o.kind && kindAllowed) out.kind
= o.kind;` from `patchOptions` turned 5 of the 16 tests red (the three
authoring cases, the round trip, the hint); restore verified blob ==
HEAD and `git diff HEAD` empty.
## Acceptance notes
- `ObjectSettingsPanel`'s Lifecycle picker is unchanged; the order
marked that line optional and the inspector's hint covers what the
picker would have said.
- Two whole-tree scans in `ui/scripts/__tests__`
(`check-i18n-dead-keys.test.ts` "on the real tree",
`check-vi-mock-inherit.test.ts` "the sweep") each hit their 15 s test
budget once on this shared box during the full `vitest run scripts/` (a
different test each run); each file passes alone. Load-sensitive, not
this diff; CI's run is the verdict.
## Verification
<details><summary>Local runs (worktree head 7a53b40, after merging
origin/main 96e7a43)</summary>
- Closure build `pnpm --filter '@object-ui/app-shell^...' build` under
the verify lock: VERDICT command-exit 0.
- `pnpm --dir ui exec vitest run
packages/app-shell/src/views/metadata-admin/inspectors/
packages/app-shell/src/views/studio-design/ packages/i18n/
packages/app-shell/src/__tests__/`: Test Files 527 passed (527), Tests
6021 passed, 14 skipped.
- `pnpm --filter @object-ui/app-shell run type-check` and `pnpm --filter
@object-ui/i18n run type-check`: exit 0.
- `node scripts/pm/dispatch-gates.mjs --commands`: 34 families, all run;
33 exit 0; `pnpm --dir ui exec vitest run scripts/` exit 1 on the
timeout noted above (177 of 178 files green, both affected files green
alone). `--ran` reconciliation: 34 derived, 34 run, 0 NOT-MEASURED.
- Beyond the derivation, all exit 0: `pnpm check:changeset-fixed`, `pnpm
--dir ui run check:i18n-keys`, `check:i18n-dead-keys`,
`check:i18n-designer-parity`, `check:i18n-drift`,
`check:new-line-citations`.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(spec untouched)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (CI owns `pnpm lint`)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: n/a.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JQwxbX9Xgog3zJyBcPdC2R)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 3fd0ba1 commit 5ec398d
5 files changed
Lines changed: 443 additions & 8 deletions
File tree
- .changeset
- ui/packages
- app-shell/src/views/metadata-admin/inspectors
- i18n/src/namespaces/studio
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
0 commit comments