Skip to content

refactor(layout)!: delete PageNodeRenderer, the unregistered page-node renderer - #3225

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-3223-remove-dead-page-renderer
Aug 2, 2026
Merged

refactor(layout)!: delete PageNodeRenderer, the unregistered page-node renderer#3225
os-zhuang merged 1 commit into
mainfrom
claude/issue-3223-remove-dead-page-renderer

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#3223

按 PM 在 issue 上的处置执行:删除,依据 ADR-0049(enforce-or-remove)。

动手前先自证前提

处置的前提是「没有任何调用点」。删之前把这条重新验证了一遍(全仓,含 apps/content/docs/skills/):

搜索结果
PageNodeRenderer(全仓)只有 4 处,全部在 packages/layout 自己内部 + batch 7 的 changeset 表格
from './Page' / from '../Page'只有 barrel 的 export * from './Page' 和 batch 7 测试的 import
import { … Page … } from '@object-ui/layout'(ts/tsx)
@object-ui/layout 的下游包app-shellapps/console,只用到 resolveHref / NavigationRenderer / AppShell

结论:前提成立,没有真实消费者,可以删。

改了什么

  • 删除 src/Page.tsx 及 barrel 里的 export * from './Page' 它注册在任何组件键之下都不存在、也没人 import,只通过 barrel 出现在公开 API 里;而 registerLayout() 里那条 NOTE 本来就在劝阻把 page 注册回来——page 键归 @object-ui/componentsPageRenderer(它支持 record/home/app/utility 页面类型、命名 region 和 PageVariablesProvider,被删的这个只画一个 header 加 children)。
  • registerLayout() 的 NOTE 补上了这段历史:一个键一个渲染器;page 节点若需要 layout 侧的东西,加到 components 的渲染器里,不要在这里再开一个。
  • 新增 page-node-renderer-stays-deleted.test.ts(墓碑测试)。 单纯删掉,下一个人重新加回 export 只是一行、而且看起来像在补缺口。这个测试让「加回来」直接变红并指向要先推翻的决定。它是包内源码级的禁令,不是对组合后 registry 的证明——注释里写明了这个边界。已用反向变更验证过它会红(把 export 加回去 + 建空 Page.tsx,对应两条断言失败,随后已还原)。
  • 保留 batch 7 的编译期 pin,包括全仓唯一一处 PageNodeSchema[ 'type' ] === 'page' 的断言。 这条钉的是 wire 契约——PageRenderer 就是注册在这个键上的——所以它比被删的组件活得更久,不随组件一起删;测试头部的注释改写成了「重命名的对象已被删除,活下来的是分层拆分本身」。
  • tsconfig.test.json 显式声明 types: ["node"],供墓碑测试的源码扫描使用,写法与 packages/types/tsconfig.test.json 一致;没有放进 tsconfig.json,这样包源码依旧无法编译过 Node API(这个包是发到浏览器的)。
  • README 里那段 import { Page, PageHeader } from '@object-ui/layout' 的示例删掉了——它在 batch 7 改名之后其实已经过期(而且示例用的 children 写法与组件真实签名也对不上),换成 PageHeader 的用法,并指向 PageRendererpackage.json 的 description 同步改为 AppShell, PageHeader, SidebarNav
  • major changeset(公开 API 移除),写明 FROM → TO:PageNodeRenderer from @object-ui/layoutPageRenderer from @object-ui/components,并说明 schema 驱动的消费者不受影响({ type: 'page' } 节点一直是经 registry 落到 PageRenderer,从来不是这个导出)。

顺带说明:props 那半边

issue 的第 2 点({ schema: PageNodeSchema; … } & any 会把整个类型吸收掉)在这个方案下自动消失——类型随组件一起没了,不需要单独收敛。

验证

npx vitest run packages/layout --maxWorkers=2
Test Files 5 passed (5)
Tests 71 passed (71)
npx turbo run type-check --filter=@object-ui/layout
tsc --noEmit && tsc -p tsconfig.test.json ✓
npx turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/console
Tasks: 36 successful, 36 total ✓ (layout 的两个下游包)
npx turbo run build lint --filter=@object-ui/layout ✓
node scripts/check-spec-symbol-derivation.mjs
✅ 1190 files scanned … 3 untriaged collisions in 1 packages (与 batch 7 后的基线一致,未变化)
node scripts/check-changeset-fixed.mjs ✓

第一次跑 type-check 时墓碑测试的 node:fs 报 TS2591 —— 因为 @object-ui/layout类型检查自己测试的包(有 tsconfig.test.json),而 packages/core 里同类的 *-stays-unwired 测试之所以能直接用 node:fs,是它的 tsconfig.json 把测试 exclude 掉了、根本没人编译。按 packages/types 的既有写法补 types: ["node"] 解决。

范围

只动 packages/layout/(加一个 .changeset/)。未触碰 packages/app-shell/src/views/metadata-admin/(#3218)、packages/fields/(#3221)、scripts/check-spec-symbol-derivation.mjs

草稿状态,不自行合并;issue 也未关闭,交 PM review。


🤖 Generated with Claude Code

https://claude.ai/code/session_01PRJtkgUAaVG11FsJQbvZWA


Generated by Claude Code

…e renderer
`@object-ui/layout`'s `PageNodeRenderer` (`./Page`, named `Page` before
objectui#3161 batch 7) was registered under no component key and imported
by nothing — a whole-repo grep finds zero call sites. It reached consumers
only through `export * from './Page'`, and `registerLayout()` already
carried a note telling the next reader NOT to register it: the `page` key
belongs to `@object-ui/components`'s `PageRenderer`, which is the renderer
that supports page types, named regions and `PageVariablesProvider`.
Removed under ADR-0049 (enforce-or-remove). Wiring it up instead would mean
two renderers for one key — a fork nothing is asking for — while leaving it
in place kept telling every reader that `@object-ui/layout` is where page
rendering lives. Its props were also `{ schema: PageNodeSchema; … } & any`,
and an intersection with `any` absorbs the whole type, so the signature
asserted nothing beyond "there is a schema" (objectui#3221 mechanism, fourth
spelling).
- delete `src/Page.tsx` and its barrel re-export; the `registerLayout` note
now records why no second `page` renderer belongs here
- add `page-node-renderer-stays-deleted.test.ts`: a source-level tombstone
so re-adding the export is a red test naming the decision, not a one-line
gap-fill (verified red by reintroducing the export)
- keep the batch-7 type pins, including the repo's only pin of
`PageNodeSchema['type'] === 'page'` — that is the wire key `PageRenderer`
answers to, and it outlives the component it was written beside
- `tsconfig.test.json` names `types: ["node"]` for the tombstone's source
scan, matching `packages/types/tsconfig.test.json`; it stays out of
`tsconfig.json` so package source still cannot compile against Node APIs
- README stopped documenting `<Page>` (an example already stale since the
batch-7 rename) and points at `PageRenderer` instead
Fixes#3223
@vercel

vercelBot commented Aug 2, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredAug 2, 2026 7:59pm

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation dependencies tests labels Aug 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)8.47KB3.09KB
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.12KB3.41KB
auth (LoginForm.js)17.86KB5.29KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.43KB2.09KB
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)18.38KB4.49KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)3.65KB1.42KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.25KB0.53KB
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)476.12KB104.47KB
core (index.js)2.25KB0.80KB
create-plugin (index.js)9.28KB2.98KB
data-objectstack (index.js)136.23KB34.75KB
fields (index.js)223.43KB54.66KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.46KB0.96KB
i18n (pickLocalized.js)1.70KB0.83KB
i18n (provider.js)5.37KB1.72KB
i18n (useObjectLabel.js)26.14KB6.07KB
i18n (useSafeTranslation.js)3.26KB1.44KB
layout (index.js)37.91KB10.53KB
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.05KB1.53KB
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.90KB12.35KB
plugin-charts (index.js)60.53KB17.12KB
plugin-chatbot (index.js)180.09KB42.72KB
plugin-dashboard (index.js)111.87KB28.82KB
plugin-designer (index.js)210.51KB42.50KB
plugin-detail (index.js)230.54KB56.77KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)111.39KB26.94KB
plugin-gantt (index.js)162.26KB39.53KB
plugin-grid (index.js)185.04KB49.00KB
plugin-kanban (index.js)47.82KB13.18KB
plugin-list (index.js)104.86KB25.30KB
plugin-map (index.js)16.80KB5.24KB
plugin-markdown (index.js)13.65KB4.67KB
plugin-report (index.js)40.48KB10.57KB
plugin-timeline (index.js)25.76KB7.32KB
plugin-tree (index.js)8.34KB2.82KB
plugin-view (index.js)83.54KB20.39KB
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.46KB1.21KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)0.20KB0.18KB
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

