Repository navigation
feat(app-shell): extract the approval decision-progress indicator (objectui#12033) - #12039
Merged
objectstack-fleet[bot] merged 2 commits intoOct 9, 2026
Merged
Conversation
…jectui#12033) One module-internal widget, `views/approval-progress/DecisionProgressIndicator`, draws an approval request's `decision_progress` tally: unanimous, quorum (M-of-N) and per-group countersign, as the approval-service contract declares it. `RecordApprovalsPanel` renders it in place of its inline block with the same markup, so the panel's DOM does not change. The copy keys are the same `approvalsInbox.*` rows, now asked for by literal key. No export from the package entry and no SDUI component type; registration is objectui#2763 B1's step. Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
… (objectui#12033) Claude-Session: https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #12033
Clause-②: no
A3 of the objectui#2763 split, dispatched by the
domain:uiseat 3 (claim comment 6076025575). Session:https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8.What changes
packages/app-shell/src/views/approval-progress/DecisionProgressIndicator.tsx. It takes a pending request'sdecision_progress(plus, optionally, the eligible-approver count) and draws the tally for each behavior the approval-service contract declares:unanimous,quorum(M-of-N) andper_group(countersign).RecordApprovalsPanel.tsxrenders the widget in place of its inline block. The markup moved unchanged.@object-ui/app-shell's entry, no SDUI component type, no new prop on any exported component, no new locale row.useRecordApprovals.tsis read only (itsApprovalDecisionProgresstype). The built declarations confirm it: afterpnpm --filter @object-ui/app-shell build, only the widget's own.d.tsnames it;dist/index.d.ts,dist/views/index.d.tsand the panel's.d.tsdo not..changeset/12033-approval-decision-progress.md:patchon@object-ui/app-shell.Premise, measured on main 8f815f4
decision_progressrendering. That list is the panel'sflow_stepsstepper (labelled byapprovalsInbox.stepProgress, pinned byRecordApprovalsPanel.stepProgressVertical.test.tsx).decision_progressis drawn by a separate block below it: a segmented bar plus one badge per group. The widget is bound todecision_progress, as the parent's A3 text and the ruling say, so it extracts that block. Theflow_stepsstepper stays in the panel; the stepProgressVertical suite is unchanged and green.unanimousandquorumshare theapprovalsInbox.progressApprovalsrow and drawneedticks, with the eligible-approver count beside them.per_groupusesapprovalsInbox.progressGroups, never shows the eligible count, and adds one badge pergroupsentry (a check when satisfied, a hollow circle otherwise). A tally above 12 decisions is one continuous fill. No shape is new, so nothing visible changes. What was missing were pins: the panel's suites pinned onlyper_group.packages/spec/src/contracts/approval-service.ts, thedecision_progressmember):behaviorisunanimous,quorumorper_group, withgot,needand an optionalgroupslist ofgroup/got/need/satisfied; absent onfirst_response.ApprovalDecisionProgressinuseRecordApprovals.tsmirrors it field for field, and the widget reads only those fields.The re-point is DOM-identical (one-time proof, not committed)
A temporary test rendered the merge-base panel (copied verbatim from 8f815f4) and the re-pointed panel over eight fixtures: unanimous, quorum with and without eligible approvers, per_group with a three-step
flow_steps, a 20-decision tally,need0,gotaboveneed, andfirst_response(no tally). Each fixture was rendered with no i18next instance and with aneninstance: 16 comparisons. Each requiredcontainer.innerHTMLto be byte-equal, and required the progressbar to be present in every fixture exceptfirst_response, so an empty render could not pass. Result: 16 of 16 equal (per_group, for example, is 5305 bytes on both sides). Both temporary files were deleted before anything else read the tree.i18n
No new rows and no duplicates. The widget asks for the same four
approvalsInbox.*rows the panel asked for (progressApprovals,progressGroups,progressEligible,progressBar), with the same default text and arguments. One difference: it names them by literal key, not through the panel'str(key: string)forwarder. Socheck:i18n-keysnow checks these call sites (the key exists, the default text matchesen, the arguments match the holes). Positive control: changing the widget'sprogressApprovalsdefault text made that gate exit 1 with[default-value-drift] approvalsInbox.progressApprovalsat the widget. The restore was proven by blob hash.Pins
DecisionProgressIndicator.test.tsxhas one pin per shape plus the two edge branches. The pins check structure: tick count, filled ticks, the progressbar's value range, the group badges and the icon on each badge. They also check which pack row each label comes from. The expected label is produced by the same i18next instance, asked for the key, so no English prose is pinned.got, eligible count shown.needticks (the M), not the eligible N; eligible count shown. With no eligible count, the tally shows alone.gotaboveneed: the progressbar value is capped atneed.Reverse verification (
ablation-replaceon the committed tree, each restore proven by blob hash)max(need, eligible)ticks, so it counts N instead of M. Result: red, 2 failed / 4 passed. The quorum pin got 5 ticks where it expects 2, and the per_group pin got 3 where it expects 2. The unanimous pin stayed green because its eligible count is belowneed.{false && dp.groups && (). Result: red, 2 failed / 14 passed across the widget suite andRecordApprovalsPanel.test.tsx. The widget's per_group pin and the panel's own per_group test both went red.0ec44e53a26c) andgit diff HEADis empty.Gates, all on
0e0fd992bpnpm exec vitest runoverapproval-progress/, everyRecordApprovalsPanel*suite, bothRecordDetailView.approval*suites, and the temporary proofTest Files 9 passed (9),Tests 74 passed (74)pnpm --filter @object-ui/app-shell type-check(afterturbo run build --filter=@object-ui/app-shell^..., 28 of 28)tsc --noEmitandtsc -p tsconfig.test.jsoncleanpnpm --filter @object-ui/app-shell builddist completeness: 1 package(s) complete (1040 emitted files verified)pnpm exec eslinton the three touched source files0 errors, 6 warnings, all six in the panel's untouched code; the merge-base panel has the same sixpnpm check:i18n-keyspnpm check:control-bytes·check:test-path-roots·check:changeset-claims·check:pending-changeset-literals·check:unreferenced-sources·check:i18n-dead-keys·check:vi-mock-specifiers·check:vi-mock-inherit·check:vi-mock-override-shapepnpm check:new-line-citations0 new citation(s)node scripts/check-changeset-presence.mjs·-no-major·-fixed·-overwriteCI=true pnpm exec vite buildinapps/console, thennode scripts/check-eager-closure-budget.mjsaggregate closure 3162.6 KB measured / 3204.6 KB ceiling (headroom 42.0 KB = 0.47x the 89.0 KB regression)pnpm exec vitest run packages/app-shell/(the full package suite)os-dev-reportcomment on the cardFirst-load bytes: +74 B gzip over the eager closure (3,238,404 to 3,238,478, 289 eager chunks on both builds). The control build was this tree with the panel checked out at 8f815f4, which leaves the widget unreferenced. The panel's chunk
record-approvals-renderergrew 61 B gzip. The other changed chunks moved by 1 to 6 bytes because their import specifiers carry the renamed chunk's hash.Declared narrowing: the targeted vitest run, the DOM proof and the two ablations ran outside the verify lock, with one worker. That was after about 30 minutes queued behind another seat's full app-shell suite. The type-check, build, both console builds and the full suite ran under the lock.
Acceptance notes
ApprovalsInboxPage.tsx, a near line-for-line twin that objectui#2763 B3 deletes. The other is theDetailViewapproval band inplugin-detail, a different, compact rendering in a package that app-shell depends on, so it cannot import this widget.eligibleApproversis the caller's pending-approver count (pending_approvers.length), as the panel always showed it. It is not adecision_progressfield: the contract carries no N for a quorum, and the widget adds none.RecordApprovalsPanel.test.tsxsays the counts are not interpolated without an i18next instance. That stopped being true when the provider-less fallback began interpolating (objectui#6219). The comment is unchanged here.Generated by Claude Code