Repository navigation
fix(plugin-approvals): the approval request Timeline shows each step by its label, not the node id - #25526
Conversation
…ode id The sys_approval_request page's Timeline related list asked for `step_name` (the Approval node's machine id, such as `dual_signoff`) where every sys_approval_action row also stores `step_label`, the step's label. Swap the column, and pin the choice: the existing field-existence check cannot tell the two apart, since both are declared fields. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016g7nYP9wdNx1GDa5XDr7Uy
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 8d73133a7b6070d323667d397be3975bd0e879c7 && git checkout 8d73133a7b6070d323667d397be3975bd0e879c7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 82f5a111b464cd9bfe5999d8bc0befbfdf0708b7 4e80cfeeaf7de44ac7a87d4c5cd4512b3e1a31e3 && git checkout -B drift-repro 82f5a111b464cd9bfe5999d8bc0befbfdf0708b7 && git merge --no-ff 4e80cfeeaf7de44ac7a87d4c5cd4512b3e1a31e3
node scripts/docs-audit/affected-docs.mjs --json 82f5a111b464cd9bfe5999d8bc0befbfdf0708b7
|
席位核对 · ACCEPT · 2026-10-23T19:46Z
Generated by Claude Code |
Requested by Sales · project thread
Fixes #25501
Clause-②: no
Metadata-protocol: none
What was wrong
The
sys_approval_requestrecord page (packages/plugins/plugin-approvals/src/sys-approval-request.page.ts) has a Timeline tab. That tab is arecord:related_listoversys_approval_action, and its columns asked forstep_name. Every action row stores two step fields, both declared onsys-approval-action.object.ts:step_name: "Machine name of the step at the time of the action". Every writer inapproval-service.tssets it to the Approval node id (step_name: nodeId, orraw.flow_node_id ?? raw.current_step).step_label: "Label of the step at the time of the action".ApprovalService.actionRowwrites it on every row from the request'scurrent_step_label, anddisplay-label-backfill.tsfills it on older rows.So every Timeline row showed the node id (
dual_signoff), while the highlights strip right above it showed the label (current_step_label). The object's own list views (recent,by_actor,all_actions) already usestep_label.The card's premise holds with one correction:
step_nameis not missing. It is a declared, stored field that holds the machine id, so the column named a real field. It was the wrong one of the two.The change
step_nameis nowstep_label. Nothing else on the page changes, and the page's doc comment says why the step column isstep_label.packages/specis not touched.created_at,action,actor_id,commentandattachmentsand its sort fieldcreated_atare allsys_approval_actionfields. The highlights strip'sprocess_label,status,current_step_label,submitter_idandupdated_atare allsys_approval_requestfields, and none of them is a machine id where a label exists.Tests
src/sys-approval-request-page.test.tsgainsnames each Timeline step by its label, step_label, never by the node's machine id, step_name. It reads the page's Timeline columns and asserts two things:step_labelis listed andstep_nameis not, andstep_labelis the declaredApproval Steptext field. A control asserts thatstep_namestays a declared field.The file's existing test
every field the page names exists on its objectalready checks that each Timeline column names a field ofsys_approval_action. It stayed green on the defect, becausestep_nameis a declared field. That is why this test pins the choice, the way the strip'scurrent_step_labelis already pinned.Ablation, at head
4e80cfeeaf, under the verify lock.node scripts/ablation-replace.mjs --file packages/plugins/plugin-approvals/src/sys-approval-request.page.ts --anchor "'actor_id', 'step_label', 'comment'" --replacement "'actor_id', 'step_name', 'comment'" -- pnpm --filter @objectstack/plugin-approvals exec vitest run --maxWorkers=2 src/sys-approval-request-page.test.ts:mutation landed: anchor 1 -> 0, blob 1b31bf215cb3 -> 48f41780fd75× names each Timeline step by its label, step_label, never by the node's machine id, step_nameandTests 1 failed | 19 passed (20). Only the new test went red, and the existence test stayed green, as described above.restored: blob == HEAD (1b31bf215cb3) and git diff HEAD is emptyThe test imports the page from
srcthrough a relative import, so the ablation needs nodist/rebuild.Local verification (head
4e80cfeeaf)pnpm --filter '@objectstack/plugin-approvals^...' build(dependency closure): VERDICT command-exit 0.pnpm --filter @objectstack/plugin-approvals test:Test Files 95 passed (95),Tests 1234 passed (1234).pnpm --filter @objectstack/plugin-approvals typecheck: exit 0, andcheck:test-typecheck: OK.node scripts/pm/dispatch-gates.mjs --commandsderived 69 commands. 68 exit 0.pnpm check:i18nexited 3 at first because its prerequisite build was missing; after its named build closure it gavecheck-i18n-bundles: OK.pnpm check:dual-build-cjs-loadsexited 3: PREREQUISITE NOT MET, because it needs a whole-workspacepnpm build. NOT MEASURED locally; CI runs it.--ran:69 derived famil(ies) accounted for — 68 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3).Acceptance notes
sys_approval_actionobject itself still names the step bystep_namein three places that this card does not cover:titleFormat('{action} · {step_name}');display_titleformula, the record title (nameField);highlightFields.So an action record's own title and strip read for example
approve · dual_signoff. This is not filed: it is a separate surface (the action record's own page and title, not the request page's Timeline), and changing a formula's record title has a wider reach than one column. Carrier: none.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_016g7nYP9wdNx1GDa5XDr7Uy