Skip to content

fix(components): delete the second, dead SchemaRenderer - #7514

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7319-dead-schemarenderer
Sep 3, 2026
Merged

fix(components): delete the second, dead SchemaRenderer#7514
os-project-manager merged 1 commit into
mainfrom
claude/issue-7319-dead-schemarenderer

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #7319

Deletes packages/components/src/SchemaRenderer.tsx, a 28-line component sharing an export name with the real renderer in packages/react/src/SchemaRenderer.tsx. Diff is 2 files: the deletion and a changeset.

Clause-②: no

Derived from the diff against the criterion does this change contract accept/reject behaviour, or widen/narrow the published surface? Neither. The file is not in the runtime bundle, and no package specifier resolved to it before this change or after. Evidence below. needs:contract-review is therefore not hung on this PR or on the card.

Which SchemaRenderer this is, and the probe that separates them

SchemaRenderer is a name that also exists live one package over, so a word-count probe is worthless here and nearly stopped this change for the wrong reason. Under packages/components/src the bare word occurs 312 times. Only 2 of those are this file (its own declaration and its self-recursive call); the other 310 live in 101 other files, 55 of which import the name from @object-ui/react and 0 of which import it from any relative path.

The form that decides liveness is the declaration, not the word:

probe (identical shape both rows) packages/components packages/react (control)
SchemaRenderer declarations in src 1 (this file) 1
relative from '../SchemaRenderer' importers 0 28

Every control fired in the same run as its zero.

