Skip to content

fix(app-shell): sys-objects 的三个生产端直发规范 metadata 路由,不再绕 system/metadata 别名 (#3739) - #3748

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3739-sys-objects-canonical-route
Aug 8, 2026
Merged

fix(app-shell): sys-objects 的三个生产端直发规范 metadata 路由,不再绕 system/metadata 别名 (#3739)#3748
yinlianghui merged 1 commit into
mainfrom
claude/issue-3739-sys-objects-canonical-route

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes#3739

前提核验(issue body 只是线索)

origin/main @ 8ad6070fb 上逐条复核,三处调用点全部仍在发 legacy 别名,premise 成立:

packages/app-shell/src/layout/AppSidebar.tsx:329 url: '/apps/setup/system/metadata/object'
packages/app-shell/src/layout/UnifiedSidebar.tsx:335 url: '/apps/setup/system/metadata/object'
packages/app-shell/src/console/home/QuickActions.tsx:39 href: '/apps/setup/system/metadata/object'

规范目标也核到了:packages/app-shell/src/console/AppContent.tsx:641metadata/:typeMetadataResourceListPage,两个分支都声明。别名一侧由 apps/console/src/AppContent.tsx:189-191system/metadata/:metadataTypeMetadataRedirect 服务,它就是一个 Navigate,目标恰好是本 PR 写进导航项的那个 URL。另外确认在途无人改动这三个文件(PR #3728 今天只碰了 apps/console 的 AppContent)。

改了什么

三处生产端一律改指 /apps/setup/metadata/object,并按 #3660 在同一数组下一行留下的注释形状、逐文件沿用该文件既有注释习惯,各写一段判词。

落地页面逐字节不变 —— 新 URL 就是别名跳转本来算出来的那个(object 百分号编码即自身,三处都不带 query/hash);消失的只有中间那一跳和随之而来的 re-render。别名路由一行未动,书签与外链继续能到,只是我们自己的导航不再往里喂流量。

顺手修掉一句被本次改动证伪的旧注释:两个侧边栏 #3590 段末的「Every sibling below already spells /apps/setup/system/...」。它在 #3660 之后对 sys-datasources 已经不成立,本 PR 让它对 sys-objects 也不成立 —— 留着会诱导下一个 agent 为了「前缀一致」把 URL 改回去,所以改写为「这两个 metadata-admin 条目名的是引擎的规范路由;此处的不变量不是前缀一致,而是名字指向真正渲染的那条路由」。

测试

按这一族已有的模式加/改,分两层:

  • 新增packages/app-shell/src/layout/__tests__/systemNavObjectsHop.test.tsx —— systemNavDatasourcesHop.test.tsx 的孪生件,量的是跳数而非终点:href 从两个侧边栏的真实渲染里读出来,丢进同时认识规范路由与别名的 router,ChainRecorder 记录每一个落点,一条 = 直达。两个文件分开放,因为要镜像的别名路由不同(本文件镜像 apps/consoleMetadataRedirect,与 AppContent.noAppComponentRoutes.test.tsx 同源同做法)。第三个 case 是 CONTROL:别名依然能解析,且解析要付那一跳 —— 别名可用是契约的一部分,不是附带损害;它同时证明前两条断言不空转。
  • 改写systemNavSettingsTarget.test.tsx:ADMINISTRATION_ENTRIESObject Manager 换成规范 URL,并给 AppSidebar 那一半补上同形状断言(两个侧边栏各持一份字面量,只钉一份就能单边漂回去 —— 这正是 导航仍有 4 处生产端在生成已废弃的 component/metadata 别名(SystemHub 两张卡片 + 两个 sidebar),各多绕一跳 #3660Datasources 补断言的理由)。
  • 改写QuickActions.settingsTarget.test.tsx 第三个 case。它原本叫「REGRESSION: the sibling card that was already hub-scoped is unchanged」,钉的正是我要改的那个字面量 —— 这个前提就是本 issue 证伪的东西,所以整条重写而非并存(沿用该族「只钉修好的形状,不同时钉 bug 和 fix」的纪律)。

三处都按 #3660/#3609 的做法替换旧期望,不与旧形状并存。

反向验证(先预测,再运行)

预测:只把三处生产端 URL 改回别名、测试不动,应有 9 条中 5 条转红 —— hop 文件两条 sidebar case、settings-target 文件 AppSidebar 与九条目循环、QuickActions 的 manage-objects;CONTROL、dormancy、system-settings、non-admin 四条保持绿。并预测 hop 文件会死在 expect(href).toBe(CANONICAL) 这个前置断言上,因此那条「两跳链」并不会出现在 diff 里。

实测与预测逐条吻合:

 ❯ systemNavObjectsHop.test.tsx (3 tests | 2 failed)
× AppSidebar: the zero-app fallback cluster arrives with NO redirect
× UnifiedSidebar: the /home Administration cluster arrives with NO redirect
❯ systemNavSettingsTarget.test.tsx (3 tests | 2 failed)
❯ QuickActions.settingsTarget.test.tsx (3 tests | 1 failed)
AssertionError: expected '/apps/setup/system/metadata/object' to be '/apps/setup/metadata/object'
❯ systemNavObjectsHop.test.tsx:241:18
Test Files 3 failed (3)
Tests 5 failed | 4 passed (9)

随后 git checkout -- 复原,工作树干净。

消费半径全量(不止改动包)

一条被改的 URL 在哪里被消费就在哪里可能碎,所以按调用面而非按包跑:app-shell 的 layout/__tests__console/__tests__console/home/__tests__,加上 apps/console 的别名钉(AppContent.legacyRedirects.test.tsx)与 #3660 的 System Hub 卡片钉。

 Test Files 26 passed (26)
Tests 160 passed (160)

apps/console 的 legacyRedirects 测试一行未动,并且照旧全绿 —— 本 PR 只重指内部生产端,别名本身必须继续为外部到访服务。

其余门禁:@object-ui/app-shelltype-check 干净;eslint 对六个改动文件 0 error(新测试文件单跑 0 findings);check-control-bytes OK,并对改动文件另跑了一次手工控制字节扫描(gate 的盲区正是这类字节藏身处)。

Changeset

.changeset/nav-sys-objects-canonical-route-3739.md,@object-ui/app-shell: patch。里面如实写了:三个生产端中两个侧边栏是活的,QuickActions 今天没有任何 JSX 调用点(其自身测试里有一条 dormancy 前置断言在钉这件事),所以那一处是「哪天有人把它挂回 /home,死链不会跟着回来」的守卫,而非用户可见变化。

范围

只碰 issue 点名的三个文件与它们的测试。发现了一处越界项已另行开 issue 记录(见下方评论),没有在本 PR 里动:AppContent.noAppComponentRoutes.test.tsx / AppContent.pseudoRouteSegments.test.tsx 的头注释把这两个条目当作现在还在发别名的生产端来枚举 —— #3660 之后 sys-datasources 那一行已经陈旧,本 PR 让 sys-objects 那一行也陈旧。两处测试断言本身都是从 URL 直接进入、量别名路由的行为,别名保留,因此仍然正确、仍然全绿;陈旧的只有叙述。参照 #3660 把同类注释改写单独立为 #3666 的先例,归为独立 issue。


🤖 Generated with Claude Code

https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt


Generated by Claude Code

…l metadata route (#3739)
`AppSidebar.systemFallbackNavigation`, `UnifiedSidebar.homeNavigation` and
`console/home/QuickActions` all aimed at `/apps/setup/system/metadata/object`.
That spelling is not a page: `apps/console`'s host fragment declares
`system/metadata/:metadataType` with `MetadataRedirect` as its element, a bare
Navigate onto `/apps/setup/metadata/object` — the engine's real route. Every
click paid a redundant hop plus a re-render.
Same defect #3660 fixed for `sys-datasources`, declared one line below
`sys-objects` in both sidebar arrays; it was missed there because the two
entries reach their aliases through different route tables (app-shell's
`component/metadata/resource` vs the host's `system/metadata/:type` rewrite).
Endpoints are byte-identical to what the alias hop computed; only the
intermediate hop is gone. The alias routes are untouched and stay reachable for
bookmarks and external links.
Pins: `systemNavObjectsHop.test.tsx` measures the redirect chain for both
sidebars (one entry = direct arrival) with a CONTROL case proving the alias
still forwards; `systemNavSettingsTarget.test.tsx` and
`QuickActions.settingsTarget.test.tsx` pin the URL each of the three producers
emits. #3611's "the sibling card is unchanged" anchor in the QuickActions suite
is rewritten rather than extended — that premise is what this issue falsified.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@vercel

vercelBot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredAug 8, 2026 11:23am

Request Review

@yinlianghuiClaude

Copy link
Copy Markdown
CollaboratorAuthor

越界发现已另行记录为 #3749(observation-class,finding 标签,未认领):AppContent.noAppComponentRoutes.test.tsx / AppContent.pseudoRouteSegments.test.tsx 的头注释仍以现在时把 sys-datasources / sys-objects 枚举为在发 legacy 别名的生产端 —— 前者自 #3660 起陈旧,后者自本 PR 起陈旧。两处断言都正确且在本 PR 的消费半径全量里全绿(它们量的是别名路由本身,而别名保留),陈旧的只有叙述,因此没有在本 PR 里动。


Generated by Claude Code


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)28.1 KB350 KB
Entry fileindex-CtzM40k0.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)8.66KB3.13KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)7.57KB2.97KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)22.10KB4.37KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)35.76KB9.11KB
auth (createAuthenticatedFetch.js)4.37KB1.69KB
auth (index.js)2.35KB1.07KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.91KB0.87KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)26.07KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.65KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)480.72KB105.64KB
core (index.js)2.96KB1.13KB
create-plugin (index.js)9.84KB3.18KB
data-objectstack (index.js)137.51KB35.11KB
fields (index.js)230.90KB56.84KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.65KB1.06KB
i18n (pickLocalized.js)1.70KB0.83KB
i18n (provider.js)9.48KB3.27KB
i18n (useObjectLabel.js)26.14KB6.07KB
i18n (useSafeTranslation.js)4.52KB1.96KB
layout (index.js)38.53KB10.71KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)8.75KB3.06KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.67KB1.12KB
permissions (evaluator.js)4.41KB1.44KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.55KB0.71KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)44.98KB12.37KB
plugin-charts (index.js)61.04KB17.31KB
plugin-chatbot (index.js)180.09KB42.72KB
plugin-dashboard (index.js)115.50KB29.96KB
plugin-designer (index.js)210.51KB42.51KB
plugin-detail (index.js)232.79KB57.42KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)112.10KB27.10KB
plugin-gantt (index.js)162.55KB39.57KB
plugin-grid (index.js)186.61KB49.34KB
plugin-kanban (index.js)48.30KB13.28KB
plugin-list (index.js)105.12KB25.48KB
plugin-map (index.js)16.81KB5.24KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)40.58KB10.58KB
plugin-timeline (index.js)25.76KB7.33KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.03KB20.55KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.67KB2.37KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)19.28KB6.38KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.02KB0.55KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)4.47KB2.03KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)2.71KB1.34KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@yinlianghui
yinlianghui marked this pull request as ready for review August 8, 2026 11:36
@yinlianghui
yinlianghui added this pull request to the merge queueAug 8, 2026
Merged via the queue into main with commit b7b05daAug 8, 2026
19 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3739-sys-objects-canonical-route branch August 8, 2026 11:36
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Aug 10, 2026
…ectstack-ai#3858)
objectstack-ai#3712 / PR objectstack-ai#3732 的围栏(MetadataManagerPage / listComponent 一族)之外,
P1.12.3 与 P1.16 两节还剩九条失实条目。手法沿 objectstack-ai#3700 / PR objectstack-ai#3705objectstack-ai#3704 /
PR objectstack-ai#3714objectstack-ai#3712 / PR objectstack-ai#3732:条目一律不删、就地按现实改写、原 [x] 保留、
错误名保留作纠错锚且判词紧跟其后、引用只挂裸文本编号、不种新计数。
P1.12.3 四条:
- metadataTypeRegistry —— 全仓零命中。但它并非"从未存在":文件确实以
apps/console/src/config/metadataTypeRegistry.ts 落过地(d601ef6 加入),
在 ff9a0d9(移除 studio app、迁往 metadata-admin 引擎)被删。今天的等价物
是引擎自己的 MetadataResourceRegistry
(packages/app-shell/src/views/metadata-admin/registry.ts),行类型
MetadataResourceConfig,由 registerMetadataResource() 填;且未注册的类型
同样可列可编,注册只是覆写默认值 —— "centralized config" 的语义也变了。
- SystemHubPage 注册表生成卡片 —— metadataTypeCards 是手写字面量
(Applications / Metadata / Datasources),其上方注释自述 per-type 卡片
在引擎开始自动列举全部类型后即被移除;dashboard / page / report 三张卡
都不存在。真正按注册表列举类型的是引擎的 MetadataDirectoryPage。
- 动态 /system/metadata/:metadataType —— 该拼法只在 console host fragment
(apps/console/src/AppContent.tsx)声明,三种 arity 的 route element 都是
MetadataRedirect,并非页面。真正渲染的动态路由是引擎的 metadata/:type
(packages/app-shell/src/console/AppContent.tsx);"in all route contexts"
这半句对它成立(with-app 与 zero-app 两支都声明了),故如实保留。
- app / object 专属路由 —— app 仍成立(system/apps → AppManagementPage);
object 已无专属页,由引擎通用外壳承载,配置见 builtinComponents.tsx 与
views/metadata-admin/anchors.ts 的 registerMetadataResource({ type:
'object', … }),只给列/搜索字段/建单字段/锚点,无组件覆写。全仓仅
permission(EditPage)与 datasource(ListPage)替换外壳页。
P1.16 四条 + :1001:
- listMode / MetadataTypeConfig / MetadataGrid 三个符号全仓零命中;
MetadataResourceConfig 没有任何渲染模式键,列形状只有 listColumns
(加 ListPage 整页替换)。
- "grid mode 下的表格渲染"—— 表格是真的,但不是一档模式:
ResourceListPage 对所有类型无条件渲染同一个 table,无分支可切。
- report 一条须订正卡片正文的举证:report **有** registerMetadataResource
注册(anchors.ts,基线 4e93e40 上即已存在),卡片正文"零注册"的
grep 结论有误。真实判词是:注册项里没有任何东西选择渲染模式(因为无模式
可选),它配的是对象锚点组与建单种子。
- :1001 按分诊注记逐入口分别判,且前提已部分过期:卡片正文称
sidebar / QuickActions 仍指 legacy 别名,而 objectstack-ai#3739 / PR objectstack-ai#3748 已合并 ——
两个 sidebar 的 sys-objects 与 home 的 manage-objects 现均指引擎正规路由
/apps/setup/metadata/object;hub cards 本就没有 object 卡。故三个入口
今天没有一个指向该拼法,离开的原因各不相同,如实分述。
围栏内只动这九行。节标题 **Metadata Manager Grid Mode:** 依"错误名留作
纠错锚"之例不动;:865 的 MetadataService 三个方法经核实真实存在,不在改写
之列。
Fixesobjectstack-ai#3738
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@yinlianghui@claude