fix(app-shell): surface the bell badge's notifications + approvals breakdown in the inbox popover - #4073
Merged
Merged
Conversation
…eakdown in the inbox popover (#7233) The badge is `unread topics + pendingApprovalsCount` clamped at "9+", and the per-tab count pills clamp too, so a loaded console showed three "9+"s that reconcile to nothing. Add a breakdown line under the popover header stating the exact unclamped addends beside the exact total; the formula and the counting APIs are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9U1G2piXmYrhYQX96XUyv
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 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 objectstack-ai/objectstack#7233
What the badge actually was
totalBadge = unreadTopics + pendingApprovalsCount, clamped to9+above nine. As one number it is unexplainable: objectstack#7213 measured Home's "pending approvals" card saying 8 while the bell said9+, and read that as the two counts disagreeing. They never did — the bell was carrying a second addend the user could not see.Premise check against origin/main
The issue attributes the formula to
packages/app-shell/src/hooks/useHomeInbox.ts:127-138. That is not where it lives, and worth recording:useHomeInbox.tson currentmainhas nototalBadgeat all. It is Home's one-shot hook — it returnspendingApprovalsCount,notifications,activitiesand nothing derived from them. Its lines 127-138 are the pending-approvalsfetch, i.e. the source of one addend.packages/app-shell/src/layout/InboxPopover.tsx:93—const totalBadge = unreadTopics + pendingApprovalsCount;.The premise itself holds (one opaque summed number), so this is a corrected file pointer, not a dead card.
The dispatch also asked whether the popover already sections the two streams, in which case per-section counts might be the whole fix. It does — three tabs, with a count pill on the Notifications and Approvals triggers — but that is not sufficient, and the reason is the clamp: those pills read
unreadTopics > 9 ? '9+' : unreadTopicsandpendingApprovalsCount > 9 ? '9+' : pendingApprovalsCount. On the exact input the issue is about, a user sees three separate9+s that add up to nothing. Sectioning was never the missing half; exact numbers were.The change
A breakdown line under the popover header, rendered only when there is a badge to explain:
15 total · 12 notifications + 3 pending approvalstotalis visiblyN + M.pendingApprovalsCountprop the Home card and the Approvals Inbox tab read — the number a user reconciles against is literally the one they see elsewhere.unreadTopics, the deduped(topic, title)count from objectui#2765 — the badge's own addend, so 10 identical digests + 2 mentions read as3, matching the badge instead of the raw row count.9+clamp on the badge itself are untouched. Display-only, per the ruling on the card.Labels go through i18n: three new keys (
notifications.badgeTotal/badgeNotifications/badgeApprovals) in all ten locale packs —all-locales-key-parityenforces full parity now, so en+zh alone goes red. They interpolate named placeholders ({{total}},{{unread}},{{approvals}}) rather than i18next's{{count}}, which would additionally drive plural-key resolution these packs carry no forms for.Out of scope and deliberately untouched: the four hardcoded
/apps/setup/...targets in this same file (objectstack#7266).Tests
packages/app-shell/src/layout/__tests__/InboxPopover.badgeBreakdown.test.tsx(5 cases) andpackages/i18n/src/__tests__/inboxBadgeBreakdown-i18n-7233.test.ts(30 cases, ten packs x three keys).The i18n stub in the component test interpolates rather than returning
defaultValueverbatim — a passthrough stub would have asserted on the literal{{total}}and made every numeric case vacuous.Reverse verification
Took the component change out with
git checkout origin/main -- packages/app-shell/src/layout/InboxPopover.tsxand re-ran the pin file. Expected direction: red on the four cases that assert the breakdown, green on the one that asserts its absence — which is what happened, and the split matters. The absence case passes either way by construction, so it is not evidence; the four reds are.Restored, then green:
(The first run of that sweep was
1 failed | 349 passed—all-locales-key-paritycatching the three keys present only in en/zh. That is the gate doing its job, and is what sent the keys to the other eight packs.)Also green:
packages/i18n+packages/app-shelltscbuilds after a dependency-closure build (pnpm --filter '@object-ui/app-shell^...' build),node scripts/check-i18n-call-site-keys.mjs(Every in-scope call-site key resolves against the en pack),node scripts/check-i18n-en-drift.mjs(0 en value(s) changed (3 key(s) added)), and eslint on the changed files (0 errors; 8no-explicit-anywarnings in the test's passthrough component stubs, matching the existingWorkspaceSwitcher.test.tsxpattern).Generated by Claude Code