Skip to content

feat(app-shell): ⌘K recent rows show their object's type and icon (objectui#12852) - #12877

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-12852-recent-item-type
Oct 17, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-12852-recent-item-type

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #12852
Clause-②: no

What changed

In the command palette (⌘K), every row of the recently viewed group drew the same search icon and its title alone, so two recent records with the same title rendered identically. Each row now shows:

  • on the right, as muted text, the record's object label in the viewer's language;
  • on the left, the object's icon, in place of the shared search icon.

One file changes (packages/app-shell/src/chrome/CommandPalette.tsx), plus a new test beside it and an @object-ui/app-shell patch changeset. RecentItemsProvider's stored entry shape, recentItemTypeLabel.ts, useRecentItemLabel.ts, the locale packs and packages/components/src/ui are untouched. The group's heading, order, count (5), onSelect and every existing data-testid stay as they were; the type text carries a new data-testid of palette-recent-type.

How each row's type and icon are derived

  • Object: read only from the stored href, through the existing recentItemObject (its /apps/APP/OBJECT/record/ID parse). The helper is imported, not copied.
  • Type text: the object's label from useNavTargetLabel (the object's current label in the viewer's language), else the object's machine name. This is the rule Home's Recently accessed trail uses.
  • Icon: the object's declared icon through getIcon, as the palette's record-search groups use it, else FileText, the record glyph of Home's RECENT_ICON.
  • useMetadata().objects is read inside the row function, never at render, so a closed palette still reads no metadata (CommandPalette.studioScope-11863, 'reads nothing until the palette opens').

Stored shapes that cannot carry a type

A record entry whose href is not /apps/APP/OBJECT/record/ID names no object, so its row shows no type and the neutral FileText icon. The type is never guessed from the label or the id. objectui writes record entries in one place, RecordDetailView, always in that shape. So only an entry written by another producer into the synced preference can lack a type, for example a non-/apps route.

Group membership, as measured

The group still lists recentItems.filter(it => it.type === 'record').slice(0, 5). Only records appear in it, so a 页面 (page) entry cannot appear, and the card's 页面 example does not apply to this group. The filter is unchanged.

Live before and after

Setup: hotcrm objectstack dev --fresh backend, consoles from main 5ff64ef and from this branch, zh-CN, light theme. A crm_opportunity and a crm_task were both renamed 'Objectstack' (locally only), and a contact was visited too. Then ⌘K was opened with an empty input.

  • main: the two 'Objectstack' rows are identical; every row has the search icon and no type.
  • Branch: 'Objectstack' 商机 with a dollar-sign icon, 'Objectstack' 任务 with a square-check icon, 'Sarah Johnson' 联系人 with a user icon, plus 线索 / 产品 rows with their own icons.
  • No /api/v1/meta request was made after opening the palette, on either side.

Screenshots (evidence branch claude/issue-12852-evidence, not merged):

Tests (on 210ce40)

CommandPalette.recentItemType-12852.test.tsx checks:

  • two records with the same title and different objects show different type text and different icons;
  • the type is the object's label in zh-CN (组织 / 环境) and in en;
  • CONTROL: an entry whose href names no object shows no type and the neutral icon;
  • CONTROL: an object the metadata does not hold is typed by its machine name;
  • CONTROL: the group lists the first five records in order under its heading, and a row opens its href and closes the palette.

Each new pin was ablated once (icon, type text, label vs machine name, the neutral control, the count). Each run went red, was restored and showed an empty git diff HEAD.

Gates on 210ce40:

  • Every CommandPalette, useRecentItemLabel, Home recent and RecordDetailView recent-visit test: 15 files, 76 tests passed.
  • @object-ui/app-shell type-check and build: exit 0.
  • eslint on the touched files: 0 errors.
  • check:i18n-keys, check:i18n-drift, check-changeset-presence and check:new-line-citations: exit 0.

Deviations

None in the code. The neutral icon is Home's record glyph (FileText), not the old search icon, so the palette and Home draw the same thing.

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


Generated by Claude Code

…jectui#12852)

Each row of the command palette's recent group drew the same search glyph
and its title alone, so two recent records that share a title (an
organisation and its environment) rendered identically. A row now shows its
object's label on the right and its object's icon on the left, as Home's
"Recently accessed" row does. The object is read off the stored address by
the existing recentItemObject; an address that names no object gives a row
with no type and the neutral record glyph.

Claude-Session: https://claude.ai/code/session_01SRHG3vksxM8h9PHq9mPEUW
Co-authored-by: Claude <noreply@anthropic.com>
…i#12852)

The recent rows' object lookup read `useMetadata().objects` at render,
which starts the object list's load while the palette is still closed
(CommandPalette.studioScope-11863 "reads nothing until the palette opens").
Read it inside the row function, which runs only for drawn rows.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 76 chunks) 3140.5 KB 3744.1 KB
Main entry chunk (gzip) 80.2 KB 350 KB
Entry file index-DjFsqpov.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) 21.42KB 7.85KB
app-shell (runtime-config.js) 23.60KB 8.19KB
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) 45.59KB 12.03KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 42.84KB 11.66KB
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.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
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) 606.28KB 146.84KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.83KB 69.44KB
fields (index.js) 291.19KB 74.38KB
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) 41.33KB 11.96KB
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.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 210.60KB 49.94KB
plugin-dashboard (index.js) 146.23KB 39.59KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 264.38KB 69.99KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.22KB 46.59KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 272.78KB 75.66KB
plugin-kanban (index.js) 62.30KB 19.27KB
plugin-list (index.js) 139.05KB 35.78KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.27KB 5.40KB
plugin-view (index.js) 94.68KB 24.36KB
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.23KB 39.46KB
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-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.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

