Skip to content

fix(app-shell): an approver chip that stands for several slots says so in words - #27699

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-j9w213
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-j9w213

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh

Requested by Bill · project thread

Fixes #27615

Clause-②: yes (widening) — a new key: the approvalsInbox.approverSlots count family, added to all ten published @object-ui/i18n packs. No existing value is edited.
Metadata-protocol: none

维护者速读

  • 改了什么:审批「等待以下审批人」里,一个人占多个审批席位时,人名旁原来只画一个光秃秃的 ×3;现在写成「Dev Admin · holds 3 approver slots」(中文「兼任 3 个审批席位」)。业务记录的审批面板和审批请求页两处一致。
  • 为什么改:同一行旁边是「1 位可审批人」,两个数字都对,但并排看像是矛盾,读者分不清是一个人还是三个人要审批。按 triage 裁定:只改文案,两个计数都不动。
  • 风险与回滚:风险低,只改一个徽标里的文字,加一个新的语言包键(十个语言包);回滚即 revert 本 PR。
  • 先看:ui/packages/app-shell/src/views/RecordApprovalsPanel.tsx 的 chip 渲染;approval-decision/ApprovalDecisionPanel.tsx 的 PendingApprovers;ui/packages/i18n/src/locales/en.ts / zh.ts 的 approverSlots 一族。
  • 两分钟验证:showcase 里以种子管理员打开 EXP-DEMO(High-Value Expense — Committee Quorum)的审批请求页;预期「等待以下审批人」显示「Dev Admin · holds 3 approver slots」,旁边仍是「1 eligible approver」。
  • 需要你做的:无,席位落地。

What changes

