Skip to content

Home's action centre counts ALREADY-READ messages as needing attention — useHomeInbox never joins sys_notification_receipt #4316

Description

@yinlianghui

Found while implementing #4235 (PR #4315). Not fixed there: that card is the
false-negative half (an unanswered read rendering "You're all caught up"), this
is the false-positive mirror, and closing it means adding a second object read.

What

packages/app-shell/src/hooks/useHomeInbox.ts reads sys_inbox_message
filtered by user_id, newest first, $top: limitand nothing else. It
never reads sys_notification_receipt.

Read-state is not on the message row. ADR-0030 resolved design decision 2 says
so explicitly — "Read-state lives in sys_notification_receipt (per
recipient×channel), not on sys_inbox_message" — and the shipped object
confirms it: packages/services/service-messaging/src/objects/inbox-message.object.ts
declares no read column (the pre-convergence read field named in the ADR's
drift section is gone).

So Home's "Needs your attention" card cannot tell a read message from an unread
one. It lists the five most recent messages unconditionally and badges their
count.

Consequence

A user who opens the bell and reads all nine messages, then returns to Home,
still sees up to five of them listed under "Needs your attention" with a badge —
work that is not waiting on them. The bell, two hundred pixels above, correctly
shows zero unread, because its poll DOES join the receipts
(packages/app-shell/src/layout/AppHeader.tsx, READ_STATES /
receiptByNotif). Two panels on one page, one page load, disagreeing about the
same rows.

This is the exact mirror of #4235: that card was the panel claiming nothing is
waiting when something is; this is the panel claiming something is waiting when
nothing is. #4235's fix does not touch it — it gates the affirmative empty copy
on the read having answered, which is orthogonal to what the answered rows mean.

Why it is not fixed in PR #4315

Adding the receipt join to useHomeInbox is a second read of a second object on
every Home load — which is precisely what #4225 exists to prevent, and #4225's
own suggested route is the opposite direction: extend hooks/sharedUserFeeds.ts
with an inbox feed holding the bell's already-joined 20 rows and let Home derive
its five from them, paying zero extra round trips. That route is the right fix
for this and it is already scoped to #4225, which also warns that touching it
must not re-break the bell (#4230).

Filing separately rather than as a sub-issue of #4225: #4225 is explicitly
observation-class ("Both consumers show correct data today. Nothing is stale,
nothing disagrees") and this contradicts that premise, so it is evidence #4225
should be re-graded rather than a task inside its current scope. If #4225 is
re-scoped to the shared inbox feed, this becomes a sub-issue of it.

Pointers

  • packages/app-shell/src/hooks/useHomeInbox.ts — the unjoined read
  • packages/app-shell/src/layout/AppHeader.tsx — the joined read, for contrast
  • packages/app-shell/src/hooks/sharedUserFeeds.ts — where the merged rows would live
  • objectstack docs/adr/0030-notification-platform-convergence.md — resolved decision 2

Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpm:queue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions