Skip to content

feat(plugin-detail): the default record page lets an editor advance the stage from the path - #30221

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-30075-default-page-stage-advance
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-30075-default-page-stage-advance

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #30075

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:没有自定义页面的默认记录页上,有权编辑阶段字段的用户可以直接点击阶段路径推进记录(先弹确认框);状态机不允许到达的阶段不再显示为可点击。
  • 为什么改:推进商机阶段是 CRM 最高频的更新,原来要「编辑 → 改阶段 → 更新」三步;裁决选方案 A,不新增任何元数据键。
  • 风险与回滚:风险是误点,已有二次确认;保存仍走普通记录更新,校验与状态机由服务端判定,被拒时就地显示原因。回滚即还原 buildDefaultPageSchema.ts 里那一个 allowStageChange: true。
  • 先看:ui/packages/plugin-detail/src/synth/buildDefaultPageSchema.ts(默认页写入开关的那一行);ui/packages/plugin-detail/src/renderers/recordPathStageChange.tsx(admits,按状态机收窄可点阶段);ui/packages/plugin-detail/src/renderers/record-path.tsx(每个阶段是否渲染为按钮)。
  • 两分钟验证:pnpm dev:crm 后以管理员打开任一商机记录页,点击下一个阶段,应弹出「Move this record to …?」确认框,确认后阶段前移;以只读用户打开同一页,阶段路径不可点击。
  • 需要你做的:无,席位落地。

What changes

The landing point is the producer of the default record page, buildDefaultHighlights in ui/packages/plugin-detail/src/synth/buildDefaultPageSchema.ts: the record:path node it emits now carries allowStageChange: true, the existing spec key (RecordPathProps.allowStageChange). No key is added and nothing under packages/** is edited. The console's record page (RecordDetailView) renders that synthesized page whenever no page is assigned, so this is where "a default record page" is built.

Who may press is unchanged and decided entirely by the existing opt-in (useStageChange): the page's edit session may edit the record, the stage field is not read-only, computed or a system field, and the caller's field edit grant holds for the stored row. Everyone else, and an editor whose inline edit holds unsaved changes, keeps the read-only path. Every press opens a confirmation, then saves through the record's ordinary update with the record version (updateVia, the inline save bar's path), so the server's validation and state_machine rule judge it; a refusal is shown under the path.

One change to the renderer, made because the default page now puts the press in front of every app: a stage the stage field's blocking state_machine cannot reach from the stored value is drawn as a readout, not a button. It reads the same stateMachineNextValues the record form, the grid's inline editor and the board use, so the path offers exactly the moves the edit dialog offers. A field without such a rule offers every stage, and the server still judges each write. This applies to every path with the opt-in, authored ones included.

A declared page keeps its own setting: an assigned page writes its own record:path node and never reaches the synthesizer, and an authored highlights slot replaces the default strip, so its true, false or absent key is kept as written (pinned below).

record-path.inertReadout.test.tsx encodes the 2026-08-23 "separate card" ruling. Its assertions are unchanged: every mount there is a node without the opt-in, which is still the readout. Only its docblock changes, to say that the separate card landed as the opt-in and that the default page now carries it, so that file's pins now describe authored pages that leave the key out or set it false.

Tests

New pins, as two top-level describe blocks in the existing shared harness record-path.stageChange-28199.test.tsx. The synthesized node is mounted as the JSON document buildDefaultPageSchema emits, through the real SchemaRenderer:

  1. The synthesized page writes allowStageChange: true on its path node.
  2. An editor gets a press on each other stage; nothing is written before the confirmation; confirming writes { status: 'in_review' } with the record version and re-reads.
  3. A reader the page may not let edit the record keeps the read-only path; so does a reader without the field edit grant.
  4. An authored highlights slot keeps allowStageChange: true, false, and the key left out, each rendered accordingly.
  5. With a blocking state_machine (draft to in_review or declined only), Accepted is a readout and not a button. A warning-severity machine does not narrow the press (control).
  6. A move the server still refuses for a required field elsewhere shows Amount: is required ... under the path; the current stage and the record are unchanged.

The card listed the fill-in path (a stage move that needs another required field) as not measured. Measured: the press refuses in place and names the missing field by label (pin 6, client half). The server half is the existing objectql pin validate-update-stored-row.test.ts (b): an update whose patch turns a requiredWhen true without supplying the field answers VALIDATION_FAILED with fields: [{ field: 'note', code: 'required' }] and leaves the row unchanged (18/18 green on this branch). There is no in-place fill-in; the reader opens the edit dialog to supply the field. Nothing worse than #28303's refusal in place was found.

Ablation (each through scripts/ablation-replace.mjs, which checks the mutation landed and restores to the HEAD blob; the subjects resolve to source, so no rebuild is involved):

  • Removing allowStageChange: true from the synthesizer fails 5 of 24 in the harness (pins 1, 2, 5 and its control, 6). Restored, blob equals HEAD.
  • Removing && stageChange.admits(o.stage.value) from the renderer fails exactly pin 5 (1 of 24). Restored, blob equals HEAD. A first attempt with the anchor ending in a bare ; was refused by the tool (its replacement count could not move) and changed nothing; the anchor was respelled.
Test runs
  • vitest run --isolate packages/plugin-detail/ (whole package, isolated as the CI shard runs it): Test Files 369 passed, 1 skipped (370); Tests 3270 passed, 18 skipped. This ran before origin/main was merged in; the merge changed nothing in these files.
  • record-path.stageChange-28199.test.tsx verbose: 24/24, including the 10 new #30075 cases.
  • record-path* + synth/: 30 files, 320 tests passed.
  • packages/objectql validate-update-stored-row.test.ts: 18/18.

Acceptance notes

  • The registry input description in ui/packages/plugin-detail/src/index.tsx and the spec .describe() still say the key's default is off. Both describe the key, and the key's default has not changed: the default page sets it explicitly. They are left as they are, so sdui.manifest.json and the spec docs are not regenerated.
  • Studio's page preview (PagePreview) renders the same synthesized page. Its preview host mounts no edit session, so the path stays a readout there.

Verification

Local gate families derived by node scripts/pm/dispatch-gates.mjs --commands on this branch. Results and the commit they ran against are listed in the os-dev report comment on #30075. CI runs the full set.


Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01QSeKYpFBMr917PmVkG9up7

…ffering only state-machine-admitted moves

Claude-Session: https://claude.ai/code/session_01QSeKYpFBMr917PmVkG9up7
Co-authored-by: Claude <noreply@anthropic.com>
…n; assertions unchanged

Claude-Session: https://claude.ai/code/session_01QSeKYpFBMr917PmVkG9up7
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 Nov 5, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 02:41
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 5, 2026 02:41
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 5fa9375 Nov 5, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-30075-default-page-stage-advance branch November 5, 2026 02:56
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

Development

Successfully merging this pull request may close these issues.

[Decision] Click a stage to advance a deal: the capability exists but a default record page cannot turn it on

1 participant