# Issue 774 Approval Chain Comment Surfacing

Status: implementation-ready
Scope class: approval runtime UI data fetch
Date: 2026-05-14

## Purpose

Issue #774 tracks a data-history gap in engine backend mode. Approve, reject,
and recall comments need a durable approval-runtime source and must be included
in the synthetic `ProcessInstance` that record detail passes to the approval
chain builder.

This design keeps the fix in the frontend fetch layer. It does not add a new
backend projection, alter the chain-builder algorithm, or change approval
business semantics.

## Current Failure

The two record-detail data paths differ:

- Salesforce/SOQL mode queries `ProcessInstance` with a nested
  `StepsAndWorkitems` subquery that includes `Comments`, then passes the
  returned instance directly to `buildApprovalChain`.
- Nexus backend mode starts from
  `/api/approvals/records/:recordId/instance`, then fetches `ProcessNode`,
  `ProcessInstanceNode`, and pending `ProcessInstanceWorkitem` records in
  parallel. Its runtime-node fetch omits `Comments`, and it never maps those
  runtime nodes into the builder's `StepsAndWorkitems` contract.
- `buildApprovalChain` already reads
  `instance.StepsAndWorkitems.records[].Comments` and positionally assigns
  comments to completed runtime nodes. With no `StepsAndWorkitems` on the
  synthetic instance, every completed step receives `undefined`.

Backend persistence is sufficient for approve/reject comment surfacing, and must
be extended for recall:

- New work items can carry submit comments on `ProcessInstanceWorkitem.Comments`.
- Approve and reject actions write the action comment to the completed
  `ProcessInstanceNode.Comments`.
- The processed `ProcessInstanceWorkitem` rows are then deleted, so they are not
  a durable source for completed-step comments.
- Recall transitions the pending runtime node to `Removed`; it must write the
  recall comment to that same durable `ProcessInstanceNode.Comments` field
  before deleting pending work items.

## Contract

- The approval chain builder remains the single owner of chain assembly.
- `APIProcessInstance.StepsAndWorkitems.records` is the input contract for
  completed-step comments.
- Nexus backend mode must populate that contract before calling
  `buildApprovalChain`.
- The populated records must be ordered by `CreatedDate ASC`, matching the
  existing SOQL-mode nested subquery and the builder's positional matching.
- Pending assignment fetches stay separate. They are still used for pending
  action controls and multi-approver display.
- Query failures for the comment-history fetch should fail closed to an empty
  `StepsAndWorkitems.records` array, preserving the current chain rendering
  rather than making record detail unusable.
- Salesforce/SOQL mode must keep using its existing nested subquery unchanged.
- No object-specific, tenant-specific, profile-specific, or approval-process
  name branches are introduced.

## Implementation Shape

Frontend API:

- Add a small module-private helper in `approvals.ts` that maps
  `ProcessInstanceNode` rows to synthetic `APIProcessInstanceStep` records for
  Nexus backend mode.
- In Nexus backend mode only, query `ProcessInstanceNode.Comments` alongside the
  existing runtime-node fields. Do not add `Comments` to the shared
  Salesforce/SOQL-mode `getProcessInstanceNodes` query, because the nested
  `StepsAndWorkitems` subquery is already the Salesforce-compatible comment
  source.
- Build synthetic records with `Id`, `StepStatus = NodeStatus`,
  `OriginalActorId = LastActorId`, `ActorId = LastActorId`, actor relationship
  mirrors from `LastActor`, `Comments`, and `CreatedDate = CompletedDate ||
  CreatedDate`.
- Keep the pending `ProcessInstanceWorkitem` query for pending assignments. It
  must not be repurposed as completed history because processed work items can be
  deleted after action execution.
- Attach `StepsAndWorkitems: { records }` to the synthetic `APIProcessInstance`
  before calling `buildApprovalChain`.

Backend:

- Update `Recall` so the pending `ProcessInstanceNode` update that sets
  `NodeStatus = Removed`, `CompletedDate`, and `LastActorId` also persists
  `Comments = comments`.
- Keep recall authorization, lifecycle actions, and work-item deletion behavior
  unchanged.

Types:

- Extend `APIProcessInstanceNode` with optional `Comments`.
- Reuse `APIProcessInstanceStep` for the synthetic `StepsAndWorkitems` records.
- No public UI type changes are required.

Tests:

- Extend `approvalChainBuilder.test.ts` to prove existing comment propagation
  behavior for multi-step completed chains remains positional.
- Add frontend API coverage for Nexus backend mode proving
  `getFullApprovalChain` passes comments from runtime nodes into the returned
  chain.
- Add backend coverage proving recall comments are copied to the removed
  `ProcessInstanceNode` before the pending work item is deleted.
- Keep an assertion that SOQL-mode query text still includes the nested
  `StepsAndWorkitems` subquery with `Comments`.

## Edge Cases

- Completed node without comments: render the completed step without a comment,
  matching the existing optional display behavior.
- Multiple completed steps by the same actor: comments are matched positionally
  by execution order, so repeated actors do not overwrite one another.
- Pending-only instance: no completed comments exist yet; pending assignments
  still come from `ProcessInstanceWorkitem`.
- Mixed completed/removed and pending chains: completed comments come from
  runtime nodes, pending controls come from pending work items, and upcoming
  steps remain definition-only.
- Comment-history query failure: log warning-level context if the helper has a
  catch path, return an empty list, and preserve the current no-comment chain.
- Salesforce mode: no synthetic mapping runs, so the existing API contract and
  nested SOQL behavior are unchanged.

## Verification

- Focused frontend tests:
  - `cd frontend && npm run test -- approvalChainBuilder approvals`
- Frontend build:
  - `cd frontend && npm run build`
- Repo hygiene:
  - `git diff --check`
- Baseline checks if time permits:
  - `cd backend && go test ./...`
  - `cd shared && go test ./...`

`npm run build` rewrites `frontend/public/version.json`; revert that generated
change unless the version file is intentionally in scope.

## Design Iteration Notes

1. The issue rollout text says to add a `ProcessInstanceWorkitem` query directly
   to the Nexus backend mode fetch block. Source inspection rejected that as the
   final implementation because approve/reject deletes processed work items
   after copying the action result to the runtime node. Querying work items is
   still correct for pending assignments, but not for completed comments.
2. Review feedback identified that the first implementation still missed recall
   comments because recall did not copy the comment to durable runtime-node
   history. The final design updates recall persistence rather than narrowing
   issue closure.
3. The second option is to expand the backend
   `ProcessInstanceSummary` endpoint. That would work, but the issue explicitly
   scopes the fix away from endpoint-shape changes and would create another
   projection to keep in sync with existing SOQL and chain-builder contracts.
4. The selected option maps Nexus backend runtime nodes into the builder's
   existing `StepsAndWorkitems` input contract. It uses the data source that
   survives action completion, preserves the builder's current positional
   semantics, keeps pending work-item reads for pending assignments, and leaves
   Salesforce/SOQL mode untouched.
5. Final pre-implementation confidence check: every display consumer reads the
   comment from `ApprovalChainStep.comments`; only `buildApprovalChain` writes
   that field for completed steps; therefore populating the builder's
   `StepsAndWorkitems` input in Nexus backend mode is the narrow source-of-truth
   fix.

References:

- Issue #774: https://github.com/pingcap-inc/NexusCRM/issues/774
- Prior pending-assignment design:
  `docs/design/issue-748-multi-approver-record-detail.md`
