Skip to content

fix(app-shell): the activity feed asks only a caller who may read sys_activity, and a failed read is not an empty feed (objectui#12081 item 8) - #12095

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-12081-activity-bell-scope
Oct 10, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-12081-activity-bell-scope

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #12081 (item 8)

Clause-②: no

Item 8 only, as triage directed. Items 2, 3, 4 and 7 stay on the card, and #12081 remains open.

Measured first

objectui main at 023f00d4. useSharedActivityFeed sent find('sys_activity', { $orderby: { timestamp: 'desc' }, $top: 20 }) for every signed-in user. A failure other than 404 went to markFailed(), so the store's status became error. The hook still returned .value alone. Three surfaces read that value: the header bell's Activity tab, the global:notifications page block (the same popover), and Home's activity card (through useHumanActivityFeed). Each received [] and showed "No recent activity", followed by a "View all activity" link to a list page that refuses the same user. The card says the widget "shows nothing". It actually shows the empty-state message.

Reproduced on 023f00d4 with only the test file changed. The new block in AppHeader.inboxVariant.test.tsx was 2 red and 2 green:

  • "a member without the grant issues no sys_activity request…": expected [ { object: 'sys_activity', … } ] to have a length of +0 but got 1.
  • "a 403 from the server (a stale grant) reads as failed…": the failure notice was never rendered, and the DOM held "No recent activity".

objectstack origin/main at 86da1949, read only.

  • The 403 is object-level. member_default in plugin-security's default-permission-sets.ts grants read on the two inbox objects. Its own comment says "sys_activity is deliberately NOT included: it is not a per-user-scoped shape". git grep sys_activity 86da1949 -- examples/ returns nothing, so no shipped example app grants it either. The admin sets reach it through their * entries.
  • The server already narrows an allowed read. plugin-audit's activity-read-visibility.ts adds a parent-record filter to every non-system find, findOne, count and aggregate on sys_activity. A reader who holds the object grant sees only rows about records they can open.
  • So a narrower query cannot help a user without the grant. The record Discussion tab's activity half is also a find('sys_activity', …), filtered by object_name and record_id. It hits the same object check, and for that user it shows its refusal state (objectui#11195), not rows.

PM mechanism assumption 2 holds. That is why this PR takes triage's second branch: decide from the user's grant before asking.

Changes

1. The grant is checked before the read (sharedUserFeeds.ts). The hook reads usePermissions().can('sys_activity', 'read'). This is the permission check the shell already uses for reads: ObjectDataPage's route gate and UnifiedSidebar's nav gate read it the same way. I looked at the affordance map from objectui#12084 and did not use it. Its rows are write affordances only, and its census explicitly leaves can(x, 'read') read gates outside its family. The check has three outcomes, beside the unchanged presence rule:

  • permissions not loaded (no provider mounted, or a provider whose refetch failed): unknown, so the feed reads, as before;
  • loaded and granted: the feed reads;
  • loaded and refused: no cache key and no request, and the hook reports readable: false.

2. The status reaches the surfaces. The hook now returns ActivityFeedReading, which is value, status and readable together. readable is a separate field rather than a fifth status value, so the four-value status set (#4300) is unchanged.

3. The surfaces.

  • InboxPopover takes a required activity prop in place of activities. With readable: false it shows no Activity tab trigger, no tab body and no "View all activity" link, and it ignores a remembered Activity tab choice. With error it shows "An unexpected error occurred.". With idle or loading it shows "Loading…". It shows "No recent activity" only when the status is ready.
  • AppHeader and global-notifications-renderer pass the new value through. AppHeader's exported props are unchanged: rows a host passes as activities are treated as ready.
  • Home is an extension beyond the claimed files, named here as the dispatch asked. useHomeInbox returns activity instead of activities. HomeActivity renders nothing when readable is false and shows the same three states otherwise. HomePage removes the 360px grid column when the card is absent.

4. The cache key includes the signed-in user. This is a small, bounded fix in the same file; the reasons are below.

The messages reuse existing locale keys, errors.unknown and common.loading, the same pair HomeActionCenter shows for an inbox read that has not answered. No locale key is added. Nothing is added to the package entry: sharedUserFeeds, InboxPopover, HomeRail and useHomeInbox are not re-exported from src/index.ts. AppHeader is re-exported, and its props are unchanged. So Clause-② stays no.

The fix in the same file: the activity cache key

The store keyed the activity feed on the adapter alone. Two facts make that wrong:

  • the server narrows the rows per user;
  • signing out does not reload the page (AuthProvider notes that "no sign-out call site reloads the page"), so this module-level store keeps running.

As a result, the next user to sign in on the same browser tab was shown the previous user's rows as ready, within the 30s freshness window and until a refetch landed. The key is now the adapter plus the signed-in user id, as the inbox feed's key already is. The user id is part of the key only: the query names no user, so a host without auth still reads. The test "re-keys on the signed-in user" pins it.

Why this fits in this PR rather than a separate card:

  • it is the same defect class as item 8 (the feed must show only what this user can see);
  • the fix is mechanical, and its shape already exists in the same file (the inbox feed's adapter-plus-user key);
  • the claim names no other holder of this file;
  • it runs under the same gates and adds no new verification surface.

Evidence

Head. All runs below are on f1f0ba557: 023f00d4 plus this change, merged with origin/main at de302c73. That merge brought in objectui#12088 and #12090, and neither touches any file in this diff.

Type-check. pnpm --filter @object-ui/app-shell type-check (tsc --noEmit && tsc -p tsconfig.test.json) exits 0.

Tests, narrowed. I ran every test file that names any of the seven changed modules or the components built on them (sharedUserFeeds, useHomeInbox, AppHeader, InboxPopover, global-notifications-renderer / GlobalNotificationsRenderer, HomeRail, HomePage, HomeLayout, ConsoleLayout, global:notifications), selected by git grep -l, plus affordanceGrantMap-12082.test.tsx. That is 121 files, in two runs.

  • First run: 60 of 61 files passed, 709 of 710 tests.
  • The one failure was ApiConsolePage.requestPreset-10591. It hit a 10s timeout while the page still showed "Initializing application…", under a full shared box. That happens before the shell renders anything this PR touches. Run alone on the same head it passes 12 of 12.
  • Second run: 60 of 60 files, 735 of 735 tests.

The rest of app-shell's 1,250 test files are left to CI: no vi.mock of the changed modules exists outside the files above.

Reverse checks. Each check was run from the committed d74a507 with ablation-replace.mjs, which checks that the anchor matched and that the restore leaves the file identical to HEAD (git diff HEAD empty after each leg). The tests run were the new objectui#12081 cases in the hook suite and the AppHeader suite.

  • Grant check forced open (readable = true || …): 3 red, namely the two refusal cases in the hook suite and the AppHeader member case.
  • User dropped from the cache key: 1 red, "re-keys on the signed-in user".
  • Popover empty message no longer gated on ready: 1 red, the 403 case.

Gates.

Gate Result
check:new-line-citations 0 new citations
check:control-bytes OK
check:i18n-keys exit 0
check-changeset-presence 1 changeset for 27 source files
check-changeset-no-major exit 0
check-vi-mock-inherit / -override-shape / -specifiers OK
check-test-path-roots OK
check:esm-specifiers / check:self-import / check:phantom-deps exit 0

Lint. I ran eslint on the 27 touched .ts/.tsx files only:

  • result (--format json): 27 files, 0 errors, 96 warnings, and none of the warnings is on a line this PR adds;
  • the full lint run is left to CI;
  • searching eslint.config.js for projectService, parserOptions.project or TypeChecked finds nothing, so type-aware linting is not enabled, and this diff cannot change the lint result of a file it does not touch.

Eager closure. check:eager-closure passes at d74a507: 3169.0 KB gzipped, headroom 35.6 KB. Base 023f00d4, built in a separate worktree, measures 3168.6 KB. The difference is +349 bytes gzipped (+1,202 raw), and the eager chunk count is 290 at both. The head figure was taken before the merge.

Acceptance notes

  • The useHomeInbox mocks in seven HomePage.* tests never carried notificationsStatus. This PR only replaces their activities key. The missing key is fixture drift; it was not changed here and no card was filed.
  • I did not check whether MePermissionsProvider refetches when a different user signs in on the same tab. If it does not, the next user's grant check reads the previous user's grants. The result is then either a refused read, which now shows as an error, or a missing Activity tab until reload. This is unmeasured, so no card was filed. Who picks it up: no one.
  • No guide describes the bell's Activity tab, so no documentation became false. content/docs/guide/notifications.md covers only the notifications half.

Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z


Generated by Claude Code

…_activity, and a failed read is not an empty feed (objectui#12081 item 8)

The header bell's Activity tab, the global:notifications block and Home's
activity card share one sys_activity read. It went out for every signed-in
user; a caller whose sets grant no read on sys_activity (objectstack's
member_default names it deliberately not) got 403 on every page, and the
hook handed its consumers `.value` alone, so all three said "No recent
activity".

- the caller's object grant decides before asking, through
  usePermissions().can('sys_activity', 'read'); unknown permissions (no
  provider, not loaded) still read, and only a loaded refusal sends nothing.
  The consumers render no widget for it.
- the feed returns its snapshot with its status (ActivityFeedReading), and
  the Activity tab and Home's card render error / loading instead of the
  empty copy when the read has not answered.
- the cache key carries the signed-in user beside the adapter, as the inbox
  feed's does: the server narrows these rows per caller and a sign-out keeps
  the SPA running.

Claude-Session: https://claude.ai/code/session_01B1gHb9baeX7oioD5sHVm7z
Co-authored-by: Claude <noreply@anthropic.com>
…ion (objectui#12081 item 8)

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

github-actions Bot commented Oct 10, 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/11659-console-record-ux.md

  • names HomePage.tsx → packages/app-shell/src/console/home/HomePage.tsx — edited by this change

    The backup-password reminder no longer appears in the user's first session. 「建议设置一个备用密码」 showed on the environment home right after a new user built their first app: the reminder was gated on a home-visit count (quiet on the first home mount, shown on the second), and coming back to home after building is the second mount inside the first sitting. It now stays quiet for 12 hours after the first home visit on the device, so it first shows on a later day's visit. The first-visit record (os:recovery-pw-first-seen) now holds a timestamp; a device that holds the old '1' flag starts the 12 hours over rather than reading it as long ago. When storage is unavailable it stays quiet. RecoveryPasswordReminder moves out of HomePage.tsx into its own module; it is not exported from the package entry, and its other conditions (dismissed, SSO-enforced, has a local password) are unchanged.

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 ce991bd70 (merge-base with origin/main): 27 file(s) changed outside .changeset/, read against 302 pending declaration(s) that publish a body (308 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 290 chunks) 3169.9 KB 3204.6 KB
Main entry chunk (gzip) 73.8 KB 350 KB
Entry file index-CviGQNs7.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) 19.75KB 7.29KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
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) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 587.83KB 141.44KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 269.55KB 68.24KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.26KB 11.71KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 14.32KB 5.17KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
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) 16.04KB 3.92KB
plugin-calendar (index.js) 53.43KB 15.54KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.52KB 47.99KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.53KB 49.80KB
plugin-detail (index.js) 249.19KB 65.68KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.30KB 45.92KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 249.43KB 69.15KB
plugin-kanban (index.js) 52.77KB 16.56KB
plugin-list (index.js) 120.09KB 30.26KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.06KB 11.80KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.93KB 23.24KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
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) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
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) 1.13KB 0.65KB
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) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Spec Main Shape Gate red on f1f0ba557b is not this PR's · domain:ui seat 3, session_01B1gHb9baeX7oioD5sHVm7z, 2026-10-10T13:49Z

  • The failing check: Spec Main Shape Gate, check run 114225509558.
  • Why it is not this PR's:
    • The annotation set is identical, file and line, to the one anchored on objectui#12093: page-variables.test.tsx:213, record-picker-label-association.test.tsx:170, authoring-nodes-11364.test.ts:131/:143, element-repeater-data-source-11880.test.ts:242/:247, and grid-default-filters-gantt-map-filter-round10-6152.test.ts:219/:220/:234/:235.
    • None of those files is in this PR.
    • The cause is objectstack PR #22421 (317cddd40d). The gate is red for every objectui PR.
  • The fix: anchored on objectui#12093 and folded into objectui#12085's in-flight dispatch. It is not on main yet. No re-run is spent.
  • This PR: ACCEPT 6098089464 stands, and the PR stays a draft. When objectui#12093's fix is on main, it merges main and lands through the queue once every check is green.

Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 290 chunks) 3169.7 KB 3204.6 KB
Main entry chunk (gzip) 73.8 KB 350 KB
Entry file index-Dp2U-0YL.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) 19.75KB 7.29KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
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) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 586.66KB 141.23KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 269.55KB 68.24KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.26KB 11.71KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 14.32KB 5.17KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
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) 16.04KB 3.92KB
plugin-calendar (index.js) 53.43KB 15.54KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.52KB 47.99KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.53KB 49.80KB
plugin-detail (index.js) 249.19KB 65.68KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.30KB 45.92KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 249.43KB 69.15KB
plugin-kanban (index.js) 52.77KB 16.56KB
plugin-list (index.js) 120.09KB 30.26KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.06KB 11.80KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.97KB 23.24KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
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) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
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) 1.13KB 0.65KB
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) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
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) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c927efc38ebce8a4686f84f69b05b385746ced90
Local-runs: none

