Skip to content

test(app-shell): 同步 pseudoRouteSegments 里的第二份 MetadataRedirectStub,并用整链断言钉住 (#3669) - #3691

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-3669-second-stub-sync
Aug 8, 2026
Merged

os-project-manager merged 1 commit into
mainfrom
claude/issue-3669-second-stub-sync

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #3669

前提复核(先证实,再动手)

issue 正文在最新 origin/main(e98702190,已含 PR #3671)上逐条命中,前提成立:

前置条件也实测过:PR #3671(第一份 stub 的同步,本单的直接模板)已落 main(e98702190),手法照抄。

失真是实测出来的

动手前先只给 LocationProbe 加链记录、不动 stub,把五条链打了出来(用一条必失败的断言让 vitest 把实际数组打进 diff — 该仓 reporter 吞 console.log):

用例 旧 stub 下实测的链 跳数
/apps/ghost/system/metadata/object [system/metadata/object, **component/metadata/resource?type=object**, metadata/object] 2
/apps/setup/system/metadata/object [system/metadata/object, **component/metadata/resource?type=object**, metadata/object] 2
/apps/ghost/component/metadata/resource?type=datasource [component/metadata/resource?type=datasource, metadata/datasource] 1
/apps/ghost/component/metadata/directory [component/metadata/directory, metadata] 1
/apps/setup/component/metadata/resource?type=datasource [component/metadata/resource?type=datasource, metadata/datasource] 1

两点确认:链记录器确实能看到中间站(否则后面的逆向验证就是空壳);issue 点名的两条都在测两跳链,且中间站正是已废弃的别名。

顺带一条比 issue 正文多出来的事实:本文件里 stub 的另外两条臂从未被执行过 —— 没有任何用例走 system/metadata(typeless)或 system/metadata/:type/:name,所以只有 metadataType && !itemName 这一条臂在跑,跑了两次。

改了什么

1. stub 同步(逐字转录恢复为真)

-  const base = `${prefix}/component/metadata/resource`;
+  const base = `${prefix}/metadata`;
   const target = !metadataType
-    ? `${prefix}/component/metadata/directory`
+    ? base
     : itemName
-      ? `${base}/${itemName}?type=${metadataType}`
-      : `${base}?type=${metadataType}`;
+      ? `${base}/${encodeURIComponent(metadataType)}/${itemName}`
+      : `${base}/${encodeURIComponent(metadataType)}`;

机器比对过(剥掉注释后 diff):同步后 stub 函数体与宿主 MetadataRedirect 函数体可执行代码逐字节一致,唯一差异是宿主多带的三行注释(那三行 stub 改前也没有)。

docblock 改为注明转录对象、出处 commit(7883c0250 / #3658),并明说「逐字」是一句需要持续兑现的声明 —— 它在 7883c0250 到本单之间就是假的。

2. 整链断言(防复发)

LocationProbe 升级为链记录器,renderConsoleAt 返回整条链。这是必要的:旧断言只钉最终 pathname(setup 那条连 pathname 都不钉,只钉 testid),而一跳与两跳的终点逐字节相同,所以 stub 失真时没有任何断言会红。

3. 两条受影响用例提为独立 it

两条都原本是 it.each 表里的行,而共享的表体只能钉终点,钉不了跳数。所以把这两行提出来,写成独立 it 并断言整条链 —— 原有断言一条不少地保留(data-active-app 为 crm、无 app-not-available-retry、pathname),再加上整链与「链中不含 component/metadata」。

表里其余 11 行 + 4 行、以及 #3638 落的近似段族断言一行未动(见 diff:全文件只有这两行被移除)。

覆盖清点

stub 改直达后,这两条用例不再顺路经过 with-app 分支的 component/metadata/resource/*(packages/app-shell/src/console/AppContent.tsx:792)。清点结果:没有任何路由因此失去全仓唯一覆盖,不需要补用例。

路由声明 分支 改后仍由谁覆盖
AppContent.tsx:792 component/metadata/resource/* with-app 本文件 :337(/apps/ghost/…?type=datasource)与 setup 家族表(/apps/setup/…?type=datasource)—— 两条都直接进入别名,不经 stub
AppContent.tsx:791 component/metadata/directory with-app 本文件 :338(/apps/ghost/component/metadata/directory),直接进入;且该路由本就没有经过 stub 的覆盖 —— stub 的 typeless 臂在本文件从未被执行
AppContent.tsx:663 component/metadata/resource/* 零应用 本文件零应用 describe 里的 /apps/setup/…?type=datasource(metadataApps = [])
AppContent.tsx:662 component/metadata/directory 零应用 PR #3671 已补的直接进入用例(AppContent.noAppComponentRoutes.test.tsx)

这与 #3661 的处境不同,原因 issue 的 ⚠️ 说对了:那边零应用分支的 directory 别名只有 stub 的 typeless 臂在跑,所以 #3671 必须补一条接住;本文件对应的别名覆盖全部来自直接进入的用例,stub 是否途经它们都不影响。

逆向验证(先预测,后运行)

预测:只把 stub 的 target 构造回退成别名(docblock 与断言保持不动),应当恰好两条用例转红 —— 即新提出的两条 host-rewrite 用例;红的形态应为链正中多出一个别名条目、首尾条目不变,同时两条 component/metadata 缺席断言翻为 true。其余 34 条(含输入面回归表与近似段族)应全程绿。方向为常规「红」,不存在反转 —— 整链断言是数组逐位相等,长度一变必红。

实跑,与预测逐条一致:

 × unresolved app segment: the host rewrite reaches canonical metadata/:type in ONE hop
 × the /apps/setup family is unaffected: the host rewrite still lands in ONE hop
 Test Files  1 failed (1)
      Tests  2 failed | 34 passed (36)

两张 diff 各只多出一行:

  [
    "/apps/ghost/system/metadata/object",
+   "/apps/ghost/component/metadata/resource?type=object",
    "/apps/ghost/metadata/object",
  ]

  [
    "/apps/setup/system/metadata/object",
+   "/apps/setup/component/metadata/resource?type=object",
    "/apps/setup/metadata/object",
  ]

这两张 diff 本身就是本单为什么会静默发生的直接证据:两条链的首尾条目完全没动,只有中间多了一站。旧的「只断终点」断言在结构上就不可能红。

门禁

pnpm exec vitest run …/AppContent.pseudoRouteSegments.test.tsx
  → Test Files 1 passed / Tests 36 passed(改前亦 36 —— 表里去掉 2 行,提出 2 条独立用例)

pnpm exec vitest run packages/app-shell/src/console/__tests__
  → Test Files 6 passed (6) / Tests 64 passed (64)

pnpm --workspace-concurrency=2 --filter @object-ui/app-shell type-check   → 通过(tsc --noEmit && tsc -p tsconfig.typetests.json)
pnpm exec eslint 该文件                                                    → exit 0
node scripts/check-control-bytes.mjs                                       → OK(3672 个文本文件)
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' 该文件                            → 无

type-check 前先跑了 pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build(新 worktree 未构建依赖会报一片 Cannot find module '@object-ui/*',AGENTS.md §9 陈旧产物陷阱的镜像)。

changeset

不写:本 PR 只改一个测试文件,零生产代码改动,对包的发布物与运行时行为没有任何影响,不属于用户可见变更(与 PR #3671 同判)。

越界发现

无。本次未发现新的越界问题。


🤖 Generated with Claude Code

https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt


Generated by Claude Code

…整链断言钉住 (#3669)

第二份 MetadataRedirectStub 自称逐字转录宿主 MetadataRedirect,但 #3658 把宿主
改为直接构造 canonical `…/metadata/:type` 后未同步,仍构造已废弃的
`component/metadata/resource?type=` 别名。被它喂的两条 `system/metadata/*` 用例
因此在测一条生产中已不存在的两跳链,却始终是绿的 —— 断言只钉终点,而终点在
一跳与两跳下逐字节相同。

- stub 按 7883c02 后的宿主实现逐字同步(可执行代码经机器比对逐字节一致);
- LocationProbe 升级为链记录器,renderConsoleAt 返回整条重定向链;
- 两条受影响用例从 it.each 表中提出为独立 it,改断整条链(原有断言全部保留)。

#3638 落的输入面回归断言与近似段族断言一行未动。测试-only,无 changeset。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 7, 2026 9:33pm

Request Review

@github-actions github-actions Bot added the tests label Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.1 KB 350 KB
Entry file index-eEqlis5G.js —
Status PASS —

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.66KB 3.13KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
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) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 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) 480.72KB 105.64KB
core (index.js) 2.96KB 1.13KB
create-plugin (index.js) 9.08KB 2.92KB
data-objectstack (index.js) 137.51KB 35.11KB
fields (index.js) 230.87KB 56.83KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.65KB 1.06KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 9.48KB 3.27KB
i18n (useObjectLabel.js) 26.14KB 6.07KB
i18n (useSafeTranslation.js) 4.52KB 1.96KB
layout (index.js) 38.53KB 10.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.74KB
mobile (index.js) 1.50KB 0.62KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.98KB 12.37KB
plugin-charts (index.js) 61.04KB 17.31KB
plugin-chatbot (index.js) 180.09KB 42.72KB
plugin-dashboard (index.js) 115.50KB 29.96KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 232.79KB 57.42KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 112.10KB 27.10KB
plugin-gantt (index.js) 162.55KB 39.57KB
plugin-grid (index.js) 186.61KB 49.34KB
plugin-kanban (index.js) 48.30KB 13.28KB
plugin-list (index.js) 105.12KB 25.48KB
plugin-map (index.js) 16.81KB 5.24KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.58KB 10.58KB
plugin-timeline (index.js) 25.76KB 7.33KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.03KB 20.55KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 19.28KB 6.38KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.02KB 0.55KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 2.71KB 1.34KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
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 (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
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

@yinlianghui
yinlianghui marked this pull request as ready for review August 7, 2026 21:46
@os-project-manager
os-project-manager added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit ae3bd96 Aug 8, 2026
18 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-3669-second-stub-sync branch August 8, 2026 08:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AppContent.pseudoRouteSegments.test.tsx 里还有第二份 MetadataRedirectStub,同样在 #3658 后失真(自称逐字转录,仍走废弃别名)

3 participants