Skip to content

feat(app-shell): the Studio option editor authors an option's lifecycle kind - #26931

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-9mghmx
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-9mghmx

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
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

Local runs (worktree head 7a53b40, after merging origin/main 96e7a43)
  • 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.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (spec untouched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (CI owns pnpm lint)
  • 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.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.

Generated by Claude Code

…le kind

The picklist option editor owned value/label/color and carried `kind` only
through its rest bag, so a Studio-built select could never become the
object's stageField: ObjectSchema refuses a stage field whose option declares
no kind, and nothing in Studio could write one. `kind` is now an owned key
with a picker (no kind / open / won / lost) on single-value select and radio
fields only; a multi-value field draws no picker and writes no kind, a retype
to one drops the kinds, an out-of-vocabulary kind is reported as a malformed
row, and the inspector says what the lifecycle field needs.

Claude-Session: https://claude.ai/code/session_01JQwxbX9Xgog3zJyBcPdC2R
Co-authored-by: Claude <noreply@anthropic.com>
…raft, and the i18n patch changeset

Claude-Session: https://claude.ai/code/session_01JQwxbX9Xgog3zJyBcPdC2R
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Oct 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · seat domain:ui#1 · session_01JQwxbZeBv2y91towxasSUT · head 7a53b40d · 2026-10-27T22:08Z

席位意见:选项编辑器现在把 kind 当作自有字段来写,词表取自 spec 的枚举;只在单值 select/radio 上画出并写入,改成多值类型时丢弃,未知取值按畸形行原样保留。

  1. Shape: Fixes #26729; 5 files, +443/−8; source is ObjectFieldInspector.tsx +127/−8, with one new test file (292 lines), +7 lines each in studio en/zh (the eight designer.field rows), and a patch changeset for @object-ui/i18n.
  2. Premise measured: the seat read the source diff on the head. Option.kind is typed SelectOptionKind, and OPTION_KINDS comes from SelectOptionSchema.shape.kind.unwrap().options, so the picker cannot drift from the spec. classifyOption and patchOptions move together. kind is written only when takesOptionKind(type, multiple) holds (SINGLE_OPTION_TYPES and multiple !== true). "No kind" removes the key, and withoutOptionKinds drops kinds on a retype to a multi-value type. An out-of-vocabulary kind classifies as kind-not-valid and is kept verbatim. The stageKinds hint shows only while the object names this field as stageField and an option lacks a kind. Dev: new file 16/16, ending at ObjectSchema accepting stageField (control refused at fields.status.options.0-2.kind); 527/527 affected files; ablation of the patchOptions write turned 5 of 16 red, restored to blob 78f4eac6.
  3. Gates / check-runs: PR check-runs on 7a53b40d: 43 success, 9 skipped, 0 failure (the scripts/ tests included). Dev's local run: 34 derived, 34 run, 0 NOT-MEASURED; app-shell and i18n type-check exit 0.
  4. Accepted deviations: vitest run scripts/ exited 1 twice locally from the 15 s budget on two whole-tree scan tests (a different one each run, both green alone); the PR's own check-runs are the verdict. The optional ObjectSettingsPanel picker was not added, as the order allowed.
  5. Out of scope: none filed; the scan-test timeout note stays unfiled until CI shows it. Skills-docs: none — UI editor only.
  6. Landing: not Tier H; the seat readies and arms auto-merge.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 27, 2026 22:09
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 27, 2026 22:09
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit 5ec398d Oct 27, 2026
53 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-9mghmx branch October 27, 2026 22:28
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/m tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

2 participants