Skip to content

fix(plugin-approvals): the approval request page's highlights strip names the current step by its label - #24374

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24281-approval-page-step-label
Oct 18, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24281-approval-page-step-label

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #24281

Clause-②: no

The served sys_approval_request_detail record page's highlights strip now names the current step by its label. It lists current_step_label (the "Approval Step" field) where it listed current_step, the Approval node's machine id, so a request waiting on the lead_review node reads Lead Review there. The approvals get's row builder now serves current_step_label, the member ApprovalRequestRow declares for this (370833a303, #24309), with the value step_label carries. The object's highlightFields makes the same swap.

Responsibility: plugin-approvals' own row builder and page declaration | the platform path is the declared column current_step_label and its contract member, both on main, which the row builder did not serve under that name | reached by every user who opens a pending request's served page, whose Current Step chip showed the node's machine id.

Readings are against objectstack c4526a705 (this branch's base) and objectui at the console pin .objectui-sha = 0707b4ae3, by git show and git grep, plus one wire reading on a booted showcase (below). The page was not driven in a browser.

Which of the card's two routes, and why

The card asks the fix to say which: the get now serves the label under a field name the page can list. The page names its object's declared fields, and step_label is not one. current_step_label is a declared field of sys_approval_request and a declared member of ApprovalRequestRow, so rowFromRequest serves it from the same requestDisplayLabels value as step_label. No new column, field, formula or contract member, and no packages/spec edit.

Which doors gain the member

rowFromRequest is the only builder of a served ApprovalRequestRow. It has three call sites, and no other site builds a row:

  • openNodeRequest's return;
  • listRequests, behind GET /api/v1/approvals/requests;
  • loadRequest, behind getRequest (GET /api/v1/approvals/requests/ID) and readBackRequest. readBackRequest builds the request that decideNode, recall, sendBack, resubmit, reassign, remind, requestInfo and comment echo back. The action-token chain behind the emailed links also reads through getRequest.

The two REST routes pass the rows through unchanged (res.json, packages/rest/src/approvals-routes.ts). So every one of these doors now serves current_step_label beside step_label.

Readers of step_label, measured

step_label stays served, with the same value. No reader moves.

  • This repo: action-link-pages.ts:66 (req.step_label || req.current_step), the emailed confirm and result pages. Unchanged.
  • objectui at the pin: the inbox (apps/console/src/pages/system/ApprovalsInboxPage.tsx:314, :1525; the type in approvalsApi.ts:68) and the record page's approvals panel (packages/app-shell/src/views/RecordApprovalsPanel.tsx:372; the type in useRecordApprovals.ts:97). Unchanged.
  • No objectui source reads current_step_label at the pin, so the added key changes nothing for an existing reader.

The strip and the object's list change together

The rule in the page header still holds at the pin. The Details tab is a bare record:details node. objectui's record-details.tsx draws the object's highlightFields minus the mounted strip when a node authors neither sections nor fields (bodyFieldsOmitted, objectui#12433). A strip-only swap would have put a "Current Step: lead_review" row into that tab. So both lists swap current_step for current_step_label, and the object's list minus the strip is still exactly record_id.

Other readers of the object's highlightFields, re-read at the pin (the same census PR #24279 took):

  • ObjectView and ObjectGrid default columns: only for a list view that declares no columns. All four sys_approval_request list views declare columns, and three already list current_step_label. Not reached.
  • InterfaceListPage default columns, ObjectKanban card fields, RelatedList default columns: no interface page, kanban view or related list of requests is declared in this repository. Not reached.
  • deriveLookupColumns (lookup picker preview): the two lookups to a request, sys_approval_action.request_id and sys_approval_approver.request_id, sit on objects whose enable.apiMethods is ['get', 'list'], so no create or edit form opens that picker. Not reached.
  • RecordDetailView's synthesized default page: not used, since this page is assigned (isDefault: true).
  • RecordPreviewCard, the card a referenceVia pointer pair draws: reached only where a pointer pair names a request row. Whether such rows exist is NOT MEASURED. Where they do, the card shows the step's label where it showed the node id.
  • Studio's object settings panel shows the list as authored.
  • Backend readers (@objectstack/lint field references and semantic roles, metadata-core's field-security fold, metadata-protocol's build probes): none changes behaviour for a list of existing fields. current_step_label is the fifth entry, outside the first four that the field-group shadow rule reads. The page test's build-rule leg stays green.

The null case: what the chip shows

The console reads a request through the approvals get (routeApprovalRecordReads). The get serves current_step_label by the one chain in display-labels.ts:

  • A label is stored (every request opened since the column, and every row the boot backfill filled): the stored label. openNodeRequest stores the authored node label, else the humanized node id.
  • No label stored, the snapshot carries one (a row opened before the column that the backfill has not reached): the snapshot's __nodeLabel.
  • No label stored and none in the snapshot: the humanized node id (approve_step becomes Approve Step), never the raw id.
  • No step at all (current_step null and no label anywhere): neither field is served. The strip draws no step chip, as it drew no current_step chip before.
  • The same record read through the generic data API (a host without the console's routing): the chip reads the stored column. It is null only on a row the boot fill has not labelled, and the chip is then absent where it used to show the raw id.

The object_name chip: measured, the same family, not fixed here

  • What it draws. The get serves object_name as stored, the target object's API name. On the booted showcase below it answers showcase_announcement. At the pin, HeaderHighlight draws a text field through the text cell renderer, which prints the stored string. The pointer-pair handling (resolveRecordPointer) applies only to the record_id id half, and only in the details grid and the list grid. So the Object chip reads showcase_announcement. That is a static reading of the renderer plus a wire reading of the value, not a screen reading.
  • Why it is not fixed here. The get also serves object_label (Announcement on the same request), but sys_approval_request declares no field that holds the target object's label (its 23 declared fields were read). A page bound to the object names declared fields only: this page's own pin, "every field the page names exists on its object", holds that. So a label chip needs a new member, which this card's claim excludes. It is reported to the seat with its reach and is not built.

Tests (at 2ff7f0350)

  • pnpm --filter '@objectstack/plugin-approvals^...' build: VERDICT command-exit 0. pnpm --filter @objectstack/plugin-approvals build: exit 0, at b62923f9d. The one commit after it changes only a test file.
  • pnpm --filter @objectstack/plugin-approvals test: Test Files 81 passed (81), Tests 1111 passed (1111) (1108 before, plus the 3 below).
  • pnpm --filter @objectstack/plugin-approvals typecheck: exit 0, check:test-typecheck: OK — … 8 file(s) / 324 error(s) / 27 pinned signature(s) held. The first spelling of the new test read .id off the open result, a union with the auto-outcome, and grew approval-service.test.ts's debt by 7 (189 type error(s), ledger records 182). The second commit narrows it with a typed id helper, as the file's other helpers do.
  • Three new cases, named and green (--reporter=verbose -t current_step_label):
    • approval-service.test.ts › the get serves the step label as current_step_label, beside step_label › a labelled row serves its stored label. It also asserts that the list door serves the same three fields as the get.
    • › a row opened before the column serves the snapshot label, else the humanized machine id.
    • sys-approval-request-page.test.ts › names the step by its label, current_step_label, in the strip and in the object's list alike.
    • Control in each: current_step is still served raw (approve_step) and is still a declared field.

Ablation (once, then restored; repeated on the final head). node scripts/ablation-replace.mjs --delete dropped the served line current_step_label: labels.current_step_label, from rowFromRequest, inside a script with its own trap restore. The test imports the service by relative path, so src/ is what runs and no dist/ is involved. On-disk evidence: anchor x1 → x0, blob b137eedb4067 → 2739baafe590. Expected direction: red. Observed: Tests 2 failed | 1 passed. Both get cases failed with expected { current_step_label: undefined, …(2) } to deeply equal { …(3) }, and the page pin stayed green (it does not read the get). Restore leg: blob after restore == HEAD (b137eedb4067), git diff HEAD empty, git status clean. No test file is left behind.

Wire reading (booted showcase, this branch's build). pnpm dev -- --fresh -p 38241, signed in as the seeded admin. Retitling a showcase_announcement (the showcase_dynamic_approval flow's trigger) opened a pending request at the lead_review node. GET /api/v1/approvals/requests/ID answered current_step_label: "Lead Review", step_label: "Lead Review", current_step: "lead_review", object_name: "showcase_announcement", object_label: "Announcement". GET /api/v1/approvals/requests answered the same step fields. The served metadata lists the strip ["process_label","object_name","status","current_step_label","submitter_id","updated_at"] and the object's highlightFields ["process_label","object_name","record_id","status","current_step_label","submitter_id","updated_at"]. The server was stopped after the reading.

Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 2ff7f0350 derived 69 commands for this diff, the same 69 the seat derived at 83dfbc046. All 69 ran locally at 2ff7f0350, each exit code captured before any pipe. All 69 exited 0, and none printed a PREREQUISITE NOT MET refusal of its own (the CLI and the showcase closure had been built for the wire reading).

node scripts/pm/dispatch-gates.mjs --ran with the exit-coded record: 69 derived, 69 run, 0 NOT-MEASURED, 0 UNRUN ("a DERIVED zero — all 69 recorded an exit code and none of them is 3").

The longest local runs were check:query-options-erasure (235 s), check:slot-lookup (123 s) and check-comment-mask-corpus (107 s).

Not run locally, declared to CI: test:repo (the vitest repo project; the diff touches neither its tests nor a list naming it), and the repo-wide scans (pnpm lint and the rest of the battery). The PR's check-runs are the reading. check:dev-prereqs and check:skill-frame-freshness are not derived and not owed (no packages/spec build input and no frame file changed).

Acceptance notes

  • The Object chip still shows the target object's API name (section above). It is the same family as this card, and a label needs a declared field the claim excludes. It is reported to the seat, not built.
  • objectui's served-page pin copies (apps/console/src/services/approvalRequestsDataSource.servedPage*.test.tsx, 8 files at the pin) copy the strip and the object's list with current_step. Their field maps declare no current_step_label, and their row fixtures are shaped as the get served a request before this PR, without it. approvals B1 closing card (ruling C on objectui#12438): the inbox opens a request on the served sys_approval_request page where the side sheet is, deletes the sheet, and lists each retired pin against its user report objectui#12484 carries them. They are not touched here.
  • ApprovalRequestRow.current_step_label's TSDoc says the member was "declared ahead of its producer", which this PR is. The sentence names this card as the producer's, so it stays true. It is a packages/spec file and is not touched here.
  • origin/main moved one commit past this branch's base (a2a3777bf, docs-only, none of the touched paths). The branch is not merged with it. CI's merge ref and the queue read the merged tree.

Generated by Claude Code

…ames the current step by its label

The approvals get's row builder serves `current_step_label`, the member
`ApprovalRequestRow` declares for the step label under the column's own
name, with the value `step_label` carries. The served record page's strip
and the object's `highlightFields` list `current_step_label` where they
listed `current_step`, the Approval node's machine id.

Claude-Session: https://claude.ai/code/session_01Nk17Z26X5DB59iQztDBADH
Co-authored-by: Claude <noreply@anthropic.com>
…d helper

The open result is a union with the auto-outcome, so reading `.id` off it
added seven TS2339 entries to the file's test-typecheck debt. The helper
narrows it the way the file's other id helpers do.

Claude-Session: https://claude.ai/code/session_01Nk17Z26X5DB59iQztDBADH
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-approvals, touching 11 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/plugins/plugin-approvals/src/display-labels.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

21 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 6a49efb9c452ff63870171434d9b996ae1746585.

⛔ 8 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/plugins/plugin-approvals/src/display-labels.ts) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 6a49efb9c452ff63870171434d9b996ae1746585 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3d59d8fe5c3721e51599e2ce3e8f11370b6a0f6e — the merge of head 2ff7f0350be9e5a42ffa88025f7abfc51ec9107b into base 6a49efb9c452ff63870171434d9b996ae1746585, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3d59d8fe5c3721e51599e2ce3e8f11370b6a0f6e && git checkout 3d59d8fe5c3721e51599e2ce3e8f11370b6a0f6e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6a49efb9c452ff63870171434d9b996ae1746585 2ff7f0350be9e5a42ffa88025f7abfc51ec9107b && git checkout -B drift-repro 6a49efb9c452ff63870171434d9b996ae1746585 && git merge --no-ff 2ff7f0350be9e5a42ffa88025f7abfc51ec9107b

node scripts/docs-audit/affected-docs.mjs --json 6a49efb9c452ff63870171434d9b996ae1746585

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 6a49efb9c452ff63870171434d9b996ae1746585 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:services seat 1 (#6021) · marchtian · session_01Nk17Z26X5DB59iQztDBADH · 2026-10-18T17:31Z · head 2ff7f0350b. This is the seat's own review at TIER_DEFAULT. --tier read no path-derived mandate, and the diff touches no published schema or governed text: the member it serves is already declared on main (370833a303). It lands through the merge queue once every check on this head is green.

  • Shape: a draft PR to main. Its first line is Fixes #24281, and that is the only closing keyword in the body. Clause-②: no is at a line start in the body and in the changeset (@objectstack/plugin-approvals patch). Card and PR assignee: marchtian. Not governed: check-governed-merges reads 0 of 7 paths, and 108 changed lines ≤ 3000.
  • The ruling (6249005066), met:
    1. The get serves the declared member. rowFromRequest serves current_step_label: labels.current_step_label, the value step_label carries, from the one requestDisplayLabels chain (display-labels.ts:72): the stored label, else the snapshot's __nodeLabel, else the humanized node id. The dev measured rowFromRequest as the only builder of a served ApprovalRequestRow. So the get, the list and every write's echoed request gain it.
    2. step_label stays with the same value, and current_step stays the raw id. A control case holds both. Readers named and not moved: action-link-pages.ts:66, plus the console's inbox and record panel at objectui pin 0707b4ae3.
    3. The strip and the object's list move together. Both swap current_step for current_step_label, under the page header's rule from PR fix(plugin-approvals): the approval request page's highlights strip names the flow by its label #24279: the bare record:details draws the object's list minus the strip. The existing pin still holds that difference to record_id alone.
    4. No spec edit and no new column, field or member. The seat read the diff.
  • Pins:
    • Two cases in approval-service.test.ts. The first is a labelled row through the get and the list. The second is a row with no stored label, which serves the snapshot label or else Approve Step.
    • One page case holds both lists to current_step_label and the field's declaration ("Approval Step"), with current_step still declared as the control.
    • The dev's ablation (deleting the served line) turned the get cases red, and the restore is blob-equal to HEAD.
    • A wire reading on a fresh showcase boot: the get and the list answered current_step_label: 'Lead Review' beside current_step: 'lead_review'.
  • Gates on this head: 34 checks succeeded and 6 were skipped by design (Build Docs, Console Pin Gate, Dogfood Regression Gate, Dogfood Verify CLI, Packed-tarball smoke, Temporal Conformance: the roster set). There were no failures, and the required contexts are green. Locally the dev derived 69 families on this head (the same 69 the seat derived at 83dfbc046), ran all 69, and all exited 0 (--ran: 0 NOT-MEASURED, 0 UNRUN).
  • Accepted deviations:
    • display-labels.ts, outside the claim's named files: a header comment only. It said rowFromRequest serves the step label as step_label alone, which this change made incomplete.
    • The branch base is c4526a705, not the claim's 83dfbc046. It is a later main, and the branch was not merged with the five commits since. None touches plugin-approvals. The one page-shape change among them (3a734e088, a region named main) does not reach this page, which declares regions: []. The merge queue tests the merged tree.
  • Out-of-scope findings, with their carriers:

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 18, 2026 17:33
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 18, 2026 17:33
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 18, 2026
Merged via the queue into main with commit 5da2e88 Oct 18, 2026
41 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-24281-approval-page-step-label branch October 18, 2026 18:02
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 tests tooling

Projects

None yet

2 participants