席位核对 — ACCEPT · objectui#12852 · head 210ce40 · 2026-10-17T10:59Z

Seat domain:ui#4, session session_01SJn1GEkrbbBQkhGC26wEDw. Checked against GitHub and the diff, not against the report (relayed on the card, 6233095508). The seat opened this PR from the dev's drafts (degraded channel). The body and the title are the dev's drafts verbatim.

Checklist

  • Form:

    • Draft → main, assignee os-sales.
    • The first line is Fixes #12852, and the second is Clause-②: no, as the claim declares (6232413570).
    • Three commits carry only the model-free trailer pair: two changes and a plain merge of origin/main.
    • git merge-tree against current main is clean. 3 files, +239 / −11, all inside the claim's surface. No PNG. Not a governed surface.
  • The rulings, as bound:

    • Each recent row draws its object's icon (getIcon, the palette's record-search resolution) and, on the right, its object's label (useNavTargetLabel, else the machine name) as muted text under data-testid="palette-recent-type".
    • The object is derived, never invented. It comes from recentItemObject(item), imported from console/home/recentItemTypeLabel.ts, not copied. An entry stored under any address but /apps/APP/OBJECT/record/ID shows no type and the neutral glyph.
    • RecentItemsProvider's stored shape, the locale packs and packages/components/src/ui/** are untouched.
    • The group's heading, order, count (5) and onSelect are unchanged.
    • useMetadata().objects is read only inside the row function. A closed palette still reads nothing: the dev's own gate caught that regression against CommandPalette.studioScope-11863 and fixed it in 9777dcb.
  • Live (hotcrm objectstack dev --fresh; a crm_opportunity and a crm_task both renamed "Objectstack", locally only; zh-CN, light):

    row main 5ff64ef this branch
    Objectstack search icon, no type dollar icon, 商机
    Objectstack search icon, no type check-square icon, 任务
    Sarah Johnson search icon, no type user icon, 联系人

    No /api/v1/meta request after opening the palette, on either side.

  • Screenshots, read by the seat (never-merged claude/issue-12852-evidence at 7122f5c):

    • before: five identical search-glyph rows, two of them "Objectstack";
    • after: 商机 / 任务 / 联系人 / 线索 / 产品 on the right, each with its own icon.

    Sent to the maintainer at 2026-10-17T10:59Z.

  • Tests:

    • CommandPalette.recentItemType-12852.test.tsx, 5 cases:
      • two same-titled records of different objects differ in type and icon (the card's own acceptance);
      • zh-CN types;
      • CONTROL: an underivable href shows no type;
      • CONTROL: an unknown object gets its machine name;
      • CONTROL: membership, order, count and onSelect.
    • Five ablations, each red and each restored.
    • 15 files and 76 tests across the palette, Home recent and record-visit pins passed. app-shell type-check and build, eslint, check:i18n-keys, check:i18n-drift, changeset presence and check:new-line-citations all exit 0.
  • Changeset: @object-ui/app-shell patch; user-facing prose.

  • CI: running at this record. Landing waits for green.

Rulings

  1. The neutral glyph is FileText, Home's record glyph: A, as built. The dispatch said to match Home's RecentApps. A search glyph on a recent row says nothing about what the row is.
  2. The non-record kind label is not written: A. The group lists records only (type === 'record', measured), so the card's 页面 example cannot appear there, and a branch nothing reaches is not built. If the group is ever widened, that card adds recentItemTypeLabel beside Home's identical use.
  3. The cloud console's two "Objectstack" rows (an organisation and its environment) get a type exactly when the cloud console stores them as /apps/APP/OBJECT/record/ID, the one shape objectui's RecordDetailView writes. Another shape shows no type, by ruling 2. The cloud repo is outside this session. The seat notes this on the card for repo:cloud to read, and no follow-up is filed until a measurement shows another shape.
  4. The row reads useMetadata().objects, not the palette's app-scoped prop: A. Recent items are cross-app, so the app scope would give other apps' records a machine-name type. The read happens only while the palette is open with recent rows, never at console open (pinned by studioScope-11863), so objectui#12811's narrowed open stands.

Implemented-by: cloud session session_01SRHG3vksxM8h9PHq9mPEUW, branch claude/issue-12852-recent-item-type


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Landing · head 210ce40 · 2026-10-17T11:10Z

  • Head since the ACCEPT: unchanged at 210ce40. The ACCEPT (6233106945) was checked against this exact commit, so its rulings stand.
  • Checks on 210ce40: 36 runs: 33 green, 3 expected skips, none red. The green runs include Build & E2E, Repo Gates, all 16 test shards, Type Check, Lint, Bundle Analysis and Governed Surface Queue Guard.
  • Merge state: clean against main 1fe0a8d. GitHub reports mergeable_state: clean, and git merge-tree agrees.
  • Commit messages: all three commits (two changes and a plain merge of origin/main) carry only the model-free trailer pair.
  • Action: mark the PR ready for review and enable auto-merge through the merge queue. Fixes #12852 closes the card on merge.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 17, 2026 11:11
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 17, 2026 11:11
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 17, 2026
Merged via the queue into main with commit 0e98991 Oct 17, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12852-recent-item-type branch October 17, 2026 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants