fix(app-shell): Home's action centre needs an ANSWER before it says "all caught up" (#4235) - #4315
Merged
Merged
Conversation
…all caught up" (#4235) useHomeInbox swallowed every failed sys_inbox_message read to [], so a denial reached HomeActionCenter wearing the shape of an empty inbox. objectstack#7344 measured the mechanism: 403 PERMISSION_DENIED on that object for every non-admin session, while /api/v1/notifications - a projection of the same rows - answered. The hook now reports notificationsStatus; the affirmative copy renders only on 'ready'. Source unchanged: ADR-0030 names the table read as the consumer channel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
|
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 #4235
Home's action centre told a user with nine unread messages that they were all
caught up, with no badge. This makes that pair unreachable: the affirmative copy
now renders only after the inbox read has actually answered.
The card's premise did not survive measurement — the source is right
#4235 reads the symptom as a WRONG SOURCE (
sys_inbox_messagerather than/api/v1/notifications) and asks for the panel to be re-pointed. Measured, thetable read is the sanctioned consumer channel and re-pointing it would be a
regression:
bell reads
sys_inbox_message". Its P0 phase: "UI bell readssys_inbox_message". Its cross-repo cut-over section: "Repoint the Consolebell (
AppHeader/InboxPopover/record views) fromsys_notificationtosys_inbox_message(themineview), joiningsys_notification_receiptfor read-state."
MessagingService.listInbox(packages/services/service-messaging/src/messaging-service.ts,the block headed "Inbox read API (ADR-0030) — backs /api/v1/notifications")
reads
sys_inbox_messagewhereuser_id, orderedcreated_at desc, joinedwith the same receipts. Reading the table is reading the API's own source one
hop earlier, not a second opinion.
exactly "serve the personal inbox from a dedicated authenticated route instead
of the generic data API". The maintainer ruling of 2026-08-11 chose Option A
instead — grant the table read — and objectstack#7586 landed it.
So this PR keeps the source and fixes what actually produced the symptom. A pin
was added for the query shape, so a future re-point has to argue with ADR-0030
rather than slip past it.
The cross-run contradiction, resolved: it is the signed-in user
The card records two QA runs on the same console pin
09987b68disagreeing —objectstack#7514 saw this panel empty with 9 unread, objectstack#7517 used it as
the working control against the dead bell — and asks which is stale. Neither
is. objectstack#7344 measured the mechanism in a browser on 2026-08-10:
Every inbox read returned
403 PERMISSION_DENIEDfor a plain member, because noshipped permission set granted the object — while
/api/v1/notifications, adedicated authenticated route, answered those same users with their unread rows.
An admin session read the table and the card worked. One build, one pin,
opposite screenshots, decided by who was signed in. Both QA reports are true.
The 403 itself is closed server-side by objectstack#7586. What was not closed —
and is what this PR fixes — is that the console converted that denial into good
news, so the next denial, outage or malformed answer reproduces the identical
silent lie.
What changed
useHomeInboxcaught every failed read to[], handingHomeActionCenteranempty array indistinguishable from an empty inbox. It now reports
notificationsStatus:idleloadingreadyerrorThose are
MetadataProvider's four words on purpose — #4300 landed this samerule for the app list ("an unloadable app list is UNKNOWN, not 'no default
app'") and ruled one status dialect, no second one.
HomeActionCentergates the affirmative copy onready, and renders a quiet,non-affirmative notice otherwise — alongside the approvals row when only
that half answered, so the panel never silently drops the half it failed to
read. The prop is required rather than optional-with-a-default: a call site that
cannot say whether its rows are an answer should not reach the affirmative copy
by staying silent.
A missing object stays an answer. A 404 /
OBJECT_NOT_FOUNDmeans thisdeployment has no inbox pipeline, so nothing is waiting — it degrades to the
caught-up state exactly as before, using the same
isMissingResourcesplitsharedUserFeedsandAppHeaderalready apply. Classifying it as an errorwould have put a permanent error line on Home for every community build without
service-messaging. Both polarities are pinned.
No new i18n key. The quiet notice reuses
errors.unknownandcommon.loading, which exist in all ten packs;check:i18n-keysconfirms bothresolve and both inline defaults match their
envalues byte for byte.packages/i18nis held by #4040 tranche 2 and is untouched.Scope
The bell is untouched — #4230 ruled TWO independent panels, and its raw-table
read is ADR-0030 by design. PR #4284's tripwire suite is unchanged and green.
The #4225 shared-feed refactor is deliberately NOT done here: that card is
sequenced after this one, it touches mark-read, and
sharedUserFeeds.tscarriesno change in this PR.
Verification
pnpm exec vitest run packages/app-shell— the whole affected package: 345 files, 3303 passed, 1 skipped. PR test(app-shell): pin the bell panel's Unread/All render oracle — #4230's console predates #4199 (#4230) #4284's bell tripwire suite is in that run, unchanged and green.pnpm exec vitest run packages/app-shell/src/console/home/__tests__/— 6 files, 34 tests, all green.pnpm --filter @object-ui/app-shell type-check— green (bothtsc --noEmitandtsc -p tsconfig.typetests.json).check:i18n-keys,check:control-bytes,check-changeset-presence,check-changeset-no-major— all green.Reverse verification, predictions written before the run, fix reverted by
git checkout origin/main --on the two source files with the tests untouched:predicted 4 red / 5 green, measured 4 failed | 5 passed, and the four are
exactly the unanswered-read cases:
Two of the greens are declared non-discriminating rather than dressed up as
evidence: the happy path was never broken (measured before any edit — nine rows
render and no caught-up copy appears at
origin/main), and the 404 controlguards this change's blast radius rather than reproducing the defect.
Generated by Claude Code