Skip to content

test(dom-leak): one attribute judge, shared by both gates (#4434) - #4499

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-4434-shared-leak-judge
Aug 12, 2026
Merged

test(dom-leak): one attribute judge, shared by both gates (#4434)#4499
yinlianghui merged 2 commits into
mainfrom
claude/issue-4434-shared-leak-judge

Conversation

@yinlianghui

@yinlianghui yinlianghui commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Closes #4434

The DOM-leak "is this attribute HTML-defined" judge existed in two copies, inline in two test files that cannot import each other. This extracts it once, carrying the union of what the two copies knew, and moves the calibration fixtures next to it so the judge proves itself once instead of once per copy.

Only the judge unifies. Each gate keeps its own canary sets, targets, readiness selectors, ledger and assertions — the only non-comment edits to either gate file are the import and, in the fields gate, five leakReport call sites re-spelled to the unified label parameter (they produce byte-identical messages).

The home: measured, then chosen

Measured first — there is no existing convention to follow. Nothing in this repo ships shared test code across a package boundary today:

  • no ./testing / ./test-support subpath in any exports map (grep '"./test' over every manifest: zero hits);
  • the one test-helper module that exists, packages/components/src/__tests__/test-utils.tsx, is imported only by its own package's tests;
  • zero cross-package relative imports from tests (grep "from '../../../../" over every *.test.ts(x): zero hits);
  • the only precedent is a negative one — @object-ui/fields deep subpaths resolve only through the repo vitest alias — its exports map publishes none of them #4325, which ruled out @object-ui/fields/widgets/MarkdownContent: a specifier only this repo's vitest alias could resolve, TS2882 for tsc, unresolvable outside the repo. A package's surface is its index.

So this implements the smallest sound convention, against the three constraints:

constraint private workspace package (chosen) ./test-support export on a released package
(a) reachable from packages/fields and packages/app-shell yes — one devDependency line each yes
(b) not published runtime API yes — private: true, never released no — an entry in a published exports map is public API whatever it is named, and it has to ship in the tarball to resolve
(c) no unpublished deep subpath yes — bare @object-ui/test-support, resolved through its own exports map yes

Constraint (b) is what decides it, so: packages/test-support, @object-ui/test-support, private: true.

It resolves the same way for both toolchains, which is the #4325 lesson applied rather than restated:

  • tsc — through the package exports map. Proof that this is not leaning on a repo-only alias: packages/app-shell/tsconfig.test.json sets "paths": {} precisely to force resolution through the workspace dependency, and tsc -p tsconfig.test.json --listFiles lists packages/test-support/src/dom-leak-judge.ts and .../src/index.ts as program inputs.
  • vitest — an alias next to the other 40, so the test resolves the same file tsc reads instead of depending on how Vite treats a symlinked dependency.

Home wiring, all of it: the package manifest/tsconfig/README, one devDependencies line in each consumer, one vitest alias, and one .changeset/config.json ignore entry (a never-published package must not be versioned; scripts/check-changeset-fixed.mjs requires every workspace package to be classified as one or the other). Verified green: check-type-check-coverage (now 46/46 packages, 41 compiling their tests), check-lint-coverage (46/46), check-changeset-fixed, check:phantom-deps, and changeset status — the last one matters because changesets rejects a released package depending on an ignored one, and it does not fire here since that check excludes devDependencies.

The divergence the union absorbed

Diffing the two judge blocks (fields lines 134-304 against app-shell lines 184-330 on the merge base) gives three real divergences; everything else is comment prose.

1. The SVG presentation list — ten entries the sweep had and the fields copy did not. This is the divergence the card was filed for: recharts markup the fields gate never renders.

-  'dominant-baseline', 'font-size', 'font-family', 'font-weight', 'vector-effect',
-  'shape-rendering', 'focusable', 'overflow', 'color',
+  'dominant-baseline', 'font-size', 'font-family', 'font-weight',
+  'vector-effect', 'shape-rendering', 'focusable', 'overflow', 'color',
+  'orient', 'refx', 'refy', 'markerwidth', 'markerheight', 'markerunits',
+  'patterncontentunits', 'spreadmethod', 'gradientscale', 'pathlength',

Union = the sweep's superset. Kept as a labelled second block, because the two halves have different producers (lucide icons vs. recharts) and will drift on different upgrades.

2. findLeaks truncated its records differently.

-        value: attribute.value,                          // fields: unbounded
-        outerHTML: element.outerHTML.slice(0, 400),
+        value: attribute.value.slice(0, 80),             // sweep: bounded
+        outerHTML: element.outerHTML.slice(0, 300),

Neither bound is load-bearing — both gates assert on attribute names, so these only shape the failure message. The union takes the defensive answer on each axis independently: the value is bounded at 80 (a leaked value can be an arbitrarily long serialized object), and the markup keeps the wider of the two bounds at 400 (the markup is what tells you which element in a 46-widget sweep produced the finding). Both are named constants with that reasoning next to them.

3. leakReport had two signatures.

-function leakReport(widgetType: string, variant: string, leaks: Leak[]): string   // fields
-function leakReport(target: string, leaks: Leak[]): string                        // sweep
+export function leakReport(label: string, leaks: readonly Leak[]): string

Unified on one label. The fields gate passes `field:${type} [${variant}]`, so both gates emit byte-identical failure text to what they emitted before — the message format was not changed, only where the label is assembled.

Everything else was identical in content: OPEN_PREFIXES, GLOBAL_HTML_ATTRIBUTES, ATTRIBUTE_TO_IDL_ALIAS, HAPPY_DOM_IDL_GAPS (all five entries), idlPropertiesFor and isKnownAttribute — the last two byte-for-byte.

The calibration fixtures were also unioned, and this is where the extraction paid for itself immediately: the fields fixture was the superset of markup, the sweep fixture planted the SDUI injection names, and the ten recharts SVG attributes above had a clean-markup fixture behind them in neither file. The merged CLEAN fixture now carries a defs block exercising all ten; the merged PLANTED list is 21 attributes (15 from fields, 13 from the sweep, 7 shared), with the fields copy's exact-count assertion kept.

Red-first

The calibration fixtures are the mechanism that catches happy-dom / library drift, so they have to be shown failing. Both directions were run by deleting one entry from the shared judge and restoring it byte-identically afterwards (verified by diff -q against a pre-edit copy; no git stash involved).

Direction 1 — delete the select[size] happy-dom IDL gap:

AssertionError: expected '< select> size="2"' to be '' // Object.is equality
- Expected
+ Received
+ < select> size="2"

 Test Files  1 failed (1)
      Tests  1 failed | 2 passed (3)

Direction 2 — delete markerwidth, i.e. one of the ten union-only recharts entries (the half that had no fixture behind it before this PR). Two cases go red, and the second one names the mechanism rather than the symptom:

× reports NOTHING on standard markup — no false positives
    AssertionError: expected '< marker> markerWidth="10"' to be ''
× judges SVG by its own list, because reflection does not hold there
    AssertionError: expected false to be true
      at expect(isKnownAttribute(svg, 'markerWidth')).toBe(true);

(The two angle brackets above carry a space so GitHub's body sanitizer does not eat them as HTML tags; the real assertion text has none.)

Both gates green through the shared import, full runs, repo-root vitest:

packages/fields/src/__tests__/widget-dom-leak-e2e.test.tsx     231 passed
packages/app-shell/src/__tests__/widget-dom-leak-sweep.test.tsx  37 passed
packages/test-support/src/__tests__/dom-leak-judge.test.tsx       3 passed

No judge logic left inline. Grepping both gate files for isKnownAttribute|idlPropertiesFor|HAPPY_DOM_IDL_GAPS|SVG_ATTRIBUTES|OPEN_PREFIXES|GLOBAL_HTML_ATTRIBUTES|ATTRIBUTE_TO_IDL_ALIAS|function findLeaks|function leakReport|interface Leak returns only docblock prose describing where the judge went — zero declarations. The sweep file keeps leakedAttributeNames, which was never duplicated: it is the ledger's unit, and it stays with the ledger.

Verification

command result
pnpm --filter '@object-ui/fields^...' --filter '@object-ui/app-shell^...' build dependency closure built first, clean
pnpm exec vitest run packages/test-support/ packages/fields/ packages/app-shell/ --maxWorkers=2 445 files, 4795 passed, 1 skipped, 0 failed
pnpm run type-check in test-support / fields / app-shell all green, including app-shell's second project tsc -p tsconfig.test.json
pnpm run lint in the same three 0 errors (warnings are the repo-wide pre-existing no-explicit-any set)
node scripts/check-control-bytes.mjs OK, 4196 tracked text files
check-type-check-coverage / check-lint-coverage / check-changeset-fixed / check:phantom-deps / check-changeset-no-major / check-doc-links all green
pnpm exec vitest run scripts/ (after the QUICK_REFERENCE.md fix below) 40 files, 916 passed
CI on d006a001b converged, 0 failures — Type Check, Lint, all four test shards, Build & E2E, Control Byte Scan and both changeset jobs green

One more piece of home wiring, found by CI

scripts/__tests__/quick-reference-commands-4149.test.ts requires QUICK_REFERENCE.md's packages/* row to name every private package under packages/, so a published count smaller than the directory count is explained rather than "corrected" back to the directory count — which is how objectui#4149 happened. packages/test-support makes it the second such package, so the row now names it too. The published count is unchanged at 38; the new package is private: true, which is exactly the property that ratchet is measuring.

Second commit, one CI lap: the local run was scoped to the three affected packages, and this gate lives in scripts/.

Surface

Touched: the two gate test files (import swap + the moved calibration block), the new packages/test-support, its home wiring (.changeset/config.json, vitest.config.mts, QUICK_REFERENCE.md, the two consumers' devDependencies, pnpm-lock.yaml), and one changeset.

Not touched: ledger rows, canary sets, target sets, MetricWidget / schemaHostProps, any renderer source, console/**, plugin-calendar source, chart files, content/docs/releases/.

Note on the changeset

The card expected "test-only, so none owed". scripts/check-changeset-presence.mjs disagrees and it is right: it deliberately has no carve-out for test files under src/, so editing the two gate files under released packages demands a declaration. The answer it names — and calls "a pass, not a workaround" — is an empty-frontmatter changeset declaring that this releases nothing, which is what .changeset/shared-dom-leak-judge.md is. The gate now reports: "Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption and a complete answer to this gate."

No skip-changeset label was applied, because in this repo it does not exist: objectui#3724 deleted a doc page that described exactly that mechanism, recording that "neither the workflow nor the label was ever real". The empty-frontmatter changeset is this repo's mechanism.


Generated by Claude Code

The "is this attribute HTML-defined" judge existed in two copies, inline in
two test files that cannot import each other, and the copies had already
diverged. Extract it once into a new private, never-published
`@object-ui/test-support` package, carrying the union of what both copies
knew, with the calibration fixtures moved next to it so it proves itself
once instead of once per copy.

Only the judge unifies: each gate keeps its own canary sets, targets,
readiness selectors, ledger and assertions.

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

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

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

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

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation configuration dependencies tests labels Aug 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 24.7 KB 350 KB
Entry file index-w93gDEZF.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 9.56KB 3.59KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 8.92KB 3.41KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 36.76KB 9.60KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 489.32KB 108.45KB
core (index.js) 3.37KB 1.34KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 153.79KB 41.35KB
fields (index.js) 230.07KB 57.07KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 3.35KB 1.38KB
i18n (pickLocalized.js) 3.69KB 1.73KB
i18n (provider.js) 23.12KB 7.62KB
i18n (useDisplayLocale.js) 2.33KB 1.20KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.98KB 10.85KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.13KB 12.76KB
plugin-charts (index.js) 62.01KB 17.63KB
plugin-chatbot (index.js) 181.21KB 43.14KB
plugin-dashboard (index.js) 120.85KB 31.41KB
plugin-designer (index.js) 212.58KB 42.83KB
plugin-detail (index.js) 239.03KB 59.77KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 114.58KB 27.68KB
plugin-gantt (index.js) 164.14KB 39.98KB
plugin-grid (index.js) 188.13KB 50.00KB
plugin-kanban (index.js) 48.62KB 13.42KB
plugin-list (index.js) 111.07KB 27.08KB
plugin-map (index.js) 18.16KB 5.81KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 41.16KB 10.96KB
plugin-timeline (index.js) 26.21KB 7.52KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.08KB 20.55KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 23.73KB 7.96KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.23KB 0.66KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 3.05KB 1.52KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

`scripts/__tests__/quick-reference-commands-4149.test.ts` requires the
`packages/*` row to name every private package under `packages/`, so that a
published count smaller than the directory count is explained rather than
"corrected" back (objectui#4149). Adding `packages/test-support` makes it the
second one. The published count is unchanged at 38 — the new package is
`private: true`.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 24.7 KB 350 KB
Entry file index-BFkvLfqm.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 9.56KB 3.59KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 8.92KB 3.41KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 36.76KB 9.60KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 489.32KB 108.45KB
core (index.js) 3.37KB 1.34KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 153.79KB 41.35KB
fields (index.js) 230.07KB 57.07KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 3.35KB 1.38KB
i18n (pickLocalized.js) 3.69KB 1.73KB
i18n (provider.js) 23.12KB 7.62KB
i18n (useDisplayLocale.js) 2.33KB 1.20KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.98KB 10.85KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.13KB 12.76KB
plugin-charts (index.js) 62.07KB 17.65KB
plugin-chatbot (index.js) 181.21KB 43.14KB
plugin-dashboard (index.js) 120.85KB 31.41KB
plugin-designer (index.js) 212.58KB 42.83KB
plugin-detail (index.js) 239.03KB 59.77KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 114.58KB 27.68KB
plugin-gantt (index.js) 164.14KB 39.98KB
plugin-grid (index.js) 188.13KB 50.00KB
plugin-kanban (index.js) 48.62KB 13.42KB
plugin-list (index.js) 111.07KB 27.08KB
plugin-map (index.js) 18.16KB 5.81KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 41.16KB 10.96KB
plugin-timeline (index.js) 26.21KB 7.52KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 84.08KB 20.55KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 23.73KB 7.96KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.23KB 0.66KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 3.05KB 1.52KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

Copy link
Copy Markdown
Collaborator Author

ACCEPT — step-7 复核 by PM session session_017Qqyix2QcnpUC9XeYVDzx3 (focused review).

  • Home decision correctly derived from the constraints, not taste: no existing cross-package test-support convention (measured — zero test subpath exports, zero cross-package test imports), and constraint (b) eliminates the core conditional-export shape (a published exports-map entry is public API whatever it's named). The private workspace package satisfies all three, with resolution PROVEN for both toolchains (tsc --listFiles + the vitest alias resolving the same file) — the @object-ui/fields deep subpaths resolve only through the repo vitest alias — its exports map publishes none of them #4325 vacuity lesson applied in advance.
  • The divergence diff is the deliverable: three real divergences (ten sweep-only SVG entries, truncation bounds, two leakReport signatures) unioned with per-axis reasoning, and the union EXPOSED a calibration hole — the ten recharts entries had a clean-markup fixture in NEITHER copy; the merged fixture closes it. Red-first in both directions including the union-only half, restore verified byte-identical.
  • Both dispatch contradictions correctly resolved against the repo's own record: the empty-frontmatter changeset is the gate's named exemption (my "none owed" was the objectstack spelling), and the skip-changeset label is a phantom this repo's own pin test documents as never-real — not applying it was right, and the standing dev clauses are corrected on this point.
  • The one red lap was read before being treated: a REAL ratchet (QUICK_REFERENCE's private-package row gaining its second entry), fixed in-task — not waved off as flake. 271 judge-adjacent tests green through the shared import; inline-judge grep clean; the finding(plugin-dashboard): the #3291 toDomProps whitelist stops at packages/fields — SDUI widgets elsewhere still close their DOM leak by hand, if at all #4425 metric rows untouched and correctly noted as the migration's remaining open item.
  • CI 18/18 green, mergeable clean.

Flipping ready + arming auto-merge. The DOM-leak infrastructure is now: one judge, one home, two gates consuming it — #4425's completion (metric rows + contract docs) is the family's last card.


Generated by Claude Code


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 12, 2026 22:06
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 12, 2026
Merged via the queue into main with commit 031bde7 Aug 12, 2026
21 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-4434-shared-leak-judge branch August 12, 2026 22:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

configuration dependencies documentation Improvements or additions to documentation tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding: the DOM-leak "is this attribute HTML-defined" judge now exists in two copies, in two test files that cannot import each other

2 participants