When one person holds several of a pending step's approver slots, the "Waiting on" chip collapses those slots into one chip. It used to draw the number as a bare ×3, beside the tally line's "1 eligible approver" (objectui#12781 counts distinct principals). Both numbers are right, but side by side they read as a contradiction.

  • The chip says what its number counts. Where the chip drew ×N (N above one), it now draws the pack's words: "Dev Admin · holds 3 approver slots". It is the same copy, read by literal key, on the business record's approvals panel (RecordApprovalsPanel) and on the approval request page (ApprovalDecisionPanel's PendingApprovers).
  • Neither count changes. approverChips still collapses the same slots into the same chip with the same count, and eligibleApproverCount still counts that person once. No widget, dialog or hook is added.
  • @object-ui/i18n: one new count family, approvalsInbox.approverSlots, in all ten packs. Each pack carries every plural category its language selects, the same set as progressEligible beside it (en / de one and other; fr / es / pt add many; ru one, few, many and other; ar all six; zh / ja / ko count-invariant), plus the count-invariant base row. Real wording in every pack, not English placeholders: en "holds 3 approver slots", zh "兼任 3 个审批席位", ja "承認枠 3 件を兼任", ko "승인 자리 3개 겸임", de "belegt 3 Genehmigerplätze", fr "occupe 3 sièges d'approbateur", es "ocupa 3 puestos de aprobador", pt "ocupa 3 postos de aprovador", ru "занимает 3 места согласующего", ar "يشغل 3 من مقاعد الاعتماد".
  • ApproverChip.count gains a doc comment saying how it is drawn and why.

Changeset: .changeset/27615-approver-slots-chip.md, patch for @object-ui/app-shell and @object-ui/i18n, carrying the same Clause-②: and Metadata-protocol: lines.

Tests

  • Record approvals panel, a new describe in RecordApprovalsPanel.eligibleApprovers-12781.test.tsx (same fixtures as the objectui#12781 pins: user-id slots, a name per id, the server's quorum tally):
    • the stock committee quorum (one user in all three slots): exactly one chip, whose text is Dev Admin· plus the pack row approverSlots at count 3, no × anywhere in the panel, and the eligible line is the pack row progressEligible at count 1;
    • one user in two slots among two others: three chips, only the doubled one carries the slot words at count 2, and the eligible line reads 3.
  • Approval request page, the same two cases in ApprovalDecisionPanel.eligibleApprovers-12781.test.tsx, plus a zh case: under a zh provider the chip draws the zh pack's row (asserted different from en's) beside the zh eligible line.
  • Labels are asserted as the pack rows the components read, rendered by the same i18next instance, never as restated English prose.
  • Two existing assertions moved from the bare ×2 / ×3 to the family's count-invariant row, which is what a provider-less render draws: RecordApprovalsPanel.test.tsx (the per-group chip test) and ApprovalDecisionPanel.test.tsx (the objectui#12077 three-slot chip test).
  • The existing objectui#12781 tests are unchanged and green.
Targeted run on the merged head 0f72297: approvals views + i18n pack tests

pnpm --dir ui exec vitest run --maxWorkers=2 packages/app-shell/src/views/RecordApprovalsPanel packages/app-shell/src/views/approval-decision/ packages/app-shell/src/views/approval-progress/ packages/i18n/

 Test Files  106 passed (106)
      Tests  1499 passed | 13 skipped (1512)
os-verify-lock: VERDICT command-exit 0

The i18n pack tests in that run include all-locales-key-parity, count-families-11445, plural-categories-11432 and untranslated-identity-4376.

Ablation, one per panel: the old bare count restored, the new pins go red

Each leg is node scripts/ablation-replace.mjs, which mutates the committed panel back to drawing ×{chip.count}, runs the panel's test file, and restores with the restore proven by blob hash and an empty git diff HEAD. The tests import the panels from source, so no build sits between the mutation and the run.

RecordApprovalsPanel.tsx      ok mutation landed: anchor 1 -> 0, blob 9ad41c4e5210 -> d497647b1758
  × the stock committee quorum: one chip that says its person holds three slots, beside one eligible approver
  × one person in two slots among others: only that chip carries the slot words
  Tests  2 failed | 4 passed (6)
  ok restored: blob == HEAD (9ad41c4e5210) and `git diff HEAD` is empty

ApprovalDecisionPanel.tsx     ok mutation landed: anchor 1 -> 0, blob 80c0bdadbe41 -> 2e767972b457
  × the stock committee quorum: one chip that says its person holds three slots, beside one eligible approver
  × one person in two slots among others: only that chip carries the slot words
  × the zh pack draws its own words for the chip, beside its own eligible count
  Tests  3 failed | 4 passed (7)
  ok restored: blob == HEAD (80c0bdadbe41) and `git diff HEAD` is empty

Acceptance notes

  • Chip count above one from two different people. approverChips keys a chip by label, detail and group, so two distinct addresses that resolve to an identical name with no group (two users who share a full name, say) also collapse into one chip with a count, and the eligible count says 2 beside it. That is a property of the chip's key and was there before this change. The chip's title already lists both raw ids. This change gives the number words; it does not change what the chip groups.
  • Provider-less hosts draw the count-invariant base row ("holds 3 approver slot(s)"), the same way the existing progressEligible line does there. With an I18nProvider the plural slot is selected.
  • The only other "Waiting on" line, the approval status beside DeclaredActionsBar, lists each name once and draws no count, so there is nothing to reconcile there. The ×{group.items.length} in InboxPopover counts notifications in a group and is out of scope.
  • No new widget, dialog or hook.

Verification

  • Local gates: the full dispatch-gates --commands list for this diff, run on the merged head and reconciled with --ran. That includes node scripts/check-sdui-manifest.mjs (exit 0) and pnpm --dir ui exec vitest run scripts/. The ui i18n checks were also run: check:i18n-keys, check:i18n-drift, check:i18n-dead-keys and check:new-line-citations, all exit 0.
  • Type-check: pnpm turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/i18n, both packages, both tsc programs (source and tests).
  • Scope: the full @object-ui/app-shell vitest suite was not run locally. CI's Test UI runs it.

Generated by Claude Code

The pending-approver chip drew a bare "×N" when one principal holds several
of a step's slots, beside an eligible-approver count that counts that person
once. The chip now reads "holds N approver slots" from the new
approvalsInbox.approverSlots count family, on the record approvals panel and
the approval request page alike; neither count changes.

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

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh
Contract-review-claim: session_01Utp6CbDAucNyQ4UtzCJKyh · head 0f72297 · 2026-10-29T19:31Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 0f72297edaa4feececc50a7a1c7077d531cc36f5
Local-runs: none

Face: declaration only. The PR body, the changeset and claim 6504969450 each say Clause-②: yes (widening). The tree touches no packages/spec/src/**, no governed path and no narrowing line. This review was written 2026-10-29T19:32Z from the diff (17 files, +188/−5), the card, triage 6500636553 and dev report 6506321390.

① Derived judgments

  • @object-ui/i18n is published (en and TranslationKeys = typeof en are exported). It gains one count family, approvalsInbox.approverSlots, in all ten packs, with each pack's plural categories, the same set its progressEligible family carries. The English is holds {{count}} approver slot(s), with _one and _other. The Chinese is 兼任 {{count}} 个审批席位. The change is additive: no key is renamed or removed, no existing value is edited, and nothing narrows.
  • @object-ui/app-shell is private: true. The chip's ×N span becomes · {t('approvalsInbox.approverSlots', { count })} in RecordApprovalsPanel and ApprovalDecisionPanel's PendingApprovers. Its defaultValue equals the English base row. ApproverChip.count gains only a docblock. Neither count changes: approverChips and eligibleApproverCount are untouched.
  • The triage route is the one that landed: copy only, no new widget, dialog or hook. The pins are a principal holding several slots on both panels, including zh. The [finding] 38 watch hints name real files the gates read but miss the corpus by their file EXTENSION — every card touching those files is under-named #12781 suites stay green.

② Semver level

patch for @object-ui/app-shell and @object-ui/i18n, which matches Metadata-protocol: none.

③ Boundary flags

  • open_questions is empty.
  • The dev noted one finding without filing it: two distinct addresses that resolve to one identical name with no group also collapse into one chip. That predates this PR, is unreproduced, and sits in Acceptance notes.
  • The ru base (count-less) row reads Мест согласующего: {{count}}, a different phrasing from its plural rows. It is only the fallback that i18next never selects when count is passed, so it is not a contract matter.
  • The PR author is objectstack-fleet[bot] (a relay write).

Implemented-by: claude/project-thread-j9w213
Reviewed-by: session_01Utp6CbDAucNyQ4UtzCJKyh

VERDICT: PASS


Generated by Claude Code

…y's words

The console's served-page pin still expected the bare count the chip no
longer draws; it now expects the approverSlots row, keeping the case's
intent: one approver named once, with a count, the id printed nowhere.

Claude-Session: https://claude.ai/code/session_01Utp6CbDAucNyQ4UtzCJKyh
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh

Regen-provenance: 6506420542 · 0f72297edaa4feececc50a7a1c7077d531cc36f5 → 5f52f80c5d15927e76125289ff58c3de0b972f57 · git diff --stat → one file, ui/apps/console/src/services/approvalRequestsDataSource.servedPageApprovers-12482.test.tsx (+6/−1)

The one commit after the reviewed head is test-only. It moves a console pin from the bare Ada Lovelace×3 to the approverSlots plural row at count 3, and adds a check that ×3 appears nowhere. That pin made CI Test UI (16/16) red on 0f72297e. The commit changes no package source and no @object-ui/i18n key. The declaration face is unchanged, so record 6506420542 (TIER_DEFAULT PASS) keeps governing this head. The seat approved the file-surface widening (delta report 6506734499).


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh

席位核对

ACCEPT · head 5f52f80c5d15927e76125289ff58c3de0b972f57 · 2026-10-29T19:59Z

  • Shape: draft → main, with Fixes #27615 on its own line and no other card number beside a closing keyword.
    • Clause-②: yes (widening) appears in the PR body, the changeset and claim 6504969450. It covers one new count family, approvalsInbox.approverSlots, in all ten @object-ui/i18n packs. No existing value is edited.
    • Metadata-protocol: none.
    • Contract review: TIER_DEFAULT PASS 6506420542 at 0f72297e. It governs this head through Regen-provenance 6506767969, because the one later commit is test-only.
  • Change, read: the chip in RecordApprovalsPanel and in ApprovalDecisionPanel's PendingApprovers used to draw a bare ×N. It now draws · {t('approvalsInbox.approverSlots', { count })}: en "holds 3 approver slots", zh 「兼任 3 个审批席位」. Neither count changes. There is no new widget, dialog or hook. This is the triage route (6500636553).
  • Surface: within claim 6504969450. Two tests beside each panel moved their bare-count assertions. The REWORK widened the surface by one file, the console pin approvalRequestsDataSource.servedPageApprovers-12482.test.tsx; the seat approved it, and it is reported in delta 6506734499.
    • DecisionProgressIndicator.tsx was left unchanged: the count line keeps its copy, and the chip carries the reconciling words.
  • Round 1 red, root cause: the dev's narrowed local scope left out ui/apps/console, so the console pin was missed. CI Test UI (16/16) caught it on 0f72297e. The fix asserts the pack's plural row and that ×3 appears nowhere. A grep of ui/ found no other approver-chip ×N pin.
  • Accepted deviations: the narrowed local test scope (CI ran the full suite green on this head); the signature line comes first and the attribution comment is on line 2; trailers carry no model name. Two empty stray files, /cmds2.txt and /ran2.txt, sit outside the repo in the dev container and are not part of the PR.
  • Noted, not filed (Acceptance notes): two distinct addresses that resolve to one identical name with no group also collapse into one chip. This predates this PR and was not reproduced.
  • Gates (check-runs on the head): 44 success, 11 skipped, 0 failure. Lint & Repo Gates, TypeScript Type Check and Check Changeset each read success. check-governed-merges --pr 27699: NOT governed, 0 of 18 paths, 200 changed lines.
  • Landing: non-governed, all green, mergeable_state clean, so the seat lands it with pr_ready + automerge_enable.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 29, 2026 20:00
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 29, 2026
Merged via the queue into main with commit a5d7098 Oct 29, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-j9w213 branch October 29, 2026 20:25
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 tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

approvals: a principal holding N of a step's approver slots — the chip's ×N and the eligible-approver count need the copy that reconciles them

1 participant