Skip to content

fix(app-shell): the approval step line draws the route the request's run took, not the flow graph's first branch - #26666

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-v6vydb
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-v6vydb

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread
Fixes #26563

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:审批步骤线不再画服务端 flow_steps(流程图从起点沿 approve/true/第一条边走出的那一条主干),改为只画本次流程运行真实走过的步骤:同一 flow_run_id 下已通过的审批请求,按到达先后排列,最后是当前待审步骤;当前步骤之后一概不画。业务记录的审批面板照此绘制;审批请求页只持有自身这一行,证明不了任何已走步骤,所以暂不画步骤线。
  • 为什么改:hotclm 的合同审批走「直属上级 → 仅财务」时,步骤线却写着「直属上级 · 法务负责人 + 财务负责人(会签) · 分管领导 · 总经理」,审批人被告知要与法务会签、后面还有两级,实际都不会发生。分诊裁定:⛔ 永不画猜测的路径。
  • 风险与回滚:纯 UI 显示改动,无契约、无元数据、无服务端改动。代价是过渡期内审批请求页不再显示步骤线,业务记录面板上看不到自己未参与的步骤(列表读只对参与者开放)。回滚 = revert 本 PR。
  • 先看:① ui/packages/app-shell/src/views/RecordApprovalsPanel.tsx 新增的 visitedApprovalSteps(哪些请求算「已走过」);② ui/packages/app-shell/src/views/approval-decision/ApprovalDecisionPanel.tsx 删去的那一行步骤线;③ RecordApprovalsPanel.visitedRoute-26563.test.tsx。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/RecordApprovalsPanel.visitedRoute-26563.test.tsx,预期 11 个测试全绿,其中报告的路由只画出「直属上级、财务负责人」两步。
  • 需要你做的:无,席位落地。

What changes

The approval step line drew the request's flow_steps. That field is built by plugin-approvals' attachFlowSteps, which walks the flow graph from its start node and takes the approve edge, else the true edge, else the first edge at each node. It marks done, current and upcoming against that one trunk. It is not the route the request takes. A request that a decision sent down another branch was drawn on rungs it never visits. When the request's own node is off the trunk, it is drawn on none of the trunk's rungs as current. A rung reached after a side branch rejoins showed the trunk's branch as done.

Measured first, as the triage asked: the single request read (GET /api/v1/approvals/requests/:id) carries neither a resolved route nor the visited nodes. Beyond flow_steps, the row names only its own node (flow_node_id, current_step, step_label) and its run (flow_run_id). So the route read is a plugin-approvals child (see Acceptance notes), and this PR draws only what a request row proves:

  • Business record approvals panel. visitedApprovalSteps(pendingRequest, requests) builds the line from the request rows the panel already holds (the list read, no new request). A passed step is a request with the same flow_run_id, on another node, approved, and opened before the pending one. A node approved in several rounds is drawn once, at its latest round. The pending request comes last. Nothing ahead of it is drawn, because no request row says which branch the run takes next.
  • Approval request page (record:approval_decision). The decision panel no longer draws a step line. Its bound row proves only its own step, which the page header already names, and one step is no line.
  • ApprovalRequestLite declares the served flow_run_id and flow_node_id. The flow_steps doc says why no surface draws it.
  • ApprovalStepLine's markup is unchanged. Its header now says the caller owns the proof of the steps it hands in, and the stale paragraph about the inbox sheet's copy of the stepper is gone (that sheet no longer draws one).
  • The app-shell README's two approval paragraphs say the same.

Tests

  • New RecordApprovalsPanel.visitedRoute-26563.test.tsx, 11 tests. The panel cases use the reported route (manager → finance only) with the served trunk attached. They pin a branch that rejoins the trunk (manager → legal only → executive), a request at the run's first rung, and a viewer served only the pending row. The unit cases for visitedApprovalSteps pin ordering, another run, a step not approved (returned, rejected), a row opened after the pending one, round dedup, an earlier round of the pending node, a decided request, an unlabeled step, and the controls for a missing run, node or open time.
  • RecordApprovalsPanel.stepProgressVertical.test.tsx: the layout contract is unchanged. Its fixtures are now a run's request rows, so they reach the line the way the panel builds it. The connector-tint case now pins that every segment is green, because the line draws nothing ahead of the pending step.
  • ApprovalDecisionPanel.stepLine-12491.test.tsx: rewritten to pin the absence of the line. The reported row with its served trunk draws no line and none of the trunk's rungs, a decided row draws none either, and the tally still draws above the decisions. The deleted layout cases duplicate the ones RecordApprovalsPanel.stepProgressVertical.test.tsx pins over the same ApprovalStepLine markup.
  • approvalRequestsDataSource.servedPageStepLine-12491.test.tsx (console, the real served page): the step-line cases now pin that no line and no other trunk rung is drawn, at pending, at decided, and for a person-submitted request. The flow-origin cases are unchanged.
