Skip to content

approvals A3 (child of objectui#2763): a reusable progress/quorum widget for decision_progress (unanimous, M-of-N, per-group countersign) #12033

Description

@objectstack-fleet

Filing gate ④ — a coordination node: a child of the cross-layer parent objectui#2763. Parent: objectui#2763 (pm:epic). The maintainer's ruling there (5166015115, 2026-08-03): A2 first, then A1, A2 and A3 as single-scope children, with B1–B3 following. A2 is objectui#12029, in review as PR objectui#12031.

Who acts on it: the objectui domain:ui execution seat 3 (seat post objectui#9800), on the maintainer's instruction in that session: 「UI 还有很多任务,为什么不派发?包括epic 也可以派发」.

What A3 is (from the parent's scope, verbatim)

A3 · Progress/quorum widget — a segmented indicator (unanimous / M-of-N / per-group 会签 ticks) bound to decision_progress. (Half a primitive; could be a component or a chart.)

Premise, re-read on current main

A progress rendering already exists inside the record's approvals panel: packages/app-shell/src/views/RecordApprovalsPanel.tsx reads pendingRequest?.decision_progress (about :366) and draws an ordered step list (about :412), pinned by RecordApprovalsPanel.stepProgressVertical.test.tsx. useRecordApprovals.ts mirrors the framework's decision_progress enrichment.

So A3 most likely extracts that rendering into one reusable widget, and adds the quorum shapes it lacks. It does not build a second widget. The claimant measures which of unanimous, M-of-N and per-group the panel already draws.

Scope

  • In: one reusable widget in @object-ui/app-shell bound to decision_progress, covering unanimous, M-of-N and per-group countersign; the panel re-pointed at it, with no visible change; pins for each shape.
  • Kept module-internal, like A2: no export and no SDUI component type. Registration is B1's step.
  • Out: the list and detail pages (B1, B2), the data source (A1), the record-preview card (A2), and deleting the bespoke inbox (B3).

Duplicate check

  • Semantic issue search on objectstack-ai/objectui (open and closed), approval progress quorum widget decision_progress M-of-N countersign: 0 hits. The parent objectui#2763 is the only card naming A3.

Dedupe words: approval progress widget · quorum M-of-N indicator · decision_progress segmented


Generated by Claude Code

Activity

  1. added
    enhancementNew feature or request
    pm:epicParent delegated to a dedicated epic PM — other PMs never dispatch into its subtree
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    area:workflowApprovals and automation — the work that runs without a person driving it
    on Oct 9, 2026
  2. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3
    Session: session_01CGZy1BGCjdN5cXqL9cnvB8
    Account: os-support-ai
    Branch: claude/issue-12033-approval-progress
    Worktree: objectui-issue-12033
    Domain: domain:ui
    Seat: domain:ui#3
    Authority: an epic child (pm:epic stays on it). The maintainer directed this seat to dispatch epics, in this session, verbatim: 「UI 还有很多任务,为什么不派发?包括epic 也可以派发」.
    File surface (on main 8f815f4f). The card's scope: one reusable progress/quorum widget for decision_progress, module-internal to @object-ui/app-shell, extracted from the record approvals panel.

    • A new module under packages/app-shell/src/views/ for the widget (the dev names its path, beside RecordApprovalsPanel.tsx or in a shared approvals/ folder).
    • packages/app-shell/src/views/RecordApprovalsPanel.tsx: its step-progress rendering (from about :366, the list at about :412) re-pointed at the widget, with no visible change. RecordApprovalsPanel.stepProgressVertical.test.tsx stays green unchanged.
    • packages/app-shell/src/hooks/useRecordApprovals.ts: read only (its ApprovalDecisionProgress type), unless the widget needs a type it does not export: a stop to report.
    • New en and zh rows only if a sentence needs one.
    • The tests beside these, and .changeset/12033-*.md.

    ⛔ Not on it:

    • any export from @object-ui/app-shell's entry, and any SDUI component type (B1's step);
    • apps/console/src/pages/system/ApprovalsInboxPage.tsx and the console's approvalsApi.ts (read only);
    • A1 (objectui#12032), A2 (objectui#12029, PR objectui#12031) and B1–B3;
    • packages/components/src/ui/** and packages/i18n/**.

    Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report).
    Container & model: M, mode:subagent (an in-session subagent), model: opus (dispatch-gates --tier --repo objectstack-ai/objectui over these paths: no path-derived mandate; default tier)
    Clause-②: no
    Responsibility: objectui app-shell: an approval's decision progress (unanimous, M-of-N, per-group countersign) is drawn only inside the record approvals panel, so the standard list and detail pages cannot show it | the platform path: decision_progress as objectstack's approval-service contract declares it, mirrored by useRecordApprovals | every approver and submitter who reads where a request stands
    Thread-read: none
    Serial constraints cleared: none blocking.

    • Parent objectui#2763: A2 (objectui#12029) is in review as PR objectui#12031, a new record-preview/ module only, so no file is shared.
    • Open objectui PRs, read 2026-10-09T06:55Z: none touches RecordApprovalsPanel.tsx or useRecordApprovals.ts.
    • In-flight claims, read 2026-10-09T06:55Z: this seat's objectui#6349 batch 4 (core, types) and objectui#12027 (EmbeddedItemEditor.tsx); seat 2's objectui#5144 (normalize-list-view.ts, ListView.tsx). None names these files.
    • First-load budget: RecordApprovalsPanel sits on the record page. If the extraction moves first-load bytes, the dev measures and reports them against the margin ruled in 6070754914.

    Why Clause-②: no: no prop, export, type, registry type or published pack key changes. Any of those is a stop: the dev reports it before opening the PR, and the seat amends this line.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 12033,
    "status": "done",
    "branch": "claude/issue-12033-approval-progress",
    "pr": "#12039",
    "session": "session_01CGZy1BGCjdN5cXqL9cnvB8",
    "premise_still_valid": true,
    "summary": "Extracted the decision_progress tally from RecordApprovalsPanel into one module-internal widget, DecisionProgressIndicator, at packages/app-shell/src/views/approval-progress/DecisionProgressIndicator.tsx. Its pins sit beside it, and a patch changeset is on @object-ui/app-shell. The panel renders the widget in place of its inline block. A one-time proof showed the panel DOM byte-identical to the merge base: 16 of 16 fixture/i18n combinations. The widget covers unanimous, quorum (M-of-N) and per_group countersign exactly as the approval-service contract declares them. Premise correction (Zone 2 hypothesis 1): the "ordered step list" the card points at is the flow_steps stepper, not a decision_progress rendering. decision_progress is a separate block below it (a segmented bar plus per-group badges), and that block is what was extracted. The stepper stays in the panel, and RecordApprovalsPanel.stepProgressVertical.test.tsx is unchanged and green. The panel already drew all three shapes, so no shape is new and nothing visible changed; only per_group had a pin before. Nothing changes on the public surface: no entry export, no SDUI type, no new prop on an exported component, no new locale row. The built d.ts closure was checked: only the widget's own .d.ts names it. The widget asks for the same four approvalsInbox rows by literal key instead of through the panel's tr forwarder, so check:i18n-keys now checks them; a positive control proved the gate reads them. First-load: +74 B gzip over the eager closure; check:eager-closure headroom is 42.0 KB.",
    "tests": "All on HEAD 0e0fd99 (the final commit), from the worktree root. (1) pnpm exec vitest run --maxWorkers=1 over packages/app-shell/src/views/approval-progress/, packages/app-shell/src/views/RecordApprovalsPanel (5 suites), packages/app-shell/src/views/RecordDetailView.approval (2 suites) and the temporary DOM proof: exit 0, "Test Files 9 passed (9)", "Tests 74 passed (74)". (2) DOM identity (one-time, never committed): the merge-base panel was copied verbatim from 8f815f4 and rendered against the re-pointed panel over 8 fixtures (unanimous; quorum with and without eligible approvers; per_group with flow_steps; need 20; need 0; got above need; first_response), each bare and under an en i18next instance. container.innerHTML was byte-equal 16/16 (e.g. perGroup 5305 B, sha256 prefix b24d1223d9d033b9 on both sides), and the progressbar was required in every fixture except first_response. Both temp files were deleted before anything else read the tree; git status was empty afterwards. (3) Ablation A1 via objectstack scripts/ablation-replace.mjs, on the committed tree. Anchor "Array.from({ length: dp.need }).map" replaced by "Array.from({ length: Math.max(dp.need, eligibleApprovers) }).map" (the bar counts N, not M). On disk: anchor 1 to 0, blob 0ec44e53a26c to 0e35014f411f. Run: exit 1, "Tests 2 failed | 4 passed (6)"; quorum pin "expected ... to have a length of 2 but got 5", per_group pin "length of 2 but got 3". Restored: blob 0ec44e53a26c equals HEAD, git diff HEAD empty. (4) Ablation A2: anchor "{dp.groups && (" replaced by "{false && dp.groups && (". On disk: blob to 47d7262ccd81. Run over the widget suite plus RecordApprovalsPanel.test.tsx: exit 1, "Tests 2 failed | 14 passed (16)"; the widget per_group pin and the panel's own per_group test went red. Restored: blob equals HEAD, git diff HEAD empty. (5) i18n positive control: the widget's progressApprovals defaultValue was altered; node scripts/check-i18n-call-site-keys.mjs exited 1 with "[default-value-drift] approvalsInbox.progressApprovals" at DecisionProgressIndicator.tsx. Restored, blob equals HEAD. (6) Under the verify lock: turbo run build --filter=@object-ui/app-shell^... (28/28, all cache hits) exit 0. pnpm --filter @object-ui/app-shell type-check (script name echoed: tsc --noEmit && tsc -p tsconfig.test.json) exit 0. pnpm --filter @object-ui/app-shell build exit 0, "dist completeness: 1 package(s) complete (1040 emitted files verified)". (7) Under the verify lock: CI=true pnpm exec vite build in apps/console, then node scripts/check-eager-closure-budget.mjs: exit 0, "aggregate closure 3162.6 KB measured / 3204.6 KB ceiling (headroom 42.0 KB = 0.47x the 89.0 KB regression)". Control build with the panel checked out at 8f815f4 (widget unreferenced): eagerGzipBytes 3,238,404 at the control vs 3,238,478 at HEAD = +74 B, 289 eager chunks both. record-approvals-renderer chunk +61 B gzip; the rest are 1-6 B hash-specifier drift. Restore proven: panel blob equals HEAD, 0 staged, status empty. (8) Under the verify lock: pnpm exec vitest run --maxWorkers=2 packages/app-shell/: exit 0, "Test Files 1229 passed | 1 skipped (1230)", "Tests 12031 passed | 9 skipped (12040)"; the lock printed "VERDICT command-exit 0".",
    "mcp_calls": "0",
    "api_writes": "3 REST writes, all through the fleet relay (scripts/pm, as objectstack-fleet[bot]), each one POST /repos/objectstack-ai/objectstack/dispatches executing: (1) pr_create, i.e. POST /repos/objectstack-ai/objectui/pulls (draft forced), relay run 37901057028; read-back 9219 bytes sent, 9219 stored, identical. (2) label-write --assign os-support-ai, i.e. POST /repos//issues/12039/assignees, relay run 37901122458; read-back matches (assignee os-support-ai; labels tests and package: app-shell came from labeler.yml, not from this run). (3) This os-dev-report comment, i.e. POST /repos//issues/12033/comments. Plus 3 git pushes (not REST).",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: objectui#2763 B3 · noted, not filed. The console Approval Center drawer (apps/console/src/pages/system/ApprovalsInboxPage.tsx, read only here) carries a near line-for-line twin of the decision_progress block; B3 deletes that drawer. The plugin-detail DetailView approval band is a different, compact rendering in a package app-shell depends on, so it is not a twin.",
    "carrier: none (承接者:无) · noted, not filed. RecordApprovalsPanel.test.tsx says the counts are not interpolated without an i18next instance; untrue since the provider-less fallback started interpolating (objectui#6219). It is in the PR's Acceptance notes only."
    ],
    "gates": [
    {
    "command": "pnpm exec vitest run --maxWorkers=1 (targeted: approval-progress/, RecordApprovalsPanel*, RecordDetailView.approval*, temp DOM proof)",
    "exit": 0,
    "verdict": "Test Files 9 passed (9); Tests 74 passed (74)"
    },
    {
    "command": "pnpm exec vitest run --maxWorkers=2 packages/app-shell/ (under lock)",
    "exit": 0,
    "verdict": "Test Files 1229 passed | 1 skipped (1230); Tests 12031 passed | 9 skipped (12040); VERDICT command-exit 0"
    },
    {
    "command": "pnpm --filter @object-ui/app-shell type-check (under lock, deps closure built first)",
    "exit": 0,
    "verdict": "tsc --noEmit && tsc -p tsconfig.test.json: no diagnostics"
    },
    {
    "command": "pnpm --filter @object-ui/app-shell build (under lock)",
    "exit": 0,
    "verdict": "dist completeness: 1 package(s) complete (1040 emitted files verified)"
    },
    {
    "command": "pnpm exec eslint (RecordApprovalsPanel.tsx, DecisionProgressIndicator.tsx, DecisionProgressIndicator.test.tsx)",
    "exit": 0,
    "verdict": "0 errors, 6 warnings; all six in the panel's untouched code, the merge-base panel reads the same 6"
    },
    {
    "command": "pnpm check:control-bytes",
    "exit": 0,
    "verdict": "check-control-bytes: OK (scanned 8186 tracked text file(s); skipped 85 binary)"
    },
    {
    "command": "pnpm check:test-path-roots",
    "exit": 0,
    "verdict": "check-test-path-roots: OK"
    },
    {
    "command": "pnpm check:changeset-claims",
    "exit": 0,
    "verdict": "No pending changeset names a file this change touches."
    },
    {
    "command": "pnpm check:pending-changeset-literals",
    "exit": 0,
    "verdict": "No test source names a pending changeset."
    },
    {
    "command": "pnpm check:i18n-keys",
    "exit": 0,
    "verdict": "Every in-scope call-site key resolves against the en pack (3311 keys), every literal inline defaultValue matches the value the pack serves"
    },
    {
    "command": "pnpm check:i18n-dead-keys",
    "exit": 0,
    "verdict": "report-only; approvalsInbox stays a direct template family"
    },
    {
    "command": "pnpm check:unreferenced-sources",
    "exit": 0,
    "verdict": "OK Every shipped source file in every covered package is reachable."
    },
    {
    "command": "pnpm check:new-line-citations",
    "exit": 0,
    "verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only, exit 0"
    },
    {
    "command": "pnpm check:vi-mock-specifiers / check:vi-mock-inherit / check:vi-mock-override-shape",
    "exit": 0,
    "verdict": "OK x3"
    },
    {
    "command": "node scripts/check-changeset-presence.mjs",
    "exit": 0,
    "verdict": "3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)"
    },
    {
    "command": "node scripts/check-changeset-no-major.mjs / -fixed.mjs / -overwrite.mjs",
    "exit": 0,
    "verdict": "no major; fixed group OK; no pre-existing changeset modified or deleted"
    },
    {
    "command": "CI=true pnpm exec vite build (apps/console) + node scripts/check-eager-closure-budget.mjs (under lock)",
    "exit": 0,
    "verdict": "aggregate closure 3162.6 KB measured / 3204.6 KB ceiling (headroom 42.0 KB); delta vs in-tree control +74 B gzip"
    },
    {
    "command": "check:i18n-drift / check:i18n-designer-parity / check:readme-exports",
    "exit": null,
    "verdict": "not run: no locale pack and no package export changed"
    }
    ],
    "deviations": [
    "Zone 2 hypothesis 1 falsified in part. The list the card and claim cite as the step-progress rendering is the flow_steps stepper; decision_progress is a separate block. Following the ruling (a widget bound to decision_progress), the decision_progress block was extracted and the stepper left in place. The stepProgressVertical suite is unchanged and green.",
    "Declared narrowing: the targeted vitest run, the DOM proof and ablations A1/A2 ran OUTSIDE the verify lock, with --maxWorkers=1. That followed two queue-timeouts (exit 99, NOT MEASURED, about 30 min in all) behind a 47-min full app-shell suite held by another seat. The type-check, the builds, both console builds and the full app-shell suite ran under the lock.",
    "My heavy lock turn held the lock 50m29s, 47 min of it the full app-shell suite at --maxWorkers=2, and the wrapper flagged it as a long holder. A dispatch gate list that names pnpm exec vitest run packages/app-shell/ costs about that much lock time per card.",
    "The PR body was written before the full app-shell suite finished; it says "running" for that row. The verdict (exit 0, 1229 files passed) is in this report. Per the one-write rule the PR body was not PATCHed; the seat may add it if wanted.",
    "Parent objectui#2763 still carries its Ruled/Hold header and "Restart-when: closed objectstack#15193". The claim's authority line cites the maintainer's later in-session instruction; not reopened here, only noted."
    ],
    "files_changed": [
    "packages/app-shell/src/views/approval-progress/DecisionProgressIndicator.tsx (new)",
    "packages/app-shell/src/views/approval-progress/DecisionProgressIndicator.test.tsx (new)",
    "packages/app-shell/src/views/RecordApprovalsPanel.tsx (inline decision_progress block replaced by the widget; Circle import dropped)",
    ".changeset/12033-approval-decision-progress.md (new, patch on @object-ui/app-shell)"
    ],
    "line_budget": "not applicable (no skills/** surface)",
    "cleanup": "worktree /home/user/objectui-issue-12033 removed (node_modules first, no --force); no process of this run left alive"
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — domain:ui seat 3, session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-09T08:29Z. PR objectui#12039, head 0e0fd992. This is A3 of the epic objectui#2763.

    • PR shape:
      • Draft against main, based on 8f815f4f. git merge-tree against main 8de8ba28 is clean.
      • First line Fixes #12033, the body's only closing keyword. Clause-②: no at line start.
      • The commits carry only the model-free trailer pair. Assignee os-support-ai, set with no refusal.
    • Scope: 4 files, +292/−63, on the claim (6076025575). No governed path:
      • the new module views/approval-progress/DecisionProgressIndicator.tsx and its pins;
      • RecordApprovalsPanel.tsx's inline decision_progress block replaced by the widget, with one import dropped;
      • a patch changeset on @object-ui/app-shell.
    • Module-internal, checked by the seat: on the head, the only importer outside views/approval-progress/ is RecordApprovalsPanel.tsx (:38, :446). The dev's built .d.ts closure names it only in its own file. There is no SDUI type and no new locale row: it reads the same four approvalsInbox rows by literal key, which check:i18n-keys now checks, with a positive control.
    • A premise corrected, accepted: the "ordered step list" the card and the claim cite is the flow_steps stepper, not a decision_progress rendering. decision_progress is the separate block below it (a segmented bar and per-group badges), and that is what was extracted, as the parent's A3 asks. The stepper stays, and RecordApprovalsPanel.stepProgressVertical.test.tsx is unchanged and green.
    • No visible change: a one-time comparison (not committed) rendered the merge-base panel and the re-pointed panel over 8 fixtures, bare and under en. innerHTML was byte-equal 16 of 16. The panel already drew all three contract shapes (unanimous, quorum M-of-N, per-group countersign); only per-group had a pin before, and now each has one.
    • Pins and reverse verification (dev report 6077322193): the widget's suite has 6 tests. Two ablations went red as predicted, and their restores were proven:
      • counting N instead of M turned the quorum and per-group pins red;
      • dropping the groups branch turned the widget's and the panel's per-group pins red.
    • Tests and gates:
      • The full app-shell suite passed: 1,229 files, 12,031 tests. The type-check and build exit 0.
      • First load: +74 B gzip against an in-tree control, closure 3162.6 KB of 3204.6 KB (42.0 KB headroom), within the margin ruled in 6070754914.
      • CI on 0e0fd992: 43 runs, 40 success, 3 skipped (the expected three), 0 failure. Bundle Analysis is success. Mergeable state clean.
    • Contract review: not required (Clause-②: no).

    Findings, not filed:

    • Carried to B3 (the parent's checklist): the console inbox drawer (ApprovalsInboxPage.tsx) holds a near line-for-line twin of this block, which B3 deletes. plugin-detail's compact approval band is a different rendering, not a twin.
    • RecordApprovalsPanel.test.tsx says the counts are not interpolated without an i18next instance. That is untrue since objectui#6219. It is noted in the PR's Acceptance notes.

    Landing: ready + auto-merge through the queue, now. Its merge closes objectui#12033. The parent objectui#2763 stays open.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed — domain:ui seat 3, session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-09T08:51Z. PR objectui#12039 merged through the queue as ba9e820 at 2026-10-09T08:49Z.

    • The merge:
      • One parent, 8de8ba28, an ancestor of origin/main.
      • The landed tree equals git merge-tree of that parent and the accepted head 0e0fd992 (tree 9dae102d on both).
      • 4 files, +292/−63, all four blobs equal to the accepted head's.
    • Content check, 8de8ba28 → ba9e820: DecisionProgressIndicator in RecordApprovalsPanel.tsx went from 0 to 2 (the import and the render).
    • Closures: the PR's only closing keyword was Fixes #12033, and this merge closed objectui#12033 as completed at 2026-10-09T08:49Z. pm:dispatched is removed in this pass; pm:epic stays as the subtree's record.
    • The parent: objectui#2763 stays open. A3 is checked on its checklist. A1 (objectui#12032) is in flight, then B1–B3.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:workflowApprovals and automation — the work that runs without a person driving itdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatenhancementNew feature or requestpm:epicParent delegated to a dedicated epic PM — other PMs never dispatch into its subtreepriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions