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/componentspackages/react (control)
SchemaRenderer declarations in src1 (this file)1
relative from '../SchemaRenderer' importers028

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 filedist top-level .d.tsdist files carrying the body markercontrol (hasDeclaredVisibilityGate)
at HEAD (change applied)absent104
mutated (file restored, rebuilt)present, blob 893387f3 matching origin/main204
restored to HEAD (rebuilt)absent104

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.tsxis 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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-nXe7vDDx.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.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.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.12KB13.34KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.50KB41.08KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.63KB1.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

被合并队列踢出,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 queueSep 3, 2026
Merged via the queue into main with commit 26e06d7Sep 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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/componentspackages/react (control)
SchemaRenderer declarations in src1 (this file)1
relative from '../SchemaRenderer' importers028

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 filedist top-level .d.tsdist files carrying the body markercontrol (hasDeclaredVisibilityGate)
at HEAD (change applied)absent104
mutated (file restored, rebuilt)present, blob 893387f3 matching origin/main204
restored to HEAD (rebuilt)absent104

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.tsxis 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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-nXe7vDDx.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.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.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.12KB13.34KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.50KB41.08KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.63KB1.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

被合并队列踢出,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 queueSep 3, 2026
Merged via the queue into main with commit 26e06d7Sep 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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/componentspackages/react (control)
SchemaRenderer declarations in src1 (this file)1
relative from '../SchemaRenderer' importers028

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 filedist top-level .d.tsdist files carrying the body markercontrol (hasDeclaredVisibilityGate)
at HEAD (change applied)absent104
mutated (file restored, rebuilt)present, blob 893387f3 matching origin/main204
restored to HEAD (rebuilt)absent104

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.tsxis 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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-nXe7vDDx.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.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.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.12KB13.34KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.50KB41.08KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.63KB1.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

被合并队列踢出,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 queueSep 3, 2026
Merged via the queue into main with commit 26e06d7Sep 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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/componentspackages/react (control)
SchemaRenderer declarations in src1 (this file)1
relative from '../SchemaRenderer' importers028

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 filedist top-level .d.tsdist files carrying the body markercontrol (hasDeclaredVisibilityGate)
at HEAD (change applied)absent104
mutated (file restored, rebuilt)present, blob 893387f3 matching origin/main204
restored to HEAD (rebuilt)absent104

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.tsxis 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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-nXe7vDDx.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.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.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.12KB13.34KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.50KB41.08KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.63KB1.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

被合并队列踢出,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 queueSep 3, 2026
Merged via the queue into main with commit 26e06d7Sep 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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/componentspackages/react (control)
SchemaRenderer declarations in src1 (this file)1
relative from '../SchemaRenderer' importers028

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 filedist top-level .d.tsdist files carrying the body markercontrol (hasDeclaredVisibilityGate)
at HEAD (change applied)absent104
mutated (file restored, rebuilt)present, blob 893387f3 matching origin/main204
restored to HEAD (rebuilt)absent104

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.tsxis 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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-nXe7vDDx.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.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.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.12KB13.34KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.50KB41.08KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.63KB1.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

被合并队列踢出,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 queueSep 3, 2026
Merged via the queue into main with commit 26e06d7Sep 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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/componentspackages/react (control)
SchemaRenderer declarations in src1 (this file)1
relative from '../SchemaRenderer' importers028

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 filedist top-level .d.tsdist files carrying the body markercontrol (hasDeclaredVisibilityGate)
at HEAD (change applied)absent104
mutated (file restored, rebuilt)present, blob 893387f3 matching origin/main204
restored to HEAD (rebuilt)absent104

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.tsxis 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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-nXe7vDDx.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.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.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.12KB13.34KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.50KB41.08KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.63KB1.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

被合并队列踢出,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 queueSep 3, 2026
Merged via the queue into main with commit 26e06d7Sep 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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/componentspackages/react (control)
SchemaRenderer declarations in src1 (this file)1
relative from '../SchemaRenderer' importers028

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 filedist top-level .d.tsdist files carrying the body markercontrol (hasDeclaredVisibilityGate)
at HEAD (change applied)absent104
mutated (file restored, rebuilt)present, blob 893387f3 matching origin/main204
restored to HEAD (rebuilt)absent104

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.tsxis 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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-nXe7vDDx.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.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.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.12KB13.34KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.50KB41.08KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.63KB1.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

被合并队列踢出,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 queueSep 3, 2026
Merged via the queue into main with commit 26e06d7Sep 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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/componentspackages/react (control)
SchemaRenderer declarations in src1 (this file)1
relative from '../SchemaRenderer' importers028

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 filedist top-level .d.tsdist files carrying the body markercontrol (hasDeclaredVisibilityGate)
at HEAD (change applied)absent104
mutated (file restored, rebuilt)present, blob 893387f3 matching origin/main204
restored to HEAD (rebuilt)absent104

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.tsxis 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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 50 chunks)3181.9 KB3191.4 KB
Main entry chunk (gzip)143.2 KB350 KB
Entry fileindex-nXe7vDDx.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.67KB5.75KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.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)516.19KB117.80KB
core (index.js)6.12KB2.42KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)242.42KB61.26KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)34.34KB9.17KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.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.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)48.12KB13.34KB
plugin-charts (index.js)70.87KB19.72KB
plugin-chatbot (index.js)196.19KB46.43KB
plugin-dashboard (index.js)132.82KB34.64KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)251.07KB64.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.87KB32.66KB
plugin-gantt (index.js)167.50KB41.08KB
plugin-grid (index.js)209.25KB56.71KB
plugin-kanban (index.js)52.71KB14.55KB
plugin-list (index.js)113.33KB27.60KB
plugin-map (index.js)20.55KB6.80KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)30.84KB8.85KB
plugin-tree (index.js)9.40KB3.23KB
plugin-view (index.js)85.22KB20.93KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.63KB1.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.74KB2.25KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)8.11KB3.32KB
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-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

被合并队列踢出,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 queueSep 3, 2026
Merged via the queue into main with commit 26e06d7Sep 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-project-manager@claude