Test readings, head 4743eb8 (all under scripts/pm/os-verify-lock.sh, run from ui/)
  • The four touched suites: vitest run over RecordApprovalsPanel.visitedRoute-26563, RecordApprovalsPanel.stepProgressVertical, ApprovalDecisionPanel.stepLine-12491 and the console approvalRequestsDataSource.servedPageStepLine-12491: Test Files 4 passed (4), Tests 24 passed (24), VERDICT command-exit 0.
  • Every approval test file in packages/app-shell and apps/console (by file name, plus every test that names RecordApprovalsPanel, ApprovalDecisionPanel, ApprovalStepLine, useRecordApprovals, approval-decision or approval-progress): Test Files 68 passed (68), Tests 765 passed (765), VERDICT command-exit 0, 7m00s. The whole packages/app-shell suite (1757 test files) was not run locally; CI's Test UI shards run it.
  • pnpm --filter @object-ui/app-shell run type-check (both projects; tsconfig.test.json --listFilesOnly lists the three touched app-shell test files): VERDICT command-exit 0. pnpm --filter @object-ui/console run type-check (--listFilesOnly lists the touched console test): VERDICT command-exit 0.
Reverse verification, one-shot (no test file kept for it)
  • Record panel. node scripts/ablation-replace.mjs replaced the panel's step line steps={visitedApprovalSteps(pendingRequest, requests)} with the old steps={pendingRequest?.flow_steps}. The mutation landed: anchor 1 to 0, blob ddd69d0e14eb to 347c203eb6ff. RecordApprovalsPanel.visitedRoute-26563 then failed its 4 panel tests (Tests 4 failed, 7 passed; the 7 are the unit cases, which call visitedApprovalSteps directly). The tool restored the file: blob ddd69d0e14eb equal to HEAD, git diff HEAD empty.
  • Request page. ApprovalDecisionPanel.tsx was restored to the merge base blob (f61253e4b319, which still draws steps={request.flow_steps}) under an EXIT trap. The decision panel suite and the console served-page suite then failed 6 of 9 tests (Test Files 2 failed). The 3 still passing are the page-parse case, the flow-origin case and the one-step control. Restored to HEAD blob 4bf7bdb3f704, with git status clean.

Acceptance notes

  • Out of scope, for the seat to file (class a): the route read belongs to plugin-approvals. ApprovalService.attachFlowSteps serves the flow graph's trunk as flow_steps on every single read. For the reported request (node finance_only), every trunk entry is upcoming and its own node is absent. The child should serve the route the run took and only the steps ahead that the run's state determines. Then the request page can draw its line again, and visitedApprovalSteps can be deleted in favour of the served route. flow_steps' contract text in packages/spec/src/contracts/approval-service.ts reads "The owning flow's approval steps in graph order", so the child either changes that text or adds a field. Either way it is a contract change.
  • Viewer-scoped steps. The list read serves a request only to its participants (slate, submitter, override actor). On the business record a viewer who took no part in an earlier rung does not see it, so the line can be shorter than the run's route, never wrong. The finance controller in the report sees no line on the record panel (one step). The submitter sees the whole route.
  • Request page: no line rather than a one-step line. The seat suggested a one-step "current step" line there. A one-step line would repeat the step the page header already names, and ApprovalStepLine's own gate draws nothing under two steps, so the page draws none.
  • Steps not drawn. A rung a reject edge routes to an approval node, and a rung sent back for revision, are not "passed" (not approved), so they are left out. A route that passes through them shows only its approved rungs.
  • The upcoming styling in ApprovalStepLine has no caller today. It stays for the served route the child brings back.

Verification

