Skip to content

perf(app-shell): the in-app sidebar, the palette and recent rows name their items by name instead of loading whole metadata lists - #29413

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/project-thread-k2hvaf
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/project-thread-k2hvaf

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #29235

Clause-②: no
Metadata-protocol: none

Claim: 6575822841 · amendment: 6576916626

维护者速读

  • 改了什么:应用内侧边栏与命令面板给「没写 label 的导航项」取名时,改为按名字读那个对象/仪表盘,不再下载整张对象注册表;首页等处「最近访问」里的仪表盘、页面、报表行也改为按名字读单条,不再下载整张列表。
  • 为什么改:一个导航项不写 label(规范允许,AI 生成的应用常见)时,进应用的每个页面都为取两个名字下载全部对象定义(app-crm 上 551 KB);最近访问里有一条仪表盘或页面,首页就下载整张仪表盘/页面列表。
  • 风险与回滚:读法从「一次整表」变成「按名读单条」;应用内按名读由应用包(bundle)直接回答,不发请求。整表已被别处加载时一律直接用整表。显示的名字逐字不变(中英文均实测)。回滚即 revert 本 PR。
  • 先看:ui/packages/app-shell/src/hooks/useObjectPresence.ts(新的 useReadItemsByName)、ui/packages/app-shell/src/hooks/useNavTargetLabel.ts(仪表盘按名读)、ui/packages/app-shell/src/chrome/CommandPalette.tsx(面板取名改 byName)。
  • 两分钟验证:打开一个导航项没写 label 的应用里的页面,开发者工具 Network 过滤 meta/object:不再有不带名字的 GET /api/v1/meta/object;侧边栏显示的名字与改前一致。
  • 需要你做的:无,席位落地。

What changes

Two label readers still downloaded a whole metadata list to name one row. Measured with CDP initiator stacks on a production Console (app-crm):

Reader Before Now
UnifiedSidebar (the in-app sidebar): an entry with no label of its own useNavTargetLabel() read metadata.objects: the whole object registry and the /meta/view list useNavTargetLabel({ byName: true }): the entry's object by name (the app bundle answers it, no request); a dashboard entry the bundle does not hold, by name
CommandPalette (mounted closed in every app; it names each navigation entry on every render to build its lists) useNavTargetLabel(), the same whole-list read, hidden behind the sidebar's by the provider cache useNavTargetLabel(RECENT_ROW_LABELS), the { byName: true } constant the palette's recent rows already use
useRecentItemLabel: a recent dashboard row metadata.dashboards (through useNavTargetLabel's bundledFirst fallback) the dashboard by name
useRecentItemLabel: a recent page row / report row metadata.pages (behind bundledFirst) / metadata.reports the page / report by name

useReadItemsByName (in hooks/useObjectPresence.ts, beside readObjectsByName) is the one helper for the non-object types. It asks through the provider's existing by-name read (getItem), so no provider or package export is added (@object-ui/app-shell is private). While nothing has loaded the type's list, each name is asked once and the reader re-renders as it lands. Once the list is loaded or loading, it reads the list and asks nothing. A hand-rolled context with no status or no getItem reads its list, as before. A name that answered an item is asked again after the provider's value changes (the provider's by-name cache answers it without a request, unless an invalidation dropped it, so a rename shows). A name that answered nothing is not asked again while the reader is mounted.

useNavTargetLabel({ byName: true }) now also reads a dashboard target the bundle does not hold by name. The other callers (Studio, search page, inspectors, nav:menu) are unchanged. views/nav-menu-renderer.tsx is the nav:menu page block, not the in-app sidebar, so it is untouched.

