Repository navigation
fix(app-shell): an inbox message the signed-in user caused does not pop - #10521
Conversation
The inbox feed maps sys_inbox_message.actor_id (a bare sys_user id: the read passes no $expand) onto InboxNotification.actor_id, field by field. claimInboxArrivals skips a new unread row whose actor equals the signed-in user id, and still records it as seen. A null actor (digest row) and an absent column (older server) announce as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Reviewed in isolation from the card (objectui#8667 body and its 4 comments), the rulings it cites (objectstack#16974 with its 9 comments, incl. the director ruling 5643392717; objectui#7011; objectui#5203), the PR body, the 7-file diff against merge-base ① Derived judgmentsBehaviour and surface changes the diff introduces
Missing asks: none. The card's four steps and the unlock's two additions are each present at the lines above. Done but not asked: header-comment rewrites in Existing pins the diff edits or deletes
Can the new pins fail
Correctness of the mechanism, checked at the layers this repo owns: the feed's read ( ② Semver level
Changeset prose, sentence by sentence:
Docs/README prose added by the diff: none (no One imprecision in the PR body, not a falsehood: "both objectui#5203 pins are unchanged" names the ③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #8667
Clause-②: no
What this changes
The inbox arrival announcement (objectui#7011: an in-page toast in a visible tab, a desktop notification in a hidden one) now skips a message the signed-in user caused themselves. This is the fifth design constraint of objectui#7011, the one that could not be built until
sys_inbox_messagecarried an actor.The card's four steps, in order:
mergeInboxRows(hooks/sharedUserFeeds.ts) maps the new column field by field:actor_id: typeof m.actor_id === 'string' ? m.actor_id : null. The row literal stays explicit, with no spread.InboxNotification(layout/inboxGrouping.ts) declaresactor_id?: string | null, andDECLARED_KEYSinsharedInboxFeed.rowShape.test.tsxgainsactor_id.claimInboxArrivals(hooks/inboxArrivals.ts) adds one clause to the arrival filter:!causedBy(row, key). The row still enters the seen set, becauseidsis the whole window and the seen update is unchanged.u_aliceas the reader,u_bobas someone else) and assert each direction once.Re-adding an actor field after objectui#5203
This puts an actor field back on
InboxNotificationafter objectui#5203 retiredactor_name. That retirement rested on "no column to map from". The column now exists:maindeclaresactor_id(a lookup tosys_user) onsys_inbox_message.actor_id: n.actorId ?? null(objectstack#16974).So the reason no longer holds for an actor ID. It still holds for a NAME: nothing on the row carries a display name.
actor_nametherefore stays retired, and both objectui#5203 pins are unchanged:inboxGrouping.test.ts;actor_name" case in the row-shape suite.The header comments in
inboxGrouping.tsand the row-shape suite now say this.Measured before writing (the dispatch's mechanism hypotheses)
bc4efba.sys_inbox_messageread (useSharedInboxFeed) passes no$expand.$expand, the adapter takes theclient.data.findpath, andconvertQueryParamsadds no expansion.ObjectQL.findexpands related records only underif (ast.expand && Object.keys(ast.expand).length > 0 …). So the read returns the string the inbox channel stored.null: the dispatcher sets noactorIdon a collapsed group.null("no known actor"), the same test the upstream dispatcher applies (typeof p.actorId === 'string' ? p.actorId : undefined).useInboxArrivalNotifierreadsuseAuth().user?.idfrom@object-ui/authand passes it as the first argument ofclaimInboxArrivals. It is the same iduseSharedInboxFeedfilterssys_inbox_message.user_idby. The hook-level pin covers that wiring through theu_aliceauth mock.Null and absent actors (the unlock ruling)
Both mean "no known actor", and both announce as before:
null: a digest row;actor_id: a server older than the column.causedByanswers yes only for a string equal to the signed-in user id. Pinned three ways:actor_id: nullannounces;actor_idkey announces, and the case asserts the key is really absent;actor_idproduces the sameDECLARED_KEYSset, withactor_idnull.Tests (at HEAD
9f24501)bc4efba, before any edit (re-verifying the four already-pinned constraints, per triage):inboxArrivals,useInboxArrivalNotifier,sharedInboxFeed.rowShapeandinboxGroupinggave 4 files, 43 tests passed.pnpm exec vitest run packages/app-shell/src/hooks/ packages/app-shell/src/layout/: 83 files, 674 tests passed.pnpm turbo run type-check --filter @object-ui/app-shell --concurrency=2: 30 of 30 tasks successful.tsc -p tsconfig.test.json --listFilesOnlylists all seven edited files, so the@ts-expect-errorpin and the new tests are in the checked program.eslint --print-config) has noparserOptions.projectorprojectService, so linting is not type-aware and this diff cannot move a verdict on an untouched file. The repo-widepnpm lintis left to CI.check:control-bytes,check:test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shapecheck:new-line-citations(0 new)check:changeset-claims,check:pending-changeset-literals,check-changeset-presence,check-changeset-no-major,check-changeset-overwritecheck:spec-symbols,check:i18n-keys,check:side-effects-array,check:unreferenced-sources,check:doc-example-readerscheck-governed-queue-guard --teston the diff answers "An ordinary pull request".check:changeset-claimsreports that the pending6730-shared-activity-type-bucket.mdnameshooks/sharedUserFeeds.ts. That paragraph is aboutmapActivityRows, which this diff does not touch. I re-read it and it still holds.Reverse verification (one-shot, on the committed tree, every leg restored)
Each mutation went through
ablation-replace.mjs, whose anchor must hit and whose write and restore are checked on disk. The direction was predicted before the run. Vitest covered the arrival, hook and row-shape suites (42 tests).!causedBy(row, key)clauseactor_id: nullcausedByreads a null or absent actor as "me"After every leg,
git hash-objectequals the HEAD blob for both mutated files, andgit diff HEADis empty.Acceptance notes
content/docs/guide/notifications.mddescribes the shared inbox feed but states none of the arrival rules, so no guide text changed.InboxNotificationis not exported from the@object-ui/app-shellbarrel. Its one reader outside the package,apps/console/src/inbox-arrival-preview.tsx, imports it by source path and builds rows withoutactor_id, so it is unaffected.Dispatched by the
domain:uiseat 5 PM; sessionhttps://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy.Generated by Claude Code