Derived gate sweep, head 4743eb8

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 34 commands from the change set against merge base fb91e9793. Each command ran with its exit code captured before any pipe, and all 34 exited 0. check:type-check-debt ran under the verify lock, with VERDICT command-exit 0. The PR-body gates (check-changeset-no-major, check-closing-keyword-parity, check-adr-0087-registration) read this body through GITHUB_EVENT_PATH. dispatch-gates --ran reconciliation: ✓ 34 derived famil(ies) accounted for — 34 run, 0 NOT-MEASURED.

  • origin/main gained 12 commits after the branch point, and none of them touches a file of this diff. The branch was not merged with main before opening, and CI runs on the merge ref.
  • No dogfood battery: the title carries no ! and Clause-② carries no (narrowing) arm.

Generated by Claude Code

…run took, not the flow graph's first branch

The step line drew the request's served `flow_steps`, which the approvals
read builds by walking the flow graph from its start along the `approve`,
then `true`, then first edge. A request routed down another branch was
drawn on rungs it never visits.

The record approvals panel now builds the line from the run's own request
rows (same flow run, another node, approved, opened before the pending
one), then the pending step, and draws nothing ahead of it. The approval
request page holds only its bound row, so its decision panel draws no line
until the approvals read serves the route.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jThx2NXgeJ4uCUi1qo
@github-actions github-actions Bot added size/l 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#4 · session_01QsR2jTfvjNTaR29ehLk26T · 2026-10-27T12:19Z · head 4743eb8d7734213158add1b3c2bdb026c96ae787

  1. Shape: draft, base main, first line Fixes #26563. Clause-②: no and Metadata-protocol: none match the claim (6440119093), in the body and in the changeset. Ten files sit inside the amended surface (6440197798): RecordApprovalsPanel.tsx, ApprovalDecisionPanel.tsx, ApprovalStepLine.tsx, useRecordApprovals.ts, their tests and the changeset. One file is outside it, ui/packages/app-shell/README.md (two approval paragraphs, docs-driven per ui/AGENTS.md); it is accepted below. No content/docs/releases/.

  2. Premise, measured: the seat read the diff. Both surfaces drew flow_steps, which attachFlowSteps builds as the graph trunk. visitedApprovalSteps now builds the line from the list rows the panel already holds: same flow_run_id, another node, approved, opened before the pending one, one entry per node at its latest round, and then the pending step. Nothing ahead of the pending step is drawn. The decision panel's line is removed. ApprovalStepLine markup is unchanged. @object-ui/app-shell is private, so visitedApprovalSteps is no published surface. One nit, not blocking: the ordering compares submitted_at as strings, which holds for the server's uniform ISO-8601 Z form.

  3. Gates, pinned to the head: check-runs at 4743eb8d: 52 total, 43 success, 9 skipped, 0 failed. Type Check UI, Lint UI, Test UI (16 shards), Build & E2E UI and Check Changeset are all success. check-governed-merges --pr 26666: not governed (0 of 10 paths), 754 changed lines ≤ 3000. mergeable_state: clean.

  4. Accepted deviations:

    • the README paragraphs and the stale ApprovalStepLine header paragraph: in-touched-file accuracy repairs, and no behaviour changes;
    • the @object-ui/console bump beside the private app-shell: follows the 26473 precedent;
    • the full app-shell suite left to CI: Test UI is green at this head;
    • main not merged into the branch: CI ran the merge ref.

    The report's open question (request page: no line, a sibling read, or a one-step line) is answered A, no line, as built. Under B the reported viewer still sees one step. C repeats the header. The served route is the real fix and is filed as plugin-approvals: ApprovalService.attachFlowSteps serves the flow's main trunk as a request's flow_steps, so a request routed off the trunk gets a step list without its own step #26668.

  5. Out of scope: the server route read, ApprovalRequestRow.flow_steps → ApprovalService.attachFlowSteps, is filed as plugin-approvals: ApprovalService.attachFlowSteps serves the flow's main trunk as a request's flow_steps, so a request routed off the trunk gets a step list without its own step #26668 (no labels, for triage). Once it lands, visitedApprovalSteps is deleted and the request page draws the served route. Skills-docs: is not owed (Metadata-protocol: none, no narrowing).

  6. Prose face, checked sentence by sentence against the diff: each changeset sentence has a matching hunk:

    • the line shows passed steps, oldest first, then the pending one;
    • the selection rule (same run, another node, approved, opened earlier, one entry per node);
    • nothing ahead is drawn;
    • participants-only rows, and no line under two steps (ApprovalStepLine's own gate);
    • the decision panel draws no line.

Landing: no contract review is owed (no published schema, no governed text, Clause-②: no). The seat marks the PR ready and arms auto-merge. On MERGED, Fixes #26563 closes the card.


Generated by Claude Code

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/l tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

2 participants