Tests

  • layout/__tests__/UnifiedSidebar.labelReadsByName-29235.test.tsx: the real sidebar over the real MetadataProvider and a recording adapter. Unlabelled object and dashboard entries read object/NAME and dashboard/NAME, never the object, view or dashboard list. Labels pinned in en and zh-CN (inline locale maps).
  • chrome/CommandPalette.navLabelReadsByName-29235.test.tsx: the palette closed inside an app reads each entry's object by name and no list; opened, the entries show their objects' labels.
  • hooks/__tests__/useRecentItemLabel.readsByName-29235.test.tsx: dashboard, page and report rows ask their own item, never the list. Labels pinned in en, and in zh-CN through an inline locale map and a translation-bundle key. The machine name shows until the item lands, and for an item the server does not have (asked once). CONTROL: with the lists loaded, the rows read them and ask no item.

Acceptance notes

  • Claim amendment (6576916626): chrome/CommandPalette.tsx, at the :239 hook call only. The production re-measure showed it was the reader left in-app once the sidebar asked by name.
  • Not changed, found while measuring:
  • openPathObjectReaders-12811's row for useNavTargetLabel.ts still reads "off" with a reason naming Home; the census stays green, and its prose is not updated here (outside the claim's surface).
  • A by-name read answers the item the server resolves for that name. The list read took the first item with that name. They differ only if two packages ship a same-named dashboard, page or report.

Verification

Production Console, app-crm (objectstack dev --fresh), before (base b1779200) and after (head db585d60; later commits only move the by-name reader's asked set into a ref and merge main)

Setup: an app ops_app saved through PUT /api/v1/meta/app/ops_app with three unlabelled entries (crm_account, crm_contact, pipeline_dashboard). Recent rows come from visiting a dashboard and a page. Playwright logs in, reloads each page with the cache disabled, and logs every /api/ request with its CDP initiator stack.

In-app page route /apps/ops_app/page/crm_welcome, en:

  • Before: 29 requests, 889,029 B, including GET /api/v1/meta/object (551,834 B) and /meta/view (7,014 B). The initiator is get objects from the useNavTargetLabel chunk, called from ui-layout's NavigationRenderer.
  • After: 28 requests, 337,195 B. No GET /api/v1/meta/object without a name. crm_account and crm_contact are answered by the app bundle, with no request.
  • With only the sidebar changed, the object list was still read. Its initiator was CommandPalette's navLabel, which led to the claim amendment.

Home /home, with a recent dashboard row and a recent page row, en:

  • Before: 28 requests, 302,598 B, including GET /api/v1/meta/dashboard (5,596 B) and /meta/page (15,463 B).
  • After: 28 requests, 284,915 B, including GET /api/v1/meta/dashboard/pipeline_dashboard (3,054 B) and /meta/page/crm_welcome (1,044 B).

The text shown is identical before and after:

  • Sidebar, en: Account, Contact, Pipeline Dashboard.
  • Sidebar, zh-CN: 客户, 联系人, 销售漏斗.
  • Home recent rows, en: CRM Welcome, Pipeline Dashboard.
  • Home recent rows, zh-CN: 欢迎页, 销售漏斗仪表盘.
Ablations (each through scripts/ablation-replace.mjs, restored with git diff HEAD empty)
  • A1: the sidebar's { byName: true } removed. Both sidebar cases red.
  • A2: the dashboard target reads metadata.dashboards again. Both sidebar cases and three recents cases red.
  • A3: a page row reads metadata.pages. Three recents cases red.
  • A4: a report row reads metadata.reports. Three recents cases red.
  • A5: an absent name asked again after each provider change. The "asked once" case red (re-run on the final head).
  • A6: the palette's navLabel resolver without byName. Both palette cases red.
Local runs
  • 220 test files naming the touched modules and their surfaces (useNavTargetLabel, useRecentItemLabel, UnifiedSidebar, useObjectPresence, readObjectsByName, HomePage, RecentApps, CommandPalette, BuilderLanding, ConsoleLayout, HomeRail), at a3eaa1ec under os-verify-lock, --maxWorkers=2, four batches: 220 files and 1,797 tests passed. After merging main (fix(app-shell): a package's non-first app opens itself from the avatar menu, palette, switcher, Home tiles and report index #29327 edits CommandPalette.tsx), the 77 CommandPalette and new-test files passed again at fa2a8da5 (642 tests). CI's Test UI runs the rest of app-shell.
  • turbo run type-check --filter=@object-ui/app-shell: 65/65 tasks.
  • eslint --no-inline-config on the changed files: 0 errors. The three hooks have no warnings.
  • Derived gates (dispatch-gates --commands): 34 commands, all exit 0 at fa2a8da5, reconciled with --ran (34 derived, 34 run, 0 NOT-MEASURED). The head 3dcde8cd only merges main again (no touched file moved); on it the 35 sidebar, palette and new-test files (165 tests), app-shell type-check (65/65), check-sdui-manifest and check-changeset-no-major pass.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (not touched)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QsR2jUpJJCdztFcegijAq7

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 2, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

席位核对 · ACCEPT · 2026-11-02T14:40Z

Head-sha: 3dcde8cd5f14f16fec66aa6bb01c2e93133e2c05

  • Shape: draft → main, Fixes #29235 in the body only. 9 files, +564 / −13 (577 lines ≤ 3000). No governed path: check-governed-merges --pr 29413 reports NOT governed (0 of 9 paths). Clause-②: no and Metadata-protocol: none are line-initial in the body and the changeset, as claimed (6575822841, amended 6576916626). The changeset is patch for @object-ui/app-shell.
  • Contract review: none owed (Clause-②: no; useReadItemsByName is a new export of the private app-shell, no prop, key or string change).
  • File surface: inside the claim and its amendment: UnifiedSidebar.tsx and CommandPalette.tsx (one hook call each), useNavTargetLabel.ts, useRecentItemLabel.ts, useObjectPresence.ts (the new helper beside readObjectsByName), three new tests, the changeset.
  • Read in the diff: the sidebar resolves unlabelled entries with useNavTargetLabel({ byName: true }) and the palette with RECENT_ROW_LABELS, so an object entry reads its object by name (the bundle answers it) and a dashboard the bundle does not hold is read by name, never the /meta/dashboard list. Recent page and report rows read their item through useReadItemsByName, which asks each name once via getItem only while the type's status is idle, and falls back to list() once the list is loaded or loading, or when the provider has no status or by-name read. A name that answered nothing is not asked again while mounted; a new provider value resets the asked set so a renamed item re-reads (from the by-name cache). Labels render through the same metadataText / *Label paths as before.
  • Tests (dev): 3 new files, 8 cases over the real MetadataProvider and a recording adapter (en and zh-CN, closed and opened palette, machine name until loaded, absent asked once, CONTROL with lists loaded). Ablations A1–A6 each red, each restored with git diff HEAD empty. app-shell type-check 65 / 65 and a 35-file sweep green at this head; derived gates 34 of 34 at fa2a8da, reconciled with --ran; this head only re-merges main.
  • Measured (dev): CDP initiator stacks on a production Console (app-crm): an in-app page route 889,029 B → 337,195 B with no unscoped /meta/object; Home with recents 302,598 B → 284,915 B. Labels byte-identical in en and zh-CN.
  • Gates (check-runs on this head): 44 success, 11 skipped, 0 failed or pending. mergeable_state is clean.
  • Follow-up filed: Console: a console-created (package-less) app's routes and an out-of-bundle page route still read whole /meta/object, /meta/view and /meta/page lists #29416 (a package-less app's routes and an out-of-bundle page route still read whole /meta/object, /meta/view, /meta/page lists; dev findings 1–3, serial after this PR). Not filed: the stale prose of census row openPathObjectReaders-12811 (stays green, cosmetic; it can ride the next PR that touches the census).
  • Landing: ordinary queue landing, pr_ready then automerge_enable. Fixes #29235 closes the card on merge.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 2, 2026 14:41
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 2, 2026 14:41
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 2, 2026
Merged via the queue into main with commit a644163 Nov 2, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-k2hvaf branch November 2, 2026 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Console: unlabeled sidebar object entries and Home recents still read whole metadata lists

1 participant