Reviewed on the landing head (the branch merged main at ce991bd7, which carries objectui#12100), against card objectui#12081 (body and every comment), PR objectui#12095 (body, file list, every comment, net diff origin/main...c927efc3) and the head's check-runs. Not the dispatch order, not the dispatching seat's conclusions.

① Derived judgments

  • Scope: item 8 only, card stays open — right. PR body line one is Part of #12081 (item 8); items 2, 3, 4 and 7 are named as staying on the card. No closing keyword anywhere: the PR title, the PR body and all four commit messages on the branch were scanned for close/closes/closed/fix/fixes/fixed/resolve/resolves/resolved followed by a card number and none matches (the commit titles' fix(app-shell): is a conventional-commit type followed by a scope, not a card number). Merging this PR closes nothing.
  • Net diff — right. origin/main (ce991bd7)...c927efc3 is 28 files, +718/−123, equal to the PR's file list: 7 product-source files, 1 changeset, 20 test files (2 new). No content/docs/** file; the only prose face the diff adds is the changeset.
  • Merge shape — right, no conflict resolution. c927efc3 is a two-parent merge (f1f0ba55, ce991bd7). Its combined diff (git diff-tree --cc -p) is empty. Each of the 28 files at c927efc3 is blob-identical to the accepted head f1f0ba557b (28 of 28 same blob id). The one commit main gained between de302c73 and ce991bd7 (objectui#12100) touches none of the 28, and every file outside the 28 at c927efc3 is blob-identical to origin/main. The accepted diff lands byte-for-byte.
  • Public surface: nothing added, nothing removed — right, Clause-②: no holds. packages/app-shell/package.json exports is . and ./styles.css only; packages/app-shell/src/index.ts is not in the diff and has no export *. Of the modules the diff edits, the entry re-exports AppHeader and HomePage and imports global-notifications-renderer for its block registration. AppHeader's props interface is not touched (its activities prop stays and is lifted to a ready reading); HomePage() takes no props and its four hunks are internal (the cn import, the activity destructure, the conditional grid column, the HomeActivity prop); GlobalNotificationsRendererProps is not touched. The ACCEPT 6098089464 checked four internal symbols and AppHeader but did not name HomePage, which is re-exported (index.ts:292) and edited; closed here, still no.
  • Internal shape renames — right, not published. InboxPopoverProps.activities becomes a required activity: ActivityFeedReading; HomeInboxData.activities becomes activity; useSharedActivityFeed and useHumanActivityFeed return ActivityFeedReading; HomeActivity takes activity. None is reachable from the entry, and a git grep at the head finds no consumer of any of them outside packages/app-shell (comment mentions only). Every InboxPopover call site at the head passes activity (the ActivityFeed activities= hits are a different, untouched component). Type Check on the head is green.
  • Locale — right, no key added. The five keys the diff uses all exist in packages/i18n/src/locales/en.ts: common.loading (line 104), errors.unknown (2998), sidebar.activityFeed (3027), layout.activityFeed.empty (3194), layout.activityFeed.viewAll (3195). common.loading and errors.unknown were already read by other app-shell surfaces.
  • .changeset/12081-activity-bell-scope.md, sentence by sentence against the head — every sentence true.
    • "share one sys_activity read": the bell (AppHeader), the global:notifications block (global-notifications-renderer.tsx) and Home's card (useHomeInbox through useHumanActivityFeed) all read useSharedActivityFeed. True.
    • "checks the user's read permission on sys_activity from the permissions the console already loads (/auth/me/permissions)": readable = !perms.isLoaded || perms.can('sys_activity', 'read'); MePermissionsProvider's default endpoint is /api/v1/auth/me/permissions and apps/console/src/AppContent.tsx mounts it on that endpoint. True.
    • "A user without it gets no request": the feed key is null when readable is false, and useSharedFeed attaches nothing on a null key and returns the idle snapshot. Pinned by the hook suite's two refusal cases and the AppHeader member case. True.
    • "The bell offers no Activity tab, and Home shows no activity card": showActivity gates the tab trigger, the tab body and the "View all activity" link, and a remembered Activity pick is set aside; HomeActivity returns null and HomePage drops the 360px column. True.
    • "When permissions are not loaded (no permission provider is mounted, or the permissions request failed), the read still goes out as before": the no-provider answer is the frozen isLoaded: false whose can always returns true; in the provider isLoaded = !loading && !error && data !== null, so a failed fetch is not loaded. Both paths read. True.
    • "show 'An unexpected error occurred.' when the read fails, a refusal included, and 'Loading…' while it is in flight. 'No recent activity' appears only when the read has answered with no rows": a non-404 rejection goes to markFailed(); both surfaces render the notice for any status other than ready (error copy for error, loading copy otherwise) and gate the empty copy on ready. True. (An idle reading also shows the loading copy; under readable: true that is only the pre-presence-settle window or an adapterless host, the same dialect HomeActionCenter already uses. The sentence claims nothing false.)
    • "The server returns only the activity on records the reader can open": objectstack 86da1949 packages/plugins/plugin-audit/src/activity-read-visibility.ts narrows find, findOne, count and aggregate on sys_activity to readable parent record ids. True.
    • "(every non-admin on a deployment whose app sets do not name it)": objectstack's default-permission-sets.ts says sys_activity is deliberately NOT included in member_default. True.
    • "A sign-out keeps the console running ... The cache is now kept per signed-in user": the key is the adapter id plus user?.id; pinned by "re-keys on the signed-in user". True.
    • "Clause-②: no. Nothing is added to or removed from the package entry, and no locale key is added. AppHeader's props are unchanged: rows a host passes as activities are shown as an answer, as before": all four facts verified above; activities ? { value: activities, status: 'ready', readable: true } : apiActivity. True.
    • Frontmatter '@object-ui/app-shell': patch names the one package whose source the diff touches. Right.
  • Pending .changeset/11659-console-record-ux.md, the HomePage.tsx paragraph — stays true, no correction owed. At the head RecoveryPasswordReminder lives in packages/app-shell/src/console/home/RecoveryPasswordReminder.tsx, is imported by HomePage.tsx (line 32) and rendered there (lines 339, 425); os:recovery-pw-first-seen is the key in recoveryReminderGate.ts; index.ts does not name either module. This diff's four HomePage.tsx hunks touch none of that. The Changeset Claim Re-read run on this head is green.
  • Check-runs on c927efc3 are the gate verdicts — all green. 43 runs, all completed: 40 success, 3 skipped (the two coverage matrix entries and dependabot), 0 failure. Spec Main Shape Gate is success on this head, so the landing blocker the ACCEPT named (objectui#12093, folded into objectui#12100) is cleared. Changeset Bump Policy, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report, Changeset Claim Re-read, Type Check, Lint, Test plus 8 shards plus dist pins, Build & E2E, Governed Surface Queue Guard: success. Lint's 11 annotations are 10 no-explicit-any warnings on e2e/live/* files outside this diff plus the runner-image notice. The head's Console Performance Budget comment reads 3169.7 KB of 3204.6 KB, PASS.
  • Shape: draft PR on main, mergeable clean, assignee marchtian; Clause-②: no at the start of body line three. The ACCEPT 6098089464 was rendered on f1f0ba557b; this record is the same-form record on the landing head.

② Semver level

  • Changeset declares '@object-ui/app-shell': patch. A bug fix in a released package takes patch; the diff publishes no new or removed entry export, no locale key, no spec key, so the accept set neither widens nor narrows on the published face. Clause-②: no in the PR body and in the changeset body, no (widening)/(narrowing) arm, is consistent with patch. No major is declared (objectui version alignment; Changeset Bump Policy and Changeset Fixed Group Check green on the head). Level and declaration agree.

③ Boundary flags

From the dev's os-dev-report 6098074332 (nine deviations, no open questions, two out-of-scope findings):

  • Extension beyond the claimed surface (Home's useHomeInbox, HomeRail, HomePage; global-notifications-renderer; their tests): answered. Home was pre-named by the dispatch as an in-surface extension; the renderer is the second mount of the same InboxPopover and needs the now-required prop. Named in the PR, accepted in 6098089464; stands.
  • Bounded in-file fix, the per-user activity cache key: answered. Same defect class, the inbox key's existing shape in the same file, no other claim on the file, same gates; pinned and reverse-checked; named in the PR; accepted; stands.
  • Route refinement, usePermissions().can rather than the affordance map, and a returned 403 rendering as error rather than hidden: answered. The map's rows are write affordances and its census places read gates outside it; the route is the same read gate the route guard and navigation use; accepted; stands.
  • Mechanism assumption 1 partly falsified (the surfaces said "No recent activity" plus "View all activity", not nothing; a third consumer exists): answered. The diff covers all three consumers and pins the empty-copy case; no further action owed.
  • Test narrowing (121 of 1250 app-shell files locally; lint on 27 files): answered by the head's check-runs: Test, all 8 shards, dist pins, Lint and Type Check are green on c927efc3.
  • Eager-closure figure taken at d74a507 before the merge: answered by CI on this head, 3169.7 KB, PASS.
  • Commit trailers (the model-free pair AGENTS.md names, over the harness line): answered; the three dev commits carry it. The landing merge commit c927efc3 is a bot-made "Merge branch 'main'" with no trailer; noted, no bearing on the diff or the verdict.
  • Background waiters before the foreground-only instruction: process note, no bearing on the diff; answered.
  • Labels tests and package: app-shell written by the labeler, none by the seat: answered; no label write was owed.
  • open_questions: none.
  • Out of scope, notificationsStatus missing from seven HomePage.* mocks (carrier none): answered. The diff changes only their activities key, the suites are green on this head, no reach on this PR; not filed; stands.
  • Out of scope, whether MePermissionsProvider refetches on a same-tab user switch (carrier none, unmeasured): answered by reading rather than escalated. The provider's fetch effect depends on [endpoint, fetcher, maxRetries, retryBaseDelayMs, initialPermissions] with no user term, and AppContent.tsx mounts it without a user key, so a refetch on user switch happens only if sign-out leaves the /apps/:appName/* route element. Pre-existing and shell-wide (the same can() gates routes and navigation), not introduced by this diff; the feed's own cross-user residue is closed by the per-user key. No card owed by this PR; the seat's "noted, not filed" stands, with the dev's dedupe words on record if one is ever wanted.

Implemented-by: claude/issue-12081-activity-bell-scope
Reviewed-by: session_01B1gHb9baeX7oioD5sHVm7z

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 10, 2026 19:56
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 10, 2026 19:56
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 10, 2026
Merged via the queue into main with commit 029bdaf Oct 10, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12081-activity-bell-scope branch October 10, 2026 20:16
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.

2 participants