Repository navigation
Commit be25e2d
feat(plugin-detail): record:path stage press moves the record when allowStageChange is set (#28303)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoApKXpwrRwrv5gg6ftdt8KE)_
Fixes #28199
Refs #28061
Clause-②: yes
Metadata-protocol: none
## 维护者速读
- 改了什么:`record:path` 设了 `allowStageChange: true`
后,有权编辑状态字段的用户可以点击阶段(当前阶段除外),确认后把记录移到该阶段;没设这个键或无编辑权的用户,看到的仍是原来的只读进度条。
- 为什么改:HotCRM 销售测试反馈要能在路径上直接推进阶段(#27355 第 1 行);spec 已在 #28198
声明了这个键,但控制台一直没读它,作者写了也没效果。
- 风险与回滚:只在作者显式开启时生效;写入走记录详情行内编辑同一条更新路径并带 `ifMatch`
并发令牌,合法与否由服务端状态机/校验/权限判定,前端不做第二套判定。回滚即 revert 本 PR,未开启的页面零影响。
-
先看:`ui/packages/plugin-detail/src/renderers/recordPathStageChange.tsx`(谁能点、写入、拒绝文案)、`ui/packages/plugin-detail/src/renderers/record-path.tsx`(阶段按钮与就地拒绝区)、`ui/packages/plugin-detail/src/index.tsx`(注册表新增输入)。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/plugin-detail/src/renderers/__tests__/record-path.stageChange-28199.test.tsx`,预期
14 个用例全过。
- 需要你做的:无,席位落地。
## What changes
- **`record:path` honours `allowStageChange`.** With `allowStageChange:
true`, each stage other than the current one carries a real `button`
(accessible name "Move to STAGE") inside its existing `listitem`, on
both the desktop and the phone row. A press opens a confirmation
(`AlertDialog`); confirming writes `{ [statusField]: value }` and
re-reads the record. Without the key, or with `false`, the DOM is the
readout it was.
- **Who can press** (`useStageChange`, new
`renderers/recordPathStageChange.tsx`): the record is stored and its
data source can update; the record page's edit session allows editing
the record (`useInlineEdit().canEdit`, the page host's record-level
verdict); the status field is not read-only, computed or a system field;
the caller's `recordEdit` field grant holds (`resolveFieldAffordance`).
`RecordPathRenderer` now mounts `RecordFieldAccessForRecord` around the
body when the key is on, so that grant is asked of this stored row, as
`record:details` and `record:highlights` ask it. While the edit session
holds an inline draft, the path is a readout again.
- **One update path.** The write goes through `updateVia` (now exported
from `InlineEditSaveBar.tsx`, not from the package index), the function
the inline-edit save bar uses, with the record's `updated_at` as
`ifMatch`.
- **Refusals in place** (`stageRefusalLines`), under the rails, once for
both rows: a `CONCURRENT_UPDATE` 409 gets a fixed line and the record is
re-read; a producer-marked `userMessage` verbatim; a field-scoped
refusal (`VALIDATION_FAILED` `fields[]`, the shape an illegal
`state_machine` transition and a missing required field arrive in) one
line per entry, an entry about another field prefixed with its label; a
permission refusal the delegated-scope reason or a fixed line; anything
else the server message or a fixed line. The next press clears it.
- **Published.** `allowStageChange` joins the block's registry `inputs`
(boolean, renderer-accurate description), so `sdui.manifest.json`,
`scripts/sdui-manifest.record.json` and the generated component
reference (`content/docs/ui/components/record.mdx`, `index.mdx`) are
regenerated, and the `UNPUBLISHED_EXEMPTIONS` entry
`record:path.allowStageChange` is deleted from
`registry-inputs-spec-parity.test.ts`.
- **Copy.** Seven `detail.pathStageChange*` keys in all ten packs and in
`DETAIL_DEFAULT_TRANSLATIONS`.
- **Changeset.** `.changeset/28199-record-path-stage-change.md`,
`@object-ui/plugin-detail` and `@object-ui/i18n` patch.
## Why
The spec half (#28198) declared `RecordPathProps.allowStageChange`, and
the console registry carried it as unpublished because nothing read it.
Authors who set it got the read-only rail. This is the console half that
#28061 names.
## Tests
`ui/packages/plugin-detail/src/renderers/__tests__/record-path.stageChange-28199.test.tsx`,
14 cases:
- the press writes: buttons on every non-current stage in both rows;
confirm calls `update('crm_quote', 'q1', { status: 'in_review' }, {
ifMatch: VERSION })` and `refresh`; cancel writes nothing;
- a refusal renders in place: an illegal `state_machine` transition (the
server sentence alone), a required field elsewhere (label-prefixed), a
403 (fixed line, not the raw text), a stale token (fixed line plus a
re-read), and the next press clears it;
- the readout stays: the key absent (control, for a reader who could
edit), `false`, the page's edit session refusing the record, the field
edit grant denied, a read-only status field, an inline draft in
progress.
Ablations (one-shot, each through `scripts/ablation-replace.mjs`, which
proved the mutation landed and restored `git diff HEAD` empty; the
subject resolves to source through the vitest alias, so no rebuild
applies):
| Mutation in `recordPathStageChange.tsx` | Red |
| :--- | :--- |
| `ifMatch` token dropped from the update | the write case (1 of 14) |
| `allowStageChange` removed from the verdict | both opt-in controls (2
of 14) |
| `!inline.editing` removed | the inline-draft case (1 of 14) |
| `resolveFieldAffordance(...)` removed | the field-grant case (1 of 14)
|
| `setRefusal(...)` removed | all five refusal cases (5 of 14) |
## Acceptance notes
- **"Reachable" means "drawn and not current."** The path does not read
the object's `state_machine` transitions to hide stages: that would be a
second evaluator of a rule the server owns. Every drawn stage the record
is not on is pressable, and an illegal move comes back as the server's
refusal, shown in place. Stages a record's own columns rule out (option
`visibleWhen`) are already not drawn.
- **Inline draft.** While the record's inline edit holds a draft the
stages are not pressable, so a stage write never races the draft's Save.
Said here because the card did not ask for it.
- **Concurrency.** A 409 is not routed to `ConcurrentUpdateDialog`'s
overwrite: a stage move is one field, and the re-read record plus a
second press is the whole recovery.
- **File surface.** Beyond the claim's surface, the diff carries the
generated half of a registry change that root `AGENTS.md` requires ("A
registry change has a second half"): `sdui.manifest.json`,
`scripts/sdui-manifest.record.json`, and the generated reference pages
`content/docs/ui/components/record.mdx` and `index.mdx` (`node
scripts/gen-sdui-reference.mjs`, gated by `check:sdui-reference`). No
hand edit in any of them.
- **Clause-②: yes**, measured from the diff: the block's published
`inputs` gain a key (the manifest and the component reference grow by
one input). The claim recorded `no` as the seat's expectation; the
dispatch order says publishing the input counts.
- `ui/AGENTS.md` #2 (package README and docs): the docs page for this
input is the generated component reference
(`content/docs/ui/components/record.mdx`), regenerated here.
`plugin-detail`'s README covers `record:details` / `record:highlights` /
`record:quick_actions` / `record:related_list` binding and says nothing
about `record:path` today; it is outside the claimed surface and left
untouched — the seat may want a README line.
## Verification
Every reading below is on head `2837cf76` unless it says otherwise.
<details><summary>Tests (local, under the shared verify lock)</summary>
- `pnpm --dir ui exec vitest run packages/i18n/
packages/app-shell/src/__tests__/defaults-maps-mirror-en-pack.test.tsx
apps/console/src/__tests__/registry-inputs-spec-parity.test.ts
packages/plugin-detail/` on `bc74bc54` → Test Files 442 passed, 1
skipped (443); Tests 4842 passed, 31 skipped. The only later commit
types two object-definition reads in the new module.
- `pnpm --dir ui exec vitest run
packages/plugin-detail/src/renderers/__tests__/record-path.stageChange-28199.test.tsx`
→ 14 passed on `2837cf76`.
- `pnpm --filter @object-ui/plugin-detail run type-check` and `pnpm
--filter @object-ui/i18n run type-check` → exit 0 (full declaration
build of the closure).
- `eslint` on the changed `plugin-detail` files → 0 errors; the new
module adds no warning.
</details>
<details><summary>Derived gates</summary>
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` → 76 families; every one run with stdin
closed and its exit code recorded; `--ran` reconciliation: 76 derived,
76 run, 0 NOT-MEASURED, 0 UNRUN.
- On the first run (head `bc74bc54`) `check:skill-examples` answered
exit 3 (PREREQUISITE NOT MET: no `packages/spec` declarations) and exit
0 after a declaration build; on `2837cf76` all 76 answered exit 0 in one
pass.
- `node scripts/check-sdui-manifest.mjs` → exit 0 (present and intact,
recorded by the in-tree producer).
- `pnpm check:new-line-citations` (ui) → 0 new citations.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(`packages/spec` untouched.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (Changed files linted
locally; the repo-wide run is CI's.)
- [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 (the maintainer brief
above is Chinese by rule).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L9qibyyvCaZXvGAh38AKjD)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 7eda096 commit be25e2d
22 files changed
Lines changed: 691 additions & 40 deletions
File tree
- .changeset
- content/docs/ui/components
- scripts
- ui
- apps/console/src/__tests__
- packages
- i18n/src/locales
- plugin-detail/src
- renderers
- __tests__
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
122 | 122 | | |
123 | 123 | | |
124 | 124 | | |
125 | | - | |
| 125 | + | |
126 | 126 | | |
127 | 127 | | |
128 | 128 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
132 | 132 | | |
133 | 133 | | |
134 | 134 | | |
| 135 | + | |
135 | 136 | | |
136 | 137 | | |
137 | 138 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
16 | | - | |
17 | | - | |
| 16 | + | |
| 17 | + | |
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1805 | 1805 | | |
1806 | 1806 | | |
1807 | 1807 | | |
| 1808 | + | |
| 1809 | + | |
| 1810 | + | |
| 1811 | + | |
| 1812 | + | |
1808 | 1813 | | |
1809 | 1814 | | |
1810 | 1815 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1663 | 1663 | | |
1664 | 1664 | | |
1665 | 1665 | | |
1666 | | - | |
1667 | | - | |
1668 | | - | |
1669 | | - | |
1670 | | - | |
1671 | | - | |
1672 | 1666 | | |
1673 | 1667 | | |
1674 | 1668 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1464 | 1464 | | |
1465 | 1465 | | |
1466 | 1466 | | |
| 1467 | + | |
| 1468 | + | |
| 1469 | + | |
| 1470 | + | |
| 1471 | + | |
| 1472 | + | |
| 1473 | + | |
1467 | 1474 | | |
1468 | 1475 | | |
1469 | 1476 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1306 | 1306 | | |
1307 | 1307 | | |
1308 | 1308 | | |
| 1309 | + | |
| 1310 | + | |
| 1311 | + | |
| 1312 | + | |
| 1313 | + | |
| 1314 | + | |
| 1315 | + | |
1309 | 1316 | | |
1310 | 1317 | | |
1311 | 1318 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1433 | 1433 | | |
1434 | 1434 | | |
1435 | 1435 | | |
| 1436 | + | |
| 1437 | + | |
| 1438 | + | |
| 1439 | + | |
| 1440 | + | |
| 1441 | + | |
| 1442 | + | |
1436 | 1443 | | |
1437 | 1444 | | |
1438 | 1445 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1347 | 1347 | | |
1348 | 1348 | | |
1349 | 1349 | | |
| 1350 | + | |
| 1351 | + | |
| 1352 | + | |
| 1353 | + | |
| 1354 | + | |
| 1355 | + | |
| 1356 | + | |
1350 | 1357 | | |
1351 | 1358 | | |
1352 | 1359 | | |
| |||
0 commit comments