You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
plugin-approvals: the approval request page's Timeline lists each action's step as the machine id (dual_signoff) — its related list asks for step_name, while every row stores step_label #25501
Filing-gate: ① product defect · reach: the served sys_approval_request record page, opened from the approvals inbox as admin
Reader: objectstack triage seat → the lane that owns packages/plugins/plugin-approvals; the fix is one column name in src/sys-approval-request.page.ts
Duplicate search: semantic search "approval request timeline related list step_name machine name instead of step_label" in objectstack, incl. closed → 6 hits: #25471 (open, a different surface: the record's Approvals tab, which objectui#13248 fixed), #24281 / #24264 / #24309 / #23889 / #24863 (closed: the highlights strip, the lists and the snapshot, not this related list). None covers the page's Timeline tab.
Measured during an exploratory showcase walkthrough on 2026-10-23: objectstack mainb9c7a8f (PORT=3000 pnpm dev, stock showcase), objectui main6fb31dc console (Vite dev, :5180), locale zh-CN.
What happens
Open an approval request, e.g. EXP-2001 in the showcase showcase_expense_signoff flow. The highlights strip correctly reads 审批步骤: 经理 + 财务签批 (the node label Manager + Finance Sign-off). Directly below it, the 时间线 / Timeline tab lists three rows, and the Step column of every row reads the raw node id:
2026/10/23 下午3:00 批准 Ada Auditor (demo) dual_signoff 财务同意
2026/10/23 下午2:58 批准 Dev Admin dual_signoff 经理同意
2026/10/23 下午2:35 提交 Mei Phone (demo) dual_signoff —
Where
packages/plugins/plugin-approvals/src/sys-approval-request.page.ts, the Timeline tab's record:related_list:
sys-approval-action.object.ts declares step_name as "Machine name of the step at the time of the action". Next to it, step_label ("Label of the step at the time of the action") is filled on every row, with the boot backfill covering older rows. So the page asks for the wrong one of the two columns.
Expected
The Timeline's Step column shows step_label, the same text the strip above it shows. That means swapping the column, plus whatever the translated-label contract from #24863 asks for.
Triage: lands in packages/plugins/plugin-approvals/src/sys-approval-request.page.ts (~L123, the Timeline record:related_list columns); rationale: the page asks for step_name where every row stores step_label; swap the column. One file, size S.
Path: workflow · 审批在界面里做 | approvals.record-page-decisions (P2) | step ②
Grade: priority:p2 — inherits the P2 item: the page renders, with the wrong text.
Type: Bug
Dedupe: #25471 is the record Approvals tab (objectui, fixed by objectui#13248), not this page; distinct.
Session: session_01AkmasAfXt3cu83xGzkuKhe (objectstack triage, run on the maintainer's request in the project thread)
Claim: PM loop round 1 · domain:services seat 1 (seat post #6021, project run by the Sales account) · S mechanical card, in-seat subagent · 2026-10-23T18:22Z
Session: session_016g7nYP9wdNx1GDa5XDr7Uy
Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-25501-approval-timeline-step-label
Worktree: objectstack-issue-25501
Domain: domain:server
Seat: domain:services#1
File surface: packages/plugins/plugin-approvals/src/sys-approval-request.page.ts, the Timeline record:related_list column step_name → step_label, plus its pin and a changeset. ⛔ packages/spec is not edited. Stop on breach; explain in the report.
Container & model: S 级机械卡, mode:subagent (general-purpose subagent loading .claude/agents/os-dev.md as its role file), model: default tier, effort: medium
Clause-②: no
Metadata-protocol: none
Responsibility: plugin-approvals' own record page asks for a column no row stores | the Timeline shows the machine id dual_signoff | an approver or admin reading the request page, today
Thread-read: none
Serial constraints cleared: no open PR touches sys-approval-request.page.ts at claim (13 open PRs read at 2026-10-23T18:15:00Z on 842a0aef9d).
Resume-from: none
{
"issue": 25501,
"status": "done",
"branch": "claude/issue-25501-approval-timeline-step-label",
"pr": "https://github.com/objectstack-ai/objectstack/pull/25526",
"session": "session_016g7nYP9wdNx1GDa5XDr7Uy",
"premise_still_valid": true,
"summary": "Base origin/main dc3e5b9d7b. The Timeline record:related_list on the sys_approval_request page (packages/plugins/plugin-approvals/src/sys-approval-request.page.ts L123) asked for column step_name; it now asks for step_label, and the page doc comment says why. Measured on sys-approval-action.object.ts and approval-service.ts: both fields are declared and stored. step_name is the Approval node id (every writer sets step_name: nodeId or raw.flow_node_id ?? raw.current_step). step_label is written on every row by ApprovalService.actionRow from the request's current_step_label and backfilled by display-label-backfill.ts. So the premise holds with one correction: step_name is not absent, it is the machine id. Every other field the page names exists on its object and is not a machine id where a label exists: the Timeline's created_at/action/actor_id/comment/attachments and its sort field created_at, and the strip's process_label/status/current_step_label/submitter_id/updated_at. Landing is the claimed file; packages/spec is untouched. CI converged all green on head 4e80cfeeaf.",
"tests": "Added 'names each Timeline step by its label, step_label, never by the node's machine id, step_name' to src/sys-approval-request-page.test.ts. It reads the page's Timeline columns, asserts step_label is present and step_name is absent, asserts step_label is the declared 'Approval Step' text field, and keeps step_name declared as a control. The file's existing test 'every field the page names exists on its object' already asserts that each Timeline column names a field of sys_approval_action; it is green on the defect too, because step_name is a declared field, so it cannot pin this. Ablation at 4e80cfeeaf via scripts/ablation-replace.mjs under the verify lock (anchor \"'actor_id', 'step_label', 'comment'\" -> step_name): 'mutation landed: anchor 1 -> 0, blob 1b31bf215cb3 -> 48f41780fd75'; the new test failed, 'Tests 1 failed | 19 passed (20)', and only the new test went red; then 'restored: blob == HEAD (1b31bf215cb3) and git diff HEAD is empty'. No dist rebuild was needed because the test imports the page from src through a relative import. The first ablation attempt was a lock queue-timeout (exit 99, never acquired, file untouched, git diff HEAD empty); it was retried under the same slot. pnpm --filter @objectstack/plugin-approvals test at 4e80cfeeaf: 'Test Files 95 passed (95)', 'Tests 1234 passed (1234)'. pnpm --filter @objectstack/plugin-approvals typecheck: exit 0, 'check:test-typecheck: OK'. Lock VERDICT command-exit 0.",
"gates": "Local, at 4e80cfeeaf: dependency-closure build pnpm --filter '@objectstack/plugin-approvals^...' build, VERDICT command-exit 0. node scripts/pm/dispatch-gates.mjs --commands derived 69 commands, each run with its exit code captured before any pipe. 67 exited 0. pnpm check:i18n exited 3 (its prerequisite, the CLI build, was missing); after its named turbo build closure it exited 0 with 'check-i18n-bundles: OK'. pnpm check:dual-build-cjs-loads exited 3, PREREQUISITE NOT MET (it needs a whole-workspace build): NOT MEASURED locally. --ran with 'cmd :: exit N' records: '69 derived famil(ies) accounted for — 68 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)'. CI on PR head 4e80cfeeaf, converged: 34 success, 7 skipped, 0 failing. Required: Lint & Repo Gates success, TypeScript Type Check success, Test Core success (shards 1-6 all success), Build Core success, Governed Surface Queue Guard success. Also Check Changeset success, Dogfood Regression Gate success, Lint · PM dispatch-gates success, Type Check · workspace/source/consumer/debt success, The card this PR closes must claim this branch success. Skipped by their selectors: Console Pin Gate, Dogfood shards, Dogfood Verify CLI, Temporal Conformance, Build Docs, the opt-in upgrade harness and tarball smoke.",
"dogfood_battery": "n/a — no ! in the PR title and Clause-②: no carries no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "2 relay strokes. (1) fleet-write dispatch.mjs pr_create, run 40010783271: POST /repos/objectstack-ai/objectstack/pulls (draft, #25526) plus the POST /repos/objectstack-ai/objectstack/issues/25526/assignees leg (os-sales); read-back said 5115 bytes sent and 5115 stored, identical. (2) post-stamped.mjs: POST /repos/objectstack-ai/objectstack/issues/25501/comments, this os-dev-report. Not REST: git push of the branch (empty probe, then 4e80cfeeaf).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted in the PR's Acceptance notes, not filed. The sys_approval_action object itself still names the step by step_name in its titleFormat ('{action} · {step_name}'), its display_title formula (the nameField record title) and its highlightFields. So an action record's own title and strip read for example 'approve · dual_signoff'. This is a separate surface (the action record's own page and title, not the request page's Timeline), and the reach was not measured through a public door, so it is not a class a/b/c filing. dedupe words: sys_approval_action display_title step_name, approval action record title machine id, approval action highlightFields step_label"
]
}
Activity
objectstack-fleet commentedon Oct 23, 2026
Triage: lands in
packages/plugins/plugin-approvals/src/sys-approval-request.page.ts(~L123, the Timelinerecord:related_listcolumns); rationale: the page asks forstep_namewhere every row storesstep_label; swap the column. One file, size S.Path: workflow · 审批在界面里做 | approvals.record-page-decisions (P2) | step ②
Grade:
priority:p2— inherits the P2 item: the page renders, with the wrong text.Type: Bug
Dedupe: #25471 is the record Approvals tab (objectui, fixed by objectui#13248), not this page; distinct.
Session:
session_01AkmasAfXt3cu83xGzkuKhe(objectstack triage, run on the maintainer's request in the project thread)Generated by Claude Code
objectstack-fleet commentedon Oct 23, 2026
Claim: PM loop round 1 ·
domain:servicesseat 1 (seat post #6021, project run by the Sales account) · S mechanical card, in-seat subagent · 2026-10-23T18:22ZSession:
session_016g7nYP9wdNx1GDa5XDr7UyAccount:
os-sales(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-25501-approval-timeline-step-labelWorktree:
objectstack-issue-25501Domain:
domain:serverSeat:
domain:services#1File surface:
packages/plugins/plugin-approvals/src/sys-approval-request.page.ts, the Timelinerecord:related_listcolumnstep_name→step_label, plus its pin and a changeset. ⛔packages/specis not edited. Stop on breach; explain in the report.Container & model:
S 级机械卡,mode:subagent(general-purpose subagent loading.claude/agents/os-dev.mdas its role file),model: default tier,effort: mediumClause-②: no
Metadata-protocol: none
Responsibility: plugin-approvals' own record page asks for a column no row stores | the Timeline shows the machine id
dual_signoff| an approver or admin reading the request page, todayThread-read: none
Serial constraints cleared: no open PR touches
sys-approval-request.page.tsat claim (13 open PRs read at 2026-10-23T18:15:00Z on842a0aef9d).Resume-from: none
objectstack-fleet commentedon Oct 23, 2026
os-dev-report
{ "issue": 25501, "status": "done", "branch": "claude/issue-25501-approval-timeline-step-label", "pr": "https://github.com/objectstack-ai/objectstack/pull/25526", "session": "session_016g7nYP9wdNx1GDa5XDr7Uy", "premise_still_valid": true, "summary": "Base origin/main dc3e5b9d7b. The Timeline record:related_list on the sys_approval_request page (packages/plugins/plugin-approvals/src/sys-approval-request.page.ts L123) asked for column step_name; it now asks for step_label, and the page doc comment says why. Measured on sys-approval-action.object.ts and approval-service.ts: both fields are declared and stored. step_name is the Approval node id (every writer sets step_name: nodeId or raw.flow_node_id ?? raw.current_step). step_label is written on every row by ApprovalService.actionRow from the request's current_step_label and backfilled by display-label-backfill.ts. So the premise holds with one correction: step_name is not absent, it is the machine id. Every other field the page names exists on its object and is not a machine id where a label exists: the Timeline's created_at/action/actor_id/comment/attachments and its sort field created_at, and the strip's process_label/status/current_step_label/submitter_id/updated_at. Landing is the claimed file; packages/spec is untouched. CI converged all green on head 4e80cfeeaf.", "tests": "Added 'names each Timeline step by its label, step_label, never by the node's machine id, step_name' to src/sys-approval-request-page.test.ts. It reads the page's Timeline columns, asserts step_label is present and step_name is absent, asserts step_label is the declared 'Approval Step' text field, and keeps step_name declared as a control. The file's existing test 'every field the page names exists on its object' already asserts that each Timeline column names a field of sys_approval_action; it is green on the defect too, because step_name is a declared field, so it cannot pin this. Ablation at 4e80cfeeaf via scripts/ablation-replace.mjs under the verify lock (anchor \"'actor_id', 'step_label', 'comment'\" -> step_name): 'mutation landed: anchor 1 -> 0, blob 1b31bf215cb3 -> 48f41780fd75'; the new test failed, 'Tests 1 failed | 19 passed (20)', and only the new test went red; then 'restored: blob == HEAD (1b31bf215cb3) and git diff HEAD is empty'. No dist rebuild was needed because the test imports the page from src through a relative import. The first ablation attempt was a lock queue-timeout (exit 99, never acquired, file untouched, git diff HEAD empty); it was retried under the same slot. pnpm --filter @objectstack/plugin-approvals test at 4e80cfeeaf: 'Test Files 95 passed (95)', 'Tests 1234 passed (1234)'. pnpm --filter @objectstack/plugin-approvals typecheck: exit 0, 'check:test-typecheck: OK'. Lock VERDICT command-exit 0.", "gates": "Local, at 4e80cfeeaf: dependency-closure build pnpm --filter '@objectstack/plugin-approvals^...' build, VERDICT command-exit 0. node scripts/pm/dispatch-gates.mjs --commands derived 69 commands, each run with its exit code captured before any pipe. 67 exited 0. pnpm check:i18n exited 3 (its prerequisite, the CLI build, was missing); after its named turbo build closure it exited 0 with 'check-i18n-bundles: OK'. pnpm check:dual-build-cjs-loads exited 3, PREREQUISITE NOT MET (it needs a whole-workspace build): NOT MEASURED locally. --ran with 'cmd :: exit N' records: '69 derived famil(ies) accounted for — 68 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)'. CI on PR head 4e80cfeeaf, converged: 34 success, 7 skipped, 0 failing. Required: Lint & Repo Gates success, TypeScript Type Check success, Test Core success (shards 1-6 all success), Build Core success, Governed Surface Queue Guard success. Also Check Changeset success, Dogfood Regression Gate success, Lint · PM dispatch-gates success, Type Check · workspace/source/consumer/debt success, The card this PR closes must claim this branch success. Skipped by their selectors: Console Pin Gate, Dogfood shards, Dogfood Verify CLI, Temporal Conformance, Build Docs, the opt-in upgrade harness and tarball smoke.", "dogfood_battery": "n/a — no ! in the PR title and Clause-②: no carries no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 relay strokes. (1) fleet-write dispatch.mjs pr_create, run 40010783271: POST /repos/objectstack-ai/objectstack/pulls (draft, #25526) plus the POST /repos/objectstack-ai/objectstack/issues/25526/assignees leg (os-sales); read-back said 5115 bytes sent and 5115 stored, identical. (2) post-stamped.mjs: POST /repos/objectstack-ai/objectstack/issues/25501/comments, this os-dev-report. Not REST: git push of the branch (empty probe, then 4e80cfeeaf).", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted in the PR's Acceptance notes, not filed. The sys_approval_action object itself still names the step by step_name in its titleFormat ('{action} · {step_name}'), its display_title formula (the nameField record title) and its highlightFields. So an action record's own title and strip read for example 'approve · dual_signoff'. This is a separate surface (the action record's own page and title, not the request page's Timeline), and the reach was not measured through a public door, so it is not a class a/b/c filing. dedupe words: sys_approval_action display_title step_name, approval action record title machine id, approval action highlightFields step_label" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 23, 2026
Landed ·
domain:servicesseat 1 (seat post #6021) ·os-sales·session_016g7nYP9wdNx1GDa5XDr7Uy· 2026-10-23T20:10Z6373367068), head4e80cfeeaforigin/mainb8c959013bState:
Fixes #25501closed the card.pm:dispatchedis now off and the assignee is cleared.Generated by Claude Code