@os-zhuangClaude

Copy link
Copy Markdown
ContributorAuthor

范围外发现,已按 Prime Directive #10 单独立 issue、未在本 PR 修:

  • page-headerpage:header 对外声明了两套 authorable 键:description vs subtitle,消费端用 ?? 兜底 #3226page-header(layout)与 page:header(components)对外声明了两套 authorable 键:前者的 registration inputs 宣告 description,后者宣告 spec 的 subtitle,而 PageHeader.tsxsubtitle ?? description 在消费端兜底。同一份 metadata 在两个键下渲染结果不同,且 inputs 声明本身在教作者写非 spec 的键。issue 里给了两条处置路线(删 / 登记成 ADR-0087 conversion 条目),需要先确认还有没有真在写 description 的 legacy console 页面,故未自行动手。

packages/layout/src/index.tsPageHeader.tsx 旁边发现的,与本 PR 删除 PageNodeRenderer 无关。


Generated by Claude Code

@os-zhuangClaude

Copy link
Copy Markdown
ContributorAuthor

PM 验收:ACCEPT,放行合并队列

CI 15 项 13 绿 2 skipped、零红,已独立复核。

三件做对了、且值得记住的:

1. 先自证前提再动手。 派发时要求「若找到真实消费者就停下报告,而不是照令删除」——复查覆盖了 apps/ / content/docs/ / skills/,确认零外部消费者才删。处置的正确性不该建立在派发人没查错上。

2. 没有只是删掉,而是让它「保持被删」。export 加回来只有一行,而且看起来像在补一个缺口——单纯删除挡不住下一个 session。墓碑测试把「加回来」变成一条指向决定的红,并且用反向变更验证过它会红(加回 export + 空 Page.tsx → 两条断言失败)。没有变异验证的守卫等于没有守卫。

3. 删组件,但不删它旁边那条 wire 契约的钉扎。 这是派发时划的线,执行到位了。

两处判断我都同意

  • PageNodeSchema['type'] === 'page' 的钉扎留在 packages/layout,尽管 layout 已经完全不引用 PageNodeSchema 了。留是对的:它是全仓唯一一条钉这个判别式的断言,而它守的那个键正是 PageRenderer 注册的键——跟着它的旧主体一起删,是净损失覆盖。「搬到 packages/types 更合适」我也同意,但那超出本单范围,已记为后续,不在本 PR 返工。

  • tsconfig.test.jsontypes: ["node"] 保留。它确实是纯删除之外的配置改动,但理由成立:墓碑测试要扫源码;写法照 packages/types 既有先例;而且刻意没有放进 tsconfig.json,包源码依旧编译不过 Node API——这个包是发到浏览器的,那条边界守住了。替代方案是丢掉墓碑测试,那等于把「让删除保持删除」的唯一机制换掉,不划算。

顺带一条实测收获:packages/core 里同类的 *-stays-unwired 测试能直接用 node:fs,只是因为 core 的 tsconfig.json 把测试 exclude 掉了、根本没人编译它(已登记债务 #4118)。同一份写法在两个包里一个红一个绿,差别不在写法而在有没有被类型检查——#3181 那条线的又一个样本。

Bundle 侧也印证了这是真死代码:layout (index.js) 38.48 → 37.91 KB(gzip 10.67 → 10.53)。它一直在被打包发给每一个下游应用。

越界发现 #3226 已立案,我单独分诊(见该单)。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 2, 2026 20:06
@os-zhuang
os-zhuang added this pull request to the merge queueAug 2, 2026
Merged via the queue into main with commit 6d868e1Aug 2, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-3223-remove-dead-page-renderer branch August 2, 2026 20:06
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependenciesdocumentationImprovements or additions to documentationtests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@object-ui/layoutPageNodeRenderer 是没人注册的死渲染器,props 还是 … & any

2 participants

@os-zhuang@claude