Skip to content

perf(app-shell): scope the inbox receipt read to the listed messages (objectui#7392) - #10047

Merged
os-tesla merged 3 commits into
mainfrom
claude/issue-7392-inbox-receipt-incremental-read
Sep 19, 2026
Merged

os-tesla merged 3 commits into
mainfrom
claude/issue-7392-inbox-receipt-incremental-read

Conversation

@os-tesla

Copy link
Copy Markdown
Collaborator

Fixes #7392

Clause-②: no

The inbox poll re-read sys_notification_receipt in full on every tick — filtered by user_id + channel:'inbox', $top: 200, no cursor of any kind — beside the sys_inbox_message read's $top: 20, at 10s foregrounded and 60s hidden. mergeInboxRows uses the receipts as a lookup table keyed by notification_id while mapping over the MESSAGE rows, so every receipt outside that window of 20 was fetched, indexed and dropped, six times a minute.

The read now names those messages' notification ids as an $in comparand, and is skipped outright when the window lists nothing a receipt could belong to.

1. The badge verification — taken FIRST, because it decides the shape

The dispatch named this as the one way the change could go silently wrong: the receipt set feeds the bell's unread badge, and a receipt is not a "read" marker (delivered is a receipt and is NOT read). So "read only the receipts of the listed messages" invites turning an unread TOTAL into "unread among the newest 20" with no error and no red test.

Where the number actually comes from, expression by expression:

# where the expression
1 layout/InboxPopover.tsx const totalBadge = unreadTopics + pendingApprovalsCount; — what the inbox-bell-badge testid renders
2 same file const unreadTopics = allGroups.reduce((sum, g) = then sum + (g.unreadCount greater-than 0 ? 1 : 0), 0), over groupNotifications(notifications)
3 hooks/useInboxBell.ts notifications is useSharedInboxFeed().value with the local read overlay applied; it also exposes unreadCount = notifications.reduce(...)
4 hooks/sharedUserFeeds.ts that value is mergeInboxRows(rows, receipts), whose body is return rows.map((raw) = ... )one output row per sys_inbox_message row
5 hooks/useHomeInbox.ts Home's unreadTopicCount applies groupNotifications to the same rows and reduces it the same way (#4329)

Reading: the badge's inbox addend is bounded by the MESSAGE read's $top: 20, never by the receipt set. mergeInboxRows maps over the message rows; a receipt whose notification_id is not among them is never looked up and contributes nothing. The inbox addend has therefore been "unread within the $top: 20 window" since #4225 gave the bell and Home one feed — the receipts only ever supplied read-state to rows the message query had already chosen.

⇒ The fall-back clause does not fire. The count stays whole because it was never derived from the receipt set's size, and the second candidate shape is on the table.

Shape chosen: receipts for the listed messages. The since-cursor shape owes missed-update handling and cursor persistence for an answer that is discarded every tick anyway — the feed keeps no receipt state between ticks, so a cursor would have had to grow one.

2. Measured before/after — the card's closing criterion

Measured with a fake backend that honours $filter (equality and $in) and $top, so the row count is a reading of the query the feed wrote rather than an echo of the fixture. Fixture: a user with 200 inbox receipts, a $top: 20 message window.

per steady-state tick before after
sys_notification_receipt rows delivered 200 20
$top sent 200 20 (the id count)
receipt requests, window with nothing to join 1 per tick (7 per 60s window) 0

The "after" row count is the number of receipts among the listed messages, so it is at most the window size and in ordinary use well under it — the card's "a few". The two numbers above are produced by the same assertions in sharedInboxFeed.receiptScope-7392.test.tsx: the before figure is the wouldHaveDelivered control (the replaced filter, run against the same store), and it is re-measured by the reverse verification in section 4.

$top is now the id count, and that bound is exact rather than guessed headroom: sys_notification_receipt declares its key { fields: ['notification_id', 'user_id', 'channel'], unique: true }, so at most one row per named id can match.

3. The existing #7249 pin — it went red, and that red was correct

It pins frequency, not payload: the top=200 mentions in its docblock and in one comment are prose, and the assertion is expect(receiptReads()).toBe(inboxReads()). It went red anyway, at expected +0 to be 7, because its fixture answers the message read with an empty inbox — the one window where a receipt read is now legitimately not due, so the old unconditional read was what made that equality hold there.

Updated deliberately, and strengthened rather than loosened:

4. Reverse verification — predicted, then measured

Run against the committed fix (git checkout HEAD -- to restore, tree confirmed identical by blob hash afterwards, git diff HEAD empty), by putting the pre-change read back:

  • Predicted: three payload cases red, badge and read-state green.
  • Measured: 6 failed | 5 passed across the two files. Right about direction, wrong about the count, and the gap is the interesting part:
    • read-state stayed GREEN — the old read carried the same answer, it just carried more rows to get there. That is the behaviour-preservation half, and it holds under both reads.
    • badge went red but not on the badge: its unreadTopics equality is evaluated first and passed under the old read too; what failed after it is the row arithmetic beside it (expected +0 to be 180 — nothing dropped, because nothing was narrowed). The number is invariant, the payload is not.
    • the payload cases failed as predicted (expected 200 to be 20; the $in comparand expected undefined), and the new empty-window case failed with expected 7 to be +0.
  • On-disk proof of the mutation before the run: the injected $top: 200, occurrence counted 1, the removed $in: notificationIds counted 0. No dist is involved — the pins import ../sharedUserFeeds relatively, so the mutation lands on the module under test with no exports resolution in between.

5. Side effect worth naming: a latent truncation hazard goes with it

The old read carried no $orderby under a $top: 200 ceiling. A user holding more than 200 inbox receipts could therefore have the newest messages' receipts fall outside the 200 returned, and already-read messages would render unread and inflate the badge. Narrowing the filter to the listed ids removes that failure mode rather than merely shrinking it; it is not a separate card because it is the same read.

6. Cost paid

The two reads are sequential where they were a Promise.all: the receipt query cannot be written until the message read says which notifications are in the window. One extra round trip per tick, on a background poll, against ~180 discarded rows saved on each of them. Stated here rather than buried — it is the only thing this change makes worse.

Verification

check result
pnpm exec vitest run — the two inbox pins 11 passed (6 new + 5 cadence)
pnpm exec vitest run — 7 consumer suites (twoSurfaces, rowShape, transient404, arrival notifier, inboxArrivals, global page blocks, AppHeader inbox variant, Home action centre, console HMR reloader) 95 passed
pnpm --filter '@object-ui/app-shell^...' build (dependency closure, built before any typecheck) exit 0
pnpm --filter @object-ui/app-shell type-check (tsc --noEmit && tsc -p tsconfig.test.json), run after the last edit exit 0
test tsconfig really covers the new pin tsc -p tsconfig.test.json --listFiles hits receiptScope-7392.test.tsx 1, cadence-7249.test.tsx 1, control hooks/sharedUserFeeds.ts 1, 4114 files in the program
node scripts/check-changeset-presence.mjs exit 0 — 1 changeset declared for 1 released package
control-byte self-scan over the four touched files zero hits, with a must-hit control file in the same command that did hit

Acceptance notes


Generated by Claude Code

…(objectui#7392)

The inbox poll re-read `sys_notification_receipt` in full on every tick —
filtered by `user_id` + `channel:'inbox'`, `$top: 200`, no cursor — beside
the `sys_inbox_message` read's `$top: 20`, at 10s foregrounded and 60s
hidden. `mergeInboxRows` uses the receipts as a lookup keyed by
`notification_id` while mapping over the MESSAGE rows, so every receipt
outside that window of 20 was fetched, indexed and dropped, six times a
minute.

The read now names those messages' notification ids as an `$in` comparand
and is skipped outright when the window lists nothing a receipt could
belong to. `$top` becomes the id count, which is an exact bound rather
than a guessed headroom: the object declares its key
`{ fields: ['notification_id', 'user_id', 'channel'], unique: true }`, so
at most one row per named id can match.

No number the user sees moves. The bell badge is `unreadTopics +
pendingApprovalsCount` and `unreadTopics` folds this feed's rows, which
are one per listed message — bounded by the message read's `$top: 20`
since #4225 gave the bell and Home one feed, never by the receipt set.
Home's `unreadTopicCount` folds the same rows the same way (#4329). The
receipts only ever supplied read-state to rows the message query had
already chosen.

The two reads are sequential where they were a `Promise.all`: the receipt
query cannot be written until the message read says which notifications
are in the window. One extra round trip per tick on a background poll,
against ~180 discarded rows saved on each of them.

The #7249 cadence pin goes red on this change and that red is correct: it
asserts the receipt read is exactly as frequent as the message read, on a
fixture whose inbox was empty — a window where no receipt read is now due.
Its fixture now lists rows so the equality is exercised where it means
something, the equality itself is unchanged, and the empty window is
pinned separately.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
@github-actions

github-actions Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 1 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/6730-shared-activity-type-bucket.md

  • names hooks/sharedUserFeeds.tspackages/app-shell/src/hooks/sharedUserFeeds.ts — edited by this change

    mapActivityRows in hooks/sharedUserFeeds.ts — the feed behind the AppHeader bell's Activity tab, Home's activity card and the exported ActivityFeed panel — carried the third hand-written reading of that column in this repo, and it bucketed every value outside created / deleted / commented / mentioned as update. That is not a missing decision; it is a wrong one stated out loud: a scheduled meeting, a login, a nightly system rollup and an author's contract_countersigned all rendered as "somebody updated this record".

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 237e5b8d2 (merge-base with origin/main): 3 file(s) changed outside .changeset/, read against 1220 pending declaration(s) that publish a body (1780 pending in total). · run

…ediction

The pin shipped with the direction it predicted. Running it turned in a
different count — 6 failed, 5 passed across this file and the #7249 pin,
not the three-red/two-green that was written down — and the gap is the
informative part: the `badge` case's `unreadTopics` equality passed under
the old, wider read too, so what went red there is the row arithmetic
beside it, not the number a narrowing could have moved.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3057.0 KB 3104.5 KB
Main entry chunk (gzip) 145.9 KB 350 KB
Entry file index-B_1eoE-D.js
Status PASS

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 545.92KB 130.72KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 221.99KB 61.72KB
fields (index.js) 249.62KB 63.02KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.90KB 10.97KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 50.26KB 14.36KB
plugin-charts (index.js) 71.73KB 20.08KB
plugin-chatbot (index.js) 198.20KB 47.14KB
plugin-dashboard (index.js) 132.96KB 35.17KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 255.18KB 66.49KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 139.56KB 35.40KB
plugin-gantt (index.js) 167.62KB 41.26KB
plugin-grid (index.js) 213.44KB 58.21KB
plugin-kanban (index.js) 48.71KB 15.17KB
plugin-list (index.js) 113.53KB 27.99KB
plugin-map (index.js) 21.48KB 6.99KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.68KB 8.95KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.18KB 21.05KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 109.04KB 36.08KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.38KB 1.98KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.74KB 2.54KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 15.71KB 5.30KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.04KB 5.36KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Brings objectui#7394 (PR #10045, 237e5b8) in. It moves packages/plugin-list,
which this branch does not touch — no conflict, and the package checks are
re-run after this merge rather than before it.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018HrVaotisyhgmot9o2MLRq
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3057.0 KB 3104.5 KB
Main entry chunk (gzip) 145.9 KB 350 KB
Entry file index-B_1eoE-D.js
Status PASS

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 545.92KB 130.72KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 221.99KB 61.72KB
fields (index.js) 249.62KB 63.02KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.90KB 10.97KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 50.26KB 14.36KB
plugin-charts (index.js) 71.73KB 20.08KB
plugin-chatbot (index.js) 198.20KB 47.14KB
plugin-dashboard (index.js) 132.96KB 35.17KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 255.18KB 66.49KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 139.56KB 35.40KB
plugin-gantt (index.js) 167.62KB 41.26KB
plugin-grid (index.js) 213.44KB 58.21KB
plugin-kanban (index.js) 48.71KB 15.17KB
plugin-list (index.js) 113.53KB 27.99KB
plugin-map (index.js) 21.48KB 6.99KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.68KB 8.95KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.18KB 21.05KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 109.04KB 36.08KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.38KB 1.98KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.74KB 2.54KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 15.71KB 5.30KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.04KB 5.36KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Copy link
Copy Markdown
Collaborator Author

Post-merge verification (d8cf07584)

origin/main merged in as a merge commit, not a rebase. Two-legged ancestry reading on this shallow checkout, taken before the merge: subject merge-base --is-ancestor 237e5b8d2 46f693d7a exit 1 (not an ancestor), control leg with the branch base 3fa3b3eb6 exit 0 — so the negative is a real reading, not a shallow-checkout artefact. After the merge the subject leg is exit 0.

The re-run was load-bearing, not procedural. @object-ui/app-shell depends on @object-ui/plugin-list — so objectui#7394's landed work sits inside this branch's dependency closure even though this branch touches none of its files. The closure was rebuilt first (packages/plugin-list build: Done in that run's log), and only then did the typecheck read it; a typecheck taken before the merge would have read the pre-merge .d.ts.

reading, all AFTER the merge result
pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build then pnpm --filter @object-ui/app-shell type-check (tsc --noEmit && tsc -p tsconfig.test.json) VERDICT command-exit 0, 185s held
pnpm exec vitest run packages/app-shell/ Test Files 736 passed (736) · Tests 7302 passed, 1 skipped (7303), VERDICT command-exit 0, 781s held
check:control-bytes, check:new-line-citations, check:changeset-claims, check:pending-changeset-literals, check-changeset-no-major all exit 0
check-changeset-presence, re-derived against the new merge-base 237e5b8d2 exit 0 — "3 source file(s) of 1 released package(s) changed … declares 1 changeset(s)"
conflicts none; the merge was clean and the PR's diff against main is still the same 4 files

Both long runs exceeded the 600s foreground cap and were collected in-round by blocking on their pid, never by polling.

mergeable_state moved behind to blocked, mergeable: trueblocked is the draft plus checks still in flight on the new head. Snapshot at d8cf07584, a read and not a wait: 36 check runs, total_count 36 equals the returned array length so nothing is truncated — 12 success, 3 skipped, 20 in_progress, 1 queued, 0 failing. Convergence and landing belong to the claiming seat.

Generated by Claude Code


Generated by Claude Code

@os-tesla
os-tesla marked this pull request as ready for review September 19, 2026 22:59
@os-tesla
os-tesla added this pull request to the merge queue Sep 19, 2026
Merged via the queue into main with commit e86445f Sep 19, 2026
38 checks passed
@os-tesla
os-tesla deleted the claude/issue-7392-inbox-receipt-incremental-read branch September 19, 2026 23:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

app-shell: the inbox tick re-fetches sys_notification_receipt?top=200 in full every 10 s — never incrementally

2 participants