Nothing could reach it — four independent paths, each with a control

  • Barrel. The only occurrence of the name in packages/components/src/index.ts is prose at :54. dist/index.d.ts contains 0 occurrences of it, against 24 export lines in that same file.
  • Package specifier. The exports map has exactly two entries, . and ./style.css — no wildcard subpath, so no deep import resolves to it.
  • Alias. The package defines @/* to src/*, but 0 files in packages/components/src use that form and 0 files repo-wide import @/…SchemaRenderer. Control: the same grep shape finds 63 files repo-wide that do use @/.
  • Import graph. A transitive walk from index.ts over 210 non-test source files reaches 207; this file was not among them.

Also checked and clear: dynamic import() / React.lazy (every hit resolves to the react renderer's own tests), generated files and scripts/ (0 hits naming the components copy), the package README (0), and tests under packages/components (0 relative importers; the 236 hits there all import from @object-ui/react).

The triage stop-condition: confirmed, with one correction

Triage asked whoever took this to settle the "evaluates no gate at all" claim first, because it decides tidy-up versus removed hazard. Confirmed in substance — it is a trap. It evaluates no predicate at all. Of the real renderer's six visibility legs (visibleWhen / visible / visibleOn / visibility / hidden / hiddenOn) it consults exactly one, hidden, and by bare truthiness rather than evaluation; it reads neither enablement leg (disabled / disabledOn); and it spreads {...schema} raw, so disabled would reach the widget unevaluated — the precise inverse of the real renderer's evaluate, strip, forward-the-verdict contract.

Two precision corrections to the card, both narrowing rather than refuting it:

  1. "No gate at all" is very slightly overstated. It does apply one hide gate; what it never does is evaluate. The accurate statement is "evaluates no predicate", which is the same hazard.
  2. The card's typing is stale. It argues from hidden being boolean | string. On today's main, BaseSchema.hidden is hidden?: boolean (packages/types/src/base.ts:328) and the string sibling is hiddenOn. The trap survives the correction and gets worse in one direction: the copy ignores hiddenOn entirely, so a node declaring it is never hidden.

Reverse verification: no behavioural pin, and that is a measured claim

There is nothing behavioural to pin, so this PR invents no pin. That is demonstrated rather than asserted, by mutating in both directions with the result proven on disk each way:

source file dist top-level .d.ts dist files carrying the body marker control (hasDeclaredVisibilityGate)
at HEAD (change applied) absent 1 0 4
mutated (file restored, rebuilt) present, blob 893387f3 matching origin/main 2 0 4
restored to HEAD (rebuilt) absent 1 0 4

The mutation was confirmed on disk before the rebuild by blob hash equality plus a marker count of 1, not by an editor's exit code; the restore was proven by state (git diff HEAD empty, git status --porcelain empty), not by exit code, under a trap … EXIT INT TERM using absolute paths.

What that table shows: the body never enters the runtime bundle in either direction, so no test could distinguish before from after — any pin would have been vacuous. The file's entire shipped footprint was one stray types-only dist/SchemaRenderer.d.ts (143 bytes) with no dist/SchemaRenderer.js behind it and no specifier reaching it. That is a change to the shipped file list, not to the published surface, which is why the changeset is a patch and why Clause-② is no.

Verification

Run on 746f6f818, the tree this PR proposes. Suites from the repo root per objectui#3378.

  • vitest run --project unit --project dom packages/components/220 test files passed (220), 2021 tests passed (2021), 0 failed.
  • pnpm --filter @object-ui/components type-check — clean.
  • pnpm --filter @object-ui/components lint932 problems (0 errors, 932 warnings); all pre-existing style warnings, no errors.
  • check:readme-exports✅ check-readme-exports: OK (… 0 unbuilt …). ⚠️ On a partly-built tree this first came back exit 1 with its own verdict the population COLLAPSED -- this run proves nothing and 28 unbuilt; that is a could-not-run, not a failure of this diff (0 of its findings named packages/components, 0 named SchemaRenderer). It was converted into a real reading by building all 44 workspace targets, after which it judges 421 self-imports and 52 keys, against floors of 25/2/20.
  • check:dist-completeness✓ dist completeness: 12 package(s) complete (1608 emitted files verified); 2 type-check-only, on that same fully built tree.
  • check:control-bytes✅ check-control-bytes: OK (scanned 6194 tracked text file(s); skipped 85 binary).
  • check:phantom-deps exit 0 — the deleted file imported @object-ui/core, which 235 other files in the package (92 non-test) still import, so the dependency is not orphaned.
  • check:self-import, check:esm-specifiers, check:node-esm-load, check:entry-guard, check:side-effects-array, check:eager-closure — all exit 0.

The sweep triage asked about

Triage flagged that two unreferenced duplicates in one package in one day is a pattern and asked whether a sweep is warranted. Measured rather than guessed, and the answer is yes, but for a smaller set than it looks:

  • Of 210 non-test source files, exactly 3 are unreachable from the barrel after this deletion. Two of them — the use-sync-external-store shims — are not dead: they are reached through vite.config.ts alias entries, which an import-graph walk cannot see. That leaves one genuine orphan, packages/components/src/ui/toast.tsx (0 exports from ui/index.ts, 0 importers; control: the live sonner implementation has 16).
  • objectui#7397's file is not in this class. ui/chart.tsx is reachable from the barrel — index.ts:90 export * from './ui' reaches ui/index.ts:20 export * from './chart'. Its liveness picture differs from this card's, so it is not a second instance of this shape; that card stands on its own mechanism and stays open.
  • No gate covers this class. None of the repo's 42 check:* scripts detects an unreferenced source file, which is why both cards were found by hand. Filed separately as a finding rather than fixed here.

🤖 Generated with Claude Code

Generated by Claude Code


Generated by Claude Code

`packages/components/src/SchemaRenderer.tsx` was a 28-line component sharing an
export name with the real renderer in `packages/react/src/SchemaRenderer.tsx`,
reachable from nothing: absent from the barrel, imported by no file in the repo
in any form, and behind no subpath in the package's `exports` map.

It was never in the runtime bundle, so this is not a behaviour change. Its only
shipped footprint was a stray types-only `dist/SchemaRenderer.d.ts` with no
runtime module behind it and no specifier that resolves to it.

Deleting beat keeping because the copy is a trap: it evaluates no predicate.
Of the real renderer's six visibility legs it consults only `hidden`, by bare
truthiness rather than evaluation, so a node declaring `hiddenOn` is never
hidden; it reads neither enablement leg; and it spreads `{...schema}` raw, so
`disabled` would reach the widget unevaluated — the inverse of the real
renderer's evaluate-strip-forward contract.

Fixes #7319

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3181.9 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-nXe7vDDx.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) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 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.68KB 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) 516.19KB 117.80KB
core (index.js) 6.12KB 2.42KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 178.20KB 49.60KB
fields (index.js) 242.42KB 61.26KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.44KB 1.39KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
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.55KB 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.72KB 0.42KB
mobile (useResponsiveConfig.js) 1.37KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
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) 15.75KB 3.80KB
plugin-calendar (index.js) 48.12KB 13.34KB
plugin-charts (index.js) 70.87KB 19.72KB
plugin-chatbot (index.js) 196.19KB 46.43KB
plugin-dashboard (index.js) 132.82KB 34.64KB
plugin-designer (index.js) 212.87KB 43.19KB
plugin-detail (index.js) 251.07KB 64.12KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.50KB 41.08KB
plugin-grid (index.js) 209.25KB 56.71KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.33KB 27.60KB
plugin-map (index.js) 20.55KB 6.80KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 43.51KB 11.94KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.40KB 3.23KB
plugin-view (index.js) 85.22KB 20.93KB
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.66KB 3.50KB
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) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 3.63KB 1.70KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
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) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
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 (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
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) 4.74KB 2.25KB
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 (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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

Copy link
Copy Markdown
Collaborator Author

被合并队列踢出,CI_FAILURE —— 失败不是本 PR 的,根因已定位并另立卡派工

domain:ui 执行席,session session_01EMrWaQw3XS5DxTHxp4yRyC

失败的具体位置

队列分支 gh-readonly-queue/main/pr-7514-fe4e7a9e8…,workflow run 33776772672,job Test (shard 3/4)。该分支上其余 16 个 workflow 全绿,只有 CI 红;而 CI615 个测试文件只有 1 个失败,8048 个测试通过:

FAIL  dom  packages/plugin-timeline/src/ObjectTimeline.colorFieldLadder-7243.test.tsx
  > rung 2: 3- and 6-digit hex literals pass through
  AssertionError: expected [ '#abc' ] to deeply equal [ '#123456' ]
  ❯ ObjectTimeline.colorFieldLadder-7243.test.tsx:109:72

为什么它不是本 PR 的

本 PR 的 diff 是两个文件:删除 packages/components/src/SchemaRenderer.tsx,加一个 changeset。失败在 packages/plugin-timeline。两者之间没有 import 关系,没有共享文件,本 PR 也不触碰 plugin-timeline 的任何依赖。

⛔ 并且这不是「flake」。「flake」意味着没有确定的成因。这里成因是确定的,已按内容读出:

:34  import { render, waitFor } from '@testing-library/react';   ← ⛔ 无 cleanup,无 afterEach
:38  let lastItems: any[] = [];                                   ← 模块级共享
:40  vi.mock('./renderer', () => ({ ... lastItems = schema.items ?? []; ... }))

colorsFor() 每次 render()从不 unmount,而 rung 2 在同一个 it 里调用它两次。testing-library 的自动 cleanup 只在 afterEach 跑,不在一个 it 内的两次 render 之间 ⇒ 两个组件同时活着,共享那一个模块级数组;而 waitFor 的谓词只看 lastItems.length、不看内容,所以分辨不出「B 画好了」和「A 又写了一次」。CPU 空闲时 B 通常先到,测试就绿;队列 CI 跑 615 文件 / 772 秒,负载高,A 的重渲染插了进来。

它会踢掉任何恰好在负载下入队的 PR,与那个 PR 的内容无关。本 PR 只是第一个已知受害者。

处置

⛔ 本 PR 自身的检查在其 head 746f6f818 上是 32 项全解、29 success / 3 skipped / 0 failure,席内核验见 #7319 评论 5528249925。没有任何一处需要改动。


Generated by Claude Code

@os-project-manager
os-project-manager added this pull request to the merge queue Sep 3, 2026
Merged via the queue into main with commit 26e06d7 Sep 3, 2026
34 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7319-dead-schemarenderer branch September 3, 2026 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(components): a second, dead SchemaRenderer lives in packages/components/src — unexported, imported by nothing, and it evaluates no gate at all

2 participants