Skip to content

fix(app-shell): the bell's Activity rows name their object by its label, not its API name (objectui#12212) - #12237

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-12212-activity-object-label
Oct 12, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-12212-activity-object-label

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #12212

Clause-②: no

What changed

The bell's Activity tab (InboxPopover) printed each row's object by its API name (… · sys_user_preference, … · crm_lead) beside a summary that already names the object by its label. The row's second line now shows the object's label in the reader's language, and the API name only when nothing names the object. sys_activity still stores object_name, and the console resolves it, as triage 6121938464 directs.

  • packages/app-shell/src/layout/InboxPopover.tsx: a module-private ActivityObjectLabel component renders the object half of the row's second line. It finds the definition in useMetadata().objects (findByName), reads its label in the active language (metadataText, which also reads an inline locale map), and passes that through useObjectLabel().objectLabel, so a bundle entry objects.NAME.label wins, as on the console's other object surfaces. An empty answer falls back to the API name. These are the three readers useNavTargetLabel composes for an object, imported from that module, not copied.
  • New pin: packages/app-shell/src/layout/__tests__/InboxPopover.activityObjectLabel-12212.test.tsx.
  • .changeset/12212-activity-object-label.md (@object-ui/app-shell patch).

No new i18n key, no export, prop or type change, nothing on the server. activityItemType.ts is read, not changed: the lookup needs the reader's language and the cached definitions, both React context, so it sits at the render, not in the row mapping that Home's card shares.

Where the definitions come from (H1)

useMetadata().objects: MetadataProvider's per-type cache of the object list, the same list useNavTargetLabel, useRecentItemLabel and the app sidebar read. It is one list request for the whole type, shared with every other reader, never one per row (no N requests for N rows). The object type is not eager (EAGER_TYPES is app and view), and reading objects is what loads it. So the lookup is its own component: the row elements are built on every render of the bell, open or closed, and only a mounted row reads the list. The pin's last case holds both halves: a closed bell reads the list zero times, and the Activity tab with rows reads it.

Objects the console has not loaded (H2)

Read at objectstack origin/main 794177d9b, in packages/rest/src/meta-item-read-gate.ts: the object list answer (createMetaListAnswer) prunes each object's listViews per caller (createMetaListReadGate, filterObjectListViewsForUser) and masks fields (maskMetaObjectList). It drops no object. So a system object such as sys_user_preference is in the list the console caches, with its label, which the list's translation step serves in the request locale. Two cases can still miss: the list has not loaded yet (the row shows the API name until it lands, then re-renders with the label), or the object has since been removed. Both fall back to the API name, per the ruling. This is a reading of the source, not a live call.

Translation (H3) and the summary (H4)

objectLabel tries objects.NAME.label under every app namespace the loaded bundle carries (and the un-prefixed base name), falling back to the definition's label. The pin's zh-CN case reads 线索 from a bundle entry for crm_lead. The summary (description) is server text and is rendered unchanged; the pin asserts it verbatim in en and zh-CN.

Tests

Every run below is on 9662600, this branch's head when the PR was opened.

  • New pin, 6 cases: en business object shows Lead; en system object shows User Preference (the staging row); zh-CN shows 线索 from the bundle; an unknown object shows crm_gone; the summaries are verbatim in both languages; a closed bell reads no object list, and the Activity rows read it. Result: 6 passed.
  • Ablation 1, the row put back on the API name (through objectstack's scripts/ablation-replace.mjs; mutation and restore proven by blob hash, git diff HEAD empty after): 4 failed | 2 passed. The three label cases and the read case are red; the fallback and the summary are green, as the pin's header predicted before the run.
  • Ablation 2, the component boundary removed (ActivityObjectLabel called as a function inside the row map): 1 failed | 5 passed, failing with read while the bell is closed: expected 3 to be +0. The laziness claim in the code comment is measured.
  • Targeted vitest, every test naming InboxPopover, activityItemType or inbox-activity: 28 files, 290 tests passed. Every test naming AppHeader, ConsoleLayout or InboxPopover (the bell's hosts, several with a mocked useObjectLabel): 82 files, 719 tests passed.
  • turbo run build --filter='@object-ui/app-shell^...' --concurrency=2: 28 of 28 tasks. pnpm --filter @object-ui/app-shell type-check (both tsc legs): exit 0, and tsc -p tsconfig.test.json --listFiles includes the new pin.
  • Gates, each exit 0: check:vi-mock-specifiers, check:test-path-roots, check-changeset-presence.mjs, check:changeset-claims, check:pending-changeset-literals, check:control-bytes, check:new-line-citations (0 new), check:vi-mock-inherit, check:vi-mock-override-shape, check:phantom-deps, check:esm-specifiers, check:self-import, check:i18n-keys, check-changeset-no-major.mjs, check-changeset-fixed.mjs, check-changeset-overwrite.mjs.
  • ESLint, narrowed to the 2 touched .tsx files: the config's TS block is files: ['**/*.{ts,tsx}'] and neither path is ignored; --format json reports 2 files, 0 errors, 0 warnings. Invariance: the config sets no parserOptions.project or projectService (no type-aware linting), and no rule under eslint-rules/ reads the filesystem, so this diff cannot move a verdict on an untouched file. The full pnpm lint is CI's.

Acceptance notes

  • The same defect on a second surface, outside this claim's file surface and not touched here: Home's activity card, HomeActivity in packages/app-shell/src/console/home/HomeRail.tsx, renders the same shared feed's rows with the same timeAgo(...) · a.objectName line. A throwaway probe (deleted, never committed) rendering HomeActivity with a crm_lead row and a crm_lead definition labelled Lead read "3 hours ago · crm_lead". Reported to the seat. Moving ActivityObjectLabel to a shared module would serve both surfaces.

Generated by Claude Code

…el, not its API name (objectui#12212)

The Activity tab's second line printed `sys_activity.object_name` as stored
(`… · sys_user_preference`, `… · crm_lead`) beside a summary that already
uses the object's label. It now resolves the label in the reader's language
from the console's cached object list (`useMetadata().objects`) through
`useObjectLabel`'s bundle lookup, the way `useNavTargetLabel` reads an
object's label, and falls back to the API name only when nothing names the
object. The lookup lives in its own component so the object list is read
only while Activity rows are on screen.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 70 chunks) 3128.1 KB 3204.6 KB
Main entry chunk (gzip) 73.1 KB 350 KB
Entry file index-BkfsaBa4.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) 20.69KB 7.62KB
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) 588.51KB 141.71KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 243.13KB 67.92KB
fields (index.js) 279.96KB 71.37KB
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) 15.35KB 5.51KB
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) 54.31KB 15.86KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.72KB 48.07KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.77KB 50.02KB
plugin-detail (index.js) 250.93KB 66.25KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 178.37KB 46.27KB
plugin-gantt (index.js) 178.63KB 45.19KB
plugin-grid (index.js) 255.09KB 70.91KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 123.03KB 31.28KB
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.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 92.11KB 23.27KB
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.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants