Repository navigation
approvals face 1 of 5 (ruling C on objectui#12438): the served sys_approval_request page names each pending approver, with position labels and countersign groups, as the inbox side sheet does (folds objectui#12077) #12482
Description
Filing gate ④: a cross-layer coordination node, filed as ruling 6190549995 on objectui#12438 directs (letter C, the maintainer's 「C (推荐)」, director seat summon #42, items 2–4). It is face 1 of at most 5, first in the ruling's order by pull: "approver chips (objectui#12110's pins; objectui#12077 folds into it)".
Parent: objectstack-ai/objectstack#25950 (B1 of the epic). The closing card that mounts the page where the side sheet is waits on this one.
Reader who acts: the domain:ui seat 1 (the epic's seat, seat post #5560). It dispatches step 1, the fit check, in objectui. If the fit check shows they are needed, it files the objectstack spec-row card (spec lane) and the plugin-approvals page-edit card (services lane), chained with Blocked-by:.
Dedupe (MCP search_issues, scope objectstack-ai/objectui, all states, read 2026-10-15T11:50Z): "approval request page pending approvers names position labels countersign served page block" → 14 results, top 10 read:
- approvals B1 (child of objectui#2763): the
sys_approval_requestdetail page on the standard record renderer, composed from A1's data source, A2's record card, A3's progress widget, the action timeline and DeclaredActionsBar #12045 (B1, the mount step; it waits on the closing card); - [Decision] approvals inbox: what opening a request shows — keep the hand-built side sheet, replace it with the served request page now, or build the page's missing faces first (B1 of objectui#2763) #12438 (the ruling);
- The routed approval request page shows pending_approvers as raw user ids under More details, while its header resolves the submitter's name #12077 (open; folded into this card);
- closed: approvals inbox: the request drawer's record summary renders raw stored values, labels two fields "Owner", and logs duplicate React keys #11677, console: a record:approval_decision block placed on any page other than an approval request's renders nothing — draw a visible notice instead of null #12072, console: a record whose approval_status reads pending is shown "locked for approval" with Edit disabled even when the approvals read answers 200 with no request, so a composition without the approvals service locks records forever #12220, app-shell: spec 18's
record:approval_decisionpage block renders nothing —ApprovalDecisionRendereris written but never registered (the objectui half of spec 18's three record blocks, after #12030) #12342, console: 记录页审批入口与 sys_approval_request 声明动作双实现不一致 —— 转派/退回/要补充材料零入口,群组审批人看不到决策按钮 #3055, Approvals inbox: quick approve (inline a/r, bulk) silently drops declared decision outputs #2829 and approvals UX: surface OOO/quorum/per_group/attachments SDUI-first — stop hand-editing the inbox per feature #2678, none of which is this face.
None of them is this card.
The face
- The side sheet shows it today, pinned. The inbox's request sheet names each pending approver, with a position label and countersign grouping. The pins are
apps/console/src/pages/system/ApprovalsInboxPage.approverHeadline-12110.test.tsxand the approver cells ofApprovalsInboxPage.characterizationPins.test.tsx. - The served page does not show it. On the real
RecordDetailViewover the routed approvals source, "More details" printspending_approversas raw user ids, and no approver is named anywhere on the page (objectui#12077, measured by its dev; seat note6093164273).
Steps (ADR-0060 rule 2; the ruling's item 2, "fit before build")
- Fit check, objectui. Size the face against the existing generic block first:
DecisionProgressIndicator(packages/app-shell/src/views/approval-progress/DecisionProgressIndicator.tsx, epic A3). Its props today areprogressandeligibleApprovers(a count), and it shows counts, not named approvers.- If its declared input can admit named approvers with position labels and countersign groups, extend it.
- Otherwise open a new generic block, only if no existing block can carry the face.
- Report which, with the readings.
- Spec row (objectstack
domain:spec), only if the block's page-metadata input needs a declared key. - Served page edit (objectstack
packages/plugins/plugin-approvals/src/sys-approval-request.page.ts,domain:services), placing the block on the page. - objectui block: the renderer, registered and pinned.
Steps 2 and 3 are filed as their own cards in objectstack, chained with Blocked-by:, only when step 1 says they are needed.
Acceptance
- On the served
sys_approval_request_detailpage, each pending approver is named with their position label and countersign group, matching what the sheet's pins assert. No raw user id is shown anywhere on the page (objectui#12077's report). - The block is generic, named for what it draws (ADR-0060 rule 2). ⛔ Not a product-named widget.
- ⛔ The side sheet is not touched (ADR-0060 rule 3; the ruling's item 1).
Attribution: written by the domain:ui seat 1 in session session_01JZnppgHRp4sREcouhkQYDB, executing ruling 6190549995.
Activity
objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 1 (face 1 of ruling
6190549995on objectui#12438, filed by this seat this fire)Session:
session_01JZnppgHRp4sREcouhkQYDBAccount:
sunhaolinBranch:
claude/issue-12482-approver-namesWorktree:
objectui-issue-12482Domain:
domain:uiSeat:
domain:ui#1File surface:
packages/app-shell/src/views/approval-decision/**(therecord:approval_decisionpanel the served page already mounts) andpackages/app-shell/src/views/approval-progress/**(DecisionProgressIndicator, epic A3), only as far as naming the pending approvers needs; the tests beside each; the tenpackages/i18npacks only if a label is added; one.changeset/12482-*.md.@objectstack/spec, objectstackplugin-approvals' page,apps/console'sApprovalsInboxPage(the side sheet) and any@object-ui/*export are outside the surface (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuinames no path-derived mandate)Clause-②: no
Responsibility: n/a — not a defect card
Thread-read: none
Serial constraints cleared: read at objectui
maind9d9d98684. Nopm:dispatchedclaim in the lane namesapproval-decision/orapproval-progress/: this seat's objectui#12418, #12462 and #12472, and seat 2's objectui#12440, #12463 and #12464. objectui#12483 (face 2) isBlocked-by: objectstack-ai/objectui#12482.sys-approval-request.page.tsat objectstack57994c311f) already mountsrecord:approval_decision. That is a declared spec type with an empty strict props row, which reads the bound request. If that panel can name the pending approvers from data the request already carries, the face is objectui-only: no spec row and no page edit. If it cannot, the dev stops and reports the spec row shape it needs, and the seat files the chain.Clause-②: no: the route draws more of what a declared block already reads, with no type, prop or export change. A route that needs one stops and reports.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{ "issue": 12482, "status": "done", "branch": "claude/issue-12482-approver-names", "pr": "https://github.com/objectstack-ai/objectui/pull/12497", "session": "session_01JZnppgHRp4sREcouhkQYDB (subagent mode: the parent's id, as the claim and the harness stamp it)", "premise_still_valid": true, "summary": "The served sys_approval_request_detail page now names who a pending request waits on. The record:approval_decision panel the page already mounts draws a 'Waiting on' row from the row it is bound to. Each chip shows the server's name for a user id, a position's registry label (with its holders) for a position slot the server left unnamed, and the countersign group the slot fills. One person in N quorum slots is one chip with a xN count; an unnamed id is middle-truncated, as the inbox sheet draws it; raw slots appear on hover only. The drawing reuses app-shell's approverChips, approverDisplay and useApproverDirectory. There is no spec row, page edit, prop or export (Clause-2: no). DecisionProgressIndicator is unchanged: it is drawn only with a tally, and a first_response node has none, so it cannot carry the names without a contract change. Mechanism readings: A1 holds. A2 holds: 0 reads for a server-named slate, and at most 3 reads per page for position, team or department slots, none per approver. A3: the sheet has no 'and N more' form, so nothing of that shape exists to match. A4 is false on the served page: its Details tab draws highlightFields minus the strip's fields (record_id alone), and the approver ids appeared 0 times there before this change. The raw 'More details' print objectui#12077 measured is the synthesized page, which this face does not reach. One finding outside the face: the served page's highlights strip prints the submitter's bare user id (submitter_id, a sys_user lookup), measured before and after this change. So the card's literal line 'no raw user id anywhere on the page' holds for approver ids only. The PR line 1 is 'Fixes objectstack-ai/objectui#12482' as dispatched; the submitter finding is reported below for the seat to file.", "tests": "All at head 233965379b, through os-verify-lock (slot dev-12482). (1) The panel suite, the progress suite and the new served-page pin: VERDICT command-exit 0, Test Files 3 passed, Tests 33 passed. (2) The full union below: 47 files, 391 tests passed. (3) The census set: 18 files, 696 tests passed. Unit pins in ApprovalDecisionPanel.test.tsx: two same-name people in two groups give two group-labelled chips; three quorum slots of one person give one chip 'Ada Lovelace x3'; a position slot reads its registry label and holder; CONTROL: the server name wins and the directory is asked nothing; CONTROL: an unnamed id reads 'zzUnre...z123'; CONTROL: no pending approvers; CONTROL: a decided request names no one; a first_response node with no tally still names its approver. Served-page pins (approvalRequestsDataSource.servedPageApprovers-12482.test.tsx: the served page copied whole, the real RecordDetailView under ApprovalRecordReads, only the transport, the console adapter and the metadata store doubled): named approvers with groups and a position label; no pending approver id in the page text; objectui#12077's three-slot case; no directory read for a server-named slate; CONTROL: no pending approvers; CONTROL: an unresolvable id drawn as the sheet draws it. Ablation (one-shot): the fix was committed first; the script dropped the PendingApprovers element for a marker. The anchor count went 1 to 0 and the marker count 0 to 1. Result: vitest exit 1, Tests 10 failed and 17 passed: all 10 naming pins red; the controls and the pre-existing tests green. Restore: git checkout HEAD on the path, then a blob-hash match with HEAD and git diff HEAD empty, under a trap on EXIT, INT and TERM. No build is involved: the root vitest config aliases @object-ui specifiers to source, so a dist ablation preflight does not apply. Pre-change measurement (a temporary probe, deleted, never committed): on the served page the panel read 'Approvals - 1 of 2 / 5 eligible approvers', no approver was named, and the approver ids appeared 0 times. On the synthesized page 'More details' printed the slot store with the id 3 times, and no panel was mounted.", "gates": [ { "command": "pnpm turbo run build --filter='@object-ui/console^...' --concurrency=2 (under os-verify-lock)", "exit": 0, "verdict": "VERDICT command-exit 0; 34 tasks, 28 cached. The first attempt was a queue-timeout (exit 99, never acquired), retaken with the same slot." }, { "command": "pnpm --filter @object-ui/app-shell type-check (under os-verify-lock)", "exit": 0, "verdict": "exit 0. The first attempt, before the dependency closure was built, failed with TS2307 (module not found): a prerequisite gap, not a measurement of the diff. The test project's include glob src/**/*.test.tsx covers the panel suite, read off the glob, not counted with --listFiles." }, { "command": "apps/console: pnpm exec tsc --noEmit --listFiles (under os-verify-lock)", "exit": 0, "verdict": "exit 0; the new pin file is in the file list (1 hit)." }, { "command": "pnpm exec vitest run, one run over: approval-decision/, approval-progress/, every approvalRequestsDataSource.* suite, AppContent.approvalRecordReads, apps/console/src/pages/system/ (the ApprovalsInboxPage.* pins as CONTROL), RecordApprovalsPanel*, RecordDetailView.approvalDeclaredActions, RecordDetailView.paramDialogTitle, global-page-blocks.render, DeclaredActionsBar.overrideAffordance, useApproverDirectory.contractEnvelope-5945, approverIdentity*", "exit": 0, "verdict": "Test Files 47 passed (47), Tests 391 passed (391); os-verify-lock VERDICT command-exit 0" }, { "command": "pnpm exec vitest run, the census and ratchet tests whose subject the diff touches: product-neutral-renderer.ratchet, no-component-any-cast, no-refresh-key-remount, adr0054-ratchet, defaults-maps-mirror-en-pack, writeRefusalCensus-11583, ariaLabelLiteralCensus-12145, machineLocaleCensus-9909, dead-pack-keys-retired-8754, iconOnlyButtonCensus-12143, check-i18n-call-site-keys, check-vi-mock-specifiers, check-vi-mock-inherit, check-vi-mock-override-shape, one-authority-per-exported-name-6273, check-control-bytes, registry-inputs-spec-parity, record-approval-decision-arm-12342", "exit": 0, "verdict": "Test Files 18 passed (18), Tests 696 passed (696)" }, { "command": "pnpm check:control-bytes", "exit": 0, "verdict": "check-control-bytes: OK" }, { "command": "pnpm check:new-line-citations", "exit": 0, "verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s)" }, { "command": "pnpm check:i18n-keys", "exit": 0, "verdict": "every in-scope call-site key resolves and every literal inline defaultValue matches the en pack" }, { "command": "pnpm check:i18n-drift", "exit": 0, "verdict": "No en value changed in this range" }, { "command": "pnpm check:vi-mock-specifiers / check:vi-mock-inherit / check:vi-mock-override-shape / check:test-path-roots", "exit": 0, "verdict": "each exit 0" }, { "command": "pnpm check:changeset-claims / check:pending-changeset-literals / check:handler-key-reads / check:metadata-write-doors / check:unreferenced-sources", "exit": 0, "verdict": "each exit 0" }, { "command": "node scripts/check-changeset-no-major.mjs / check-changeset-overwrite.mjs / check-changeset-presence.mjs / check-changeset-fixed.mjs", "exit": 0, "verdict": "each exit 0; presence: 2 source files of 1 released package changed, 1 changeset declared" }, { "command": "pnpm exec eslint on ApprovalDecisionPanel.tsx, ApprovalDecisionPanel.test.tsx and the new console pin", "exit": 0, "verdict": "no findings" }, { "command": "CI check-runs on PR 12497, read once just before this report", "exit": null, "verdict": "Changeset Bump Policy: success; label: success; Test (coverage) and its shards, and dependabot: skipped; Type Check, Lint, Repo Gates, Test shards 1-16, Test (dist pins), Bundle Analysis, Build and E2E, Build Docs, Line Citation Gate, Spec Main Shape Gate (known red on main), Governed Surface Queue Guard, the changeset checks and Live E2E: in_progress. CI convergence is the PM's." }, { "command": "not run locally", "exit": null, "verdict": "NOT MEASURED locally, left to CI: pnpm lint (repo-wide), the pnpm test shards, Bundle Analysis / eager closure (needs a full console build), check:sdui-registration-pins (reads a full build), check:i18n-designer-parity (no label added, no pack changed)." } ], "mcp_calls": "9, all reads: mcp__github__issue_read x7 (get on 12482, 12438 and 12077; get_comments on 12482, 12438 and 12077, plus the read-back of this report), mcp__github__pull_request_read x2 (get, get_check_runs on 12497). No MCP write tool was used.", "api_writes": "2 REST writes, both through the fleet-write relay as objectstack-fleet[bot]. (1) pr_create: POST /repos/objectstack-ai/objectui/pulls (draft) and POST /repos/objectstack-ai/objectui/issues/12497/assignees (sunhaolin), carried by one POST /repos/objectstack-ai/objectstack/dispatches (run 38744178079, success; read-back: 11684 bytes sent and stored, identical). (2) This report: POST /repos/objectstack-ai/objectui/issues/12482/comments, via scripts/pm/post-stamped.mjs. Also 2 git pushes (the empty branch as the write probe, then 233965379b); these are not REST writes. No label writes.", "open_questions": [], "out_of_scope_findings": [ "class: a. reach: the served sys_approval_request_detail record page (UI), measured on the real RecordDetailView under the console's ApprovalRecordReads with the transport doubled, not on a live server. Wrong answer: the highlights strip prints submitter_id (declared Field.lookup('sys_user')) as the bare user id. The approvals get serves the id plus submitter_name, never an expansion, and the strip made no label read: host findOne was never called, there was no sys_user id read, and the served submitter_name was not shown. Present before and after this change. objectui#12077 recorded the header naming the submitter on its rig, so this may follow the routing of request reads through the approvals get; not bisected. Seam: spec:sys_approval_request.submitter_id (lookup sys_user) → runtime: the approvals get route's row (submitter_id bare, submitter_name beside it) → renderer: the record:highlights lookup cell. Dedupe words: submitter raw id approval request page; submitter_id lookup routed approvals get no expand; submitter_name not drawn highlights.", "carrier: domain:ui seat 1 (the objectui#12077 fold; its seat note's hide half). Noted, not filed: on a host that serves no sys_approval_request_detail page, the synthesized record page still prints pending_approvers' raw slot store under 'More details'. The pre-change probe showed the id 3 times, and the decision panel is not mounted on that page. This face does not reach it; the fix would be producer-side (the object's declaration) or a console route-scoped hide, both off this surface.", "carrier: none. Noted, not filed (in the PR's Acceptance notes): the tally's 'N eligible approver(s)' counts slots, not people, so one person in three quorum slots reads '3 eligible approver(s)' beside one x3 chip. The sheet and the business record's panel count the same way, and no contract declares which it should be.", "carrier: objectui#12484 (the closing card that retires the sheet). Noted, not filed: the chip markup is drawn in two app-shell places from one approverChips output, this panel and RecordApprovalsPanel; RecordApprovalsPanel was outside this card's file surface." ], "deviations": [ "File surface: one test-only file outside the claim's surface, apps/console/src/services/approvalRequestsDataSource.servedPageApprovers-12482.test.tsx. The PM's pins must mount the console's routed approvals source (ApprovalRecordReads, which lives in apps/console). No production code changed in apps/console.", "Two temporary measurement files were written into the worktree and run under the verify lock: apps/console/src/services/zz-probe-12482.test.tsx (the pre-change probe) and an app-shell zz-dbg-12482.test.tsx (a timing check of my own pin). Both were deleted and never committed. The tree was clean before git worktree remove.", "The worktree was cut from origin/main caac3b24ee, not the d9d9d98684 the dispatch named; main had moved. BASE = caac3b24ee.", "The objectstack origin/main was read through a named ref refs/os12482/main (57994c311f); the ref was deleted after use.", "Ablation used an inline python replace with grep -c anchor and marker counts, not scripts/ablation-replace.mjs, because objectui has no such script; restore was proven by blob hash and an empty git diff HEAD.", "Mechanism assumption A3's 'and N more' form does not exist in the sheet, so there was nothing to match. A4 is false on the served page, so nothing was hidden there; see summary." ], "files_changed": [ "packages/app-shell/src/views/approval-decision/ApprovalDecisionPanel.tsx", "packages/app-shell/src/views/approval-decision/ApprovalDecisionPanel.test.tsx", "apps/console/src/services/approvalRequestsDataSource.servedPageApprovers-12482.test.tsx", ".changeset/12482-decision-panel-names-approvers.md" ], "line_budget": "not applicable (no skills/** surface)", "cleanup": "Worktree ../objectui-issue-12482 removed after the PR opened: node_modules first, then git worktree remove without --force, exit 0." }Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed ·
domain:uiseat 1 ·session_01JZnppgHRp4sREcouhkQYDB· 2026-10-15T14:02Zmainbcf4df0ffbat 2026-10-15T14:01Z. It is an ancestor oforigin/main, with first parent8146afe886.bcf4df0ffbto the accepted head233965379b.Fixes objectstack-ai/objectui#12482. This card closed with the merge, ascompleted. objectui#12454, another seat's Studio card, closed in the same merge-queue batch, not through this PR.6190549995on objectui#12438):sys_approval_request_detailpage names each pending approver: the server's name, a position's registry label, the countersign group, and ×N for one person in N quorum slots. They are drawn in therecord:approval_decisionpanel the page already mounts.6191476739, the dev report on this card, ACCEPT6193542771.Blocked-by: objectstack-ai/objectui#12482is met, and this seat dispatches it next.finding, awaiting triage).pm:dispatchedis removed in the same act as this note. The assignee stays, as the record of who drove it.Generated by Claude Code
feat(app-shell): the served approval request page names who the pendi…