fix(fields): exclude tooling DIRECTORIES from the emitting program, not just the *.test.* name - #7213

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-6943-fields-tooling-in-dist
Sep 1, 2026
Merged

fix(fields): exclude tooling DIRECTORIES from the emitting program, not just the *.test.* name#7213
os-warren merged 1 commit into
mainfrom
claude/issue-6943-fields-tooling-in-dist

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#6943

@object-ui/fields shipped dist/__tests__/numberInputBrowserReadings.d.ts inside its published tarball. That made check:published-dist red on main since 2026-08-31, and because the same script is the first link in changeset:publish, it also failed the publish command at its first step — surfacing only to the one actor who cannot hand it off.

Which program emits dist — measured, not assumed

This is the question the card was really buying, because packages/fields/tsconfig.json carries a comment warning that tsc there only CHECKS. Editing its exclude could have changed the check program and left the emit untouched: a fix that looks right, reviews clean, and leaves the gate red.

So both legs of build (tsc && vite build && node scripts/build-css.mjs) were run in isolation against a cleaned dist:

legexitfiles written to dist
pnpm exec tsc alone00 — wrote nothing
pnpm exec vite build alone081, including dist/__tests__/numberInputBrowserReadings.d.ts

vite-plugin-dts is the emitting program. It builds its declaration program from this package's own tsconfig.json — the dts() call in vite.config.ts passes no exclude of its own — so the exclude belongs in that tsconfig, and not in the plugin options. The fix is therefore one file plus a changeset.

The change

packages/fields/tsconfig.json now excludes the tooling DIRECTORIES, matching the shape objectui#4836 landed in plugin-grid / plugin-view / plugin-designer:

"exclude": [
"node_modules", "dist",
"**/__tests__/**", "**/__mocks__/**", "**/__benchmarks__/**",
"**/*.test.ts", "**/*.test.tsx"
]

numberInputBrowserReadings.ts holds the measured Chromium/happy-dom readings the number-widget suites share. It carries no assertions, so it is deliberately not a *.test.ts, which is exactly why the name-only list let it through while its 79 neighbours in the same directory were caught.

The name patterns stay — 52 *.test.ts(x) files in this package sit outside any __tests__/ directory, so the directory patterns do not subsume them. __mocks__ and __benchmarks__ match nothing here today and are listed so the table is the directory convention itself, exactly as TOOLING_FILE in scripts/check-phantom-dependencies.mjs spells it, rather than a list to extend next time.

⛔ Not done: adding a fourth NAME pattern such as a BrowserReadings glob. That would have been the same mistake a fourth time.

Gate evidence — red first, then green

Exit codes captured before any pipe, on a full 43-task build. Verdict lines are the gate's own.

BEFORE (pnpm check:published-dist, exit 1):

Inspected 39 published package(s) after a 1s build: 5886 tarball file(s), 5730 of them build output, 1 tooling artifact(s) in build output.
❌ 1 finding(s) across 1 published package(s):
@object-ui/fields [tooling-in-published-output] packages/fields/dist/__tests__/numberInputBrowserReadings.d.ts

AFTER (exit 0):

Inspected 39 published package(s) after a 1s build: 5885 tarball file(s), 5729 of them build output, 0 tooling artifact(s) in build output.
✅ No published package's build output carries tooling material.

The counts moved by exactly one on each axis (5886 to 5885 tarball, 5730 to 5729 build output, 1 to 0 tooling), which is what shows the change reached the emit rather than merely the check. The narrower ablation says the same thing directly: rebuilding the vite build leg alone after the edit produced 80 files instead of 81, 0 tooling artifacts, with dist/index.d.ts still present as a live control that the exclude did not overshoot.

No second gate traded for the first

The likeliest way this repair goes wrong is trading one red gate for another, since scripts/check-type-check-coverage.mjs verifies that tests excluded from a build program are still checked somewhere.

node scripts/check-type-check-coverage.mjs exits 0 both before and after, and its two verdict lines are byte-identical across the change (diff of the two runs is empty):

✅ type-check coverage: 45/46 via `type-check`, 0 via their own build, 0 known-broken (0 errors outstanding), 1 not compiled.
✅ test type-check coverage: 41/41 packages compile their tests, 0 declared debt (0 errors outstanding), 0 with a narrow type-assertion project.

That holds because no coverage actually moves, measured with tsc --listFiles on both programs, each zero carrying a live control in the same query shape:

programbeforeaftercontrol
build (tsc --noEmit)reads the file0 — now excludedsrc/widgets/numberBadInput.tsx still read (1)
test (tsc -p tsconfig.test.json)reads the file1 — still readimporting suite still read (1)

numberInputBrowserReadings.ts is the only file the directory patterns newly remove from the build program, and tsconfig.test.json already reads it as a transitive input of the three suites that import it. So it needs no entry there — the same reason plugin-grid's explainDouble.ts needed none, and the reason the tsconfig.test.json half of the remedy (PR #4845's shape) is not owed on this card.

Everything else run

Union re-run on the final commit 81b778ce3, after the last commit rather than before it:

gateexit
check:published-dist0
check-type-check-coverage.mjs0
check-changeset-presence.mjs0
check-changeset-overwrite.mjs0 (1 added, 0 modified, 0 deleted)
check-changeset-fixed.mjs0
check-changeset-no-major.mjs0
check-control-bytes.mjs0 (5972 files)
check-dist-completeness.mjs --all0
check-phantom-dependencies.mjs0
check-package-self-import.mjs0
pnpm --filter @object-ui/fields type-check0
pnpm exec vitest run packages/fields/0 — 130 files, 2138 tests passed

The vitest run is from the repo ROOT deliberately: pnpm --filter @object-ui/fields test is refused by this repo's own guard (objectui#3378), which exists because running vitest from a package directory silently runs @object-ui/console's 22 files and reports them as green. The 130-file count is the control that this package's suites actually ran.

A changeset is included — a published package's tarball contents change.

Reported, deliberately not ridden

The card raised generalising this into a shared emitting-program exclude, and the dispatch ruled that out of scope. Swept it anyway and filed the reading as #7212: 29 published packages still exclude tooling by NAME only, all with zero offending files today, so each is one shared __tests__/ helper away from the same red. Three rounds of correct per-package repair have left the same trap armed everywhere else, which is the argument that the altitude is wrong — but a cross-package build-config change is its own card with its own review, and no part of it is in this PR.


Generated by Claude Code

…ot just the `*.test.*` name
`@object-ui/fields` shipped `dist/__tests__/numberInputBrowserReadings.d.ts`
inside its published tarball, which made `check:published-dist` red on `main`
and — since the same script is the first link in `changeset:publish` — failed
the publish command at its first step.
`numberInputBrowserReadings.ts` holds the measured Chromium/happy-dom readings
the number-widget suites share. It carries no assertions, so it is deliberately
not a `*.test.ts`, and the name-only exclude list did not catch it while its 79
neighbours in the same directory were kept out.
Which program had to change was measured, not assumed: this package builds with
`tsc && vite build` and the `tsc` leg inherits the root's `noEmit`. Run alone,
`tsc` exited 0 and wrote zero files; `vite build` alone produced the whole
81-file output including the offending declaration. vite-plugin-dts is the
emitting program and builds its declaration program from this package's
`tsconfig.json` (its `dts()` options pass no `exclude`), so the exclude belongs
there. After the change the same vite-only leg emits 80 files and zero tooling
artifacts, with `dist/index.d.ts` still present as a control.
Third instance of the same name-versus-directory mismatch (objectui#4006 here,
objectui#4836 in plugin-grid / plugin-view / plugin-designer), so the table is
now the directory convention itself rather than a list of names to extend.
`__mocks__` / `__benchmarks__` match nothing here today and are listed for that
reason. The name patterns stay: 52 `*.test.ts(x)` files in this package sit
outside any `__tests__/` directory.
No type coverage moves. `numberInputBrowserReadings.ts` is the only file the
directory patterns newly remove from the build program, and the
`tsconfig.test.json` chained off `type-check` already reads it as a transitive
input of the three suites that import it — measured with `--listFiles` before
and after, each zero carrying a live control.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": 6943,
"status": "done",
"branch": "claude/issue-6943-fields-tooling-in-dist",
"pr": "https://github.com/objectstack-ai/objectui/pull/7213",
"premise_still_valid": true,
"summary": "THE EMITTING PROGRAM IS vite-plugin-dts, and it builds its declaration program from packages/fields/tsconfig.json, so the fix went into that tsconfig's `exclude` and nowhere else. Measured rather than assumed, exactly as A2.1 demanded: against a cleaned dist, `pnpm exec tsc` alone exited 0 and wrote ZERO files (it inherits the root's noEmit, so the build's leading `tsc` only checks), while `pnpm exec vite build` alone produced the whole 81-file output including dist/__tests__/numberInputBrowserReadings.d.ts. The dts() call in vite.config.ts passes no `exclude` of its own, which is why the tsconfig's list is what reaches the emit -- and the ablation confirms it end to end rather than by reading: after the edit the same vite-only leg emits 80 files and 0 tooling artifacts. The exclude now names the tooling DIRECTORIES (__tests__, __mocks__, __benchmarks__), matching the shape #4836 landed in plugin-grid/plugin-view/plugin-designer; no fourth NAME pattern was added, no source file was deleted or renamed, and no declared type moved (Clause 2 stayed disengaged). The name patterns are kept because 52 *.test.ts(x) files in this package sit outside any __tests__/ directory. A2.2, A2.3, A2.4 all confirmed on head 56453410f: the exclude was name-only, rootDir '..' was left untouched, and the gate's full output names @object-ui/fields as the only affected package among all 39 published ones.",
"tests": "GATE UNION RE-RUN ON THE FINAL COMMIT 81b778ce3 (after the last commit, not before it). RED FIRST, exit codes captured before any pipe. BEFORE -- `pnpm check:published-dist` exit 1: 'Inspected 39 published package(s) after a 1s build: 5886 tarball file(s), 5730 of them build output, 1 tooling artifact(s) in build output.' + '1 finding(s) across 1 published package(s): @object-ui/fields [tooling-in-published-output] packages/fields/dist/__tests__/numberInputBrowserReadings.d.ts'. AFTER -- exit 0: 'Inspected 39 published package(s) after a 1s build: 5885 tarball file(s), 5729 of them build output, 0 tooling artifact(s) in build output.' + 'No published package's build output carries tooling material.' The counts moved by exactly one on each axis (5886/5885, 5730/5729, 1/0), which is the ablation the card asked for: the change reached the EMIT, not merely the check. Narrower ablation on the vite-only leg: 81 files -> 80, tooling artifacts 1 -> 0, with dist/index.d.ts still present as a live control that the exclude did not overshoot. SECOND GATE NOT TRADED: node scripts/check-type-check-coverage.mjs exits 0 before AND after, and diff of the two runs is EMPTY (byte-identical verdict lines: '45/46 via type-check ... 1 not compiled' and '41/41 packages compile their tests'). Coverage measured directly with tsc --listFiles on both programs, every zero carrying a live control in the same query shape: build program (tsc --noEmit) went 1 -> 0 for numberInputBrowserReadings while control src/widgets/numberBadInput.tsx stayed 1; test program (tsc -p tsconfig.test.json) stayed 1 -> 1 while its control (the importing suite) stayed 1. So no tsconfig.test.json entry is owed -- same reason plugin-grid's explainDouble.ts needed none, and PR #4845's half of the remedy is not engaged. FULL BUILD: turbo run build --filter=!@object-ui/site, 43/43 tasks successful, twice (before and after). PACKAGE SUITES: `pnpm exec vitest run packages/fields/` from the repo ROOT -- 130 test files, 2138 tests, all passed. NOTE: `pnpm --filter @object-ui/fields test` is REFUSED by this repo's own guard (objectui#3378) because package-directory vitest silently runs @object-ui/console's 22 files and reports 'Test Files 22 passed' -- a false green; the 130-file count is the control that this package's suites actually ran. `pnpm --filter @object-ui/fields type-check` exit 0 (script name echoed: 'tsc --noEmit && tsc -p tsconfig.test.json', so no zero-match). REST OF THE FAMILY, all exit 0: check-changeset-presence, check-changeset-overwrite (1 added, 0 modified, 0 deleted), check-changeset-fixed, check-changeset-no-major, check-control-bytes (5972 files), check-dist-completeness --all, check-phantom-dependencies, check-package-self-import. Gate family derived by hand from objectui's own package.json + .github/workflows (scripts/pm/dispatch-gates.mjs lives in objectstack and only answers about its own tree, so it was NOT used here). All heavy runs went through scripts/pm/os-verify-lock.sh.",
"mcp_calls": "9 -- issue_read get, issue_read get_comments, search_issues (1 targeted dedupe), issue_write create (#7212), create_pull_request, pull_request_read get (body readback), 2 report comments, 1 comment readback. REST channel probed first and it is 403 in this container with gh absent, so dedupe went through one targeted MCP search rather than REST + local grep -- channel switch declared. The dedupe result was NON-EMPTY (it returned #4006 and #4836, the two known prior instances), so it is self-validating and needed no separate control.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7212: 29 published packages still exclude tooling by NAME only (app-shell, auth, collaboration, create-plugin, i18n, layout, mobile, permissions, plugin-ai, plugin-calendar, plugin-chatbot, plugin-dashboard, plugin-detail, plugin-editor, plugin-form, plugin-gantt, plugin-kanban, plugin-list, plugin-map, plugin-markdown, plugin-report, plugin-timeline, plugin-tree, providers, react, react-runtime, runner, sdui-parser, types) -- all with ZERO offending files today, so each is one shared __tests__/ helper away from the identical red. This is A2.4's latent-instance question answered and A2.5's altitude question carried across, deliberately NOT ridden: the card body lays out three options (shared base config / static ratchet gate / stay per-package) without a recommendation, since the trade turns on how much emit-semantics modelling the repo wants to own and #4846 already ruled on that once in the other direction. Also recorded there: apps/console has NO exclude key at all and does carry src/__tests__/helpers/preview-page-sources.ts, protected only by its noEmit: true."
]
}

Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3157.9 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-CdYvWGSp.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.33KB5.59KB
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)514.13KB117.19KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
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)33.40KB8.71KB
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)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)207.22KB56.04KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.21KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
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.11KB1.48KB
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.72KB2.24KB
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)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM), reviewer of record

Verdict: ACCEPT. Arming on green. A red gate on main is closed.

⭐ A2.1 answered by measurement, and the answer is more interesting than either guess

I made "which program emits dist?" the first measurement because the package tsconfig inherits noEmit: true and its own comment says vite-plugin-dts writes dist — so editing the tsconfig's exclude risked changing the check program and leaving the gate red.

The answer is both:

the emitting program IS vite-plugin-dts, and it builds its declaration program from packages/fields/tsconfig.json — the dts() call in vite.config.ts passes no exclude of its own, so the tsconfig's list is what reaches the emit.

⇒ The tsconfig edit was correct after all, but for a reason that had to be established rather than assumed. And it was established the right way — against a cleaned dist, tsc alone exited 0 and wrote zero files, while vite build alone produced the whole 81-file output including the offender.

⭐ That is the value of putting a cheap decisive measurement first: had dts() carried its own exclude, the obvious fix would have been silently inert. The measurement cost one build and made the difference unfalsifiable either way.

The ablation proves the change reached the EMIT, not just the check

Red first, then green, with counts moving by exactly one on each axis:

beforeafter
tarball files58865885
build output57305729
tooling artifacts10

⭐ And the narrower leg carries a control against over-excluding: the vite-only run goes 81 → 80 files with tooling 1 → 0, while dist/index.d.ts is still present. A fix that excluded too much would also show "0 tooling artifacts" — the surviving entry point is what distinguishes a correct exclude from a broken emit.

The gate I was most worried about was checked both ways

My order flagged that "the likeliest way this fix goes wrong is trading one red gate for another." Closed properly:

  • check-type-check-coverage.mjs exits 0 before and after, and the diff of the two runs is empty — byte-identical verdict lines. A pass-after is not evidence on its own; an unchanged verdict is.
  • Coverage measured directly with tsc --listFiles on both programs, each zero carrying a live control in the same query shape: the build program went 1 → 0 for the offending file while control numberBadInput.tsx stayed 1; the test program stayed 1 → 1 with its own control unchanged.

⇒ No tsconfig.test.json entry is owed, and that is a measurement rather than an omission — PR #4845's half of the remedy is genuinely not engaged here.

The fix is the right shape

Tooling directories (__tests__, __mocks__, __benchmarks__), matching what #4836 landed in plugin-grid / plugin-view / plugin-designer. ⛔ No fourth name pattern, no file deleted or renamed, rootDir: ".." untouched.

⚠️ And the name patterns were kept, for a measured reason: 52 *.test.ts(x) files in this package sit outside any __tests__/ directory. Replacing rather than extending would have been a fourth instance of the same class in the opposite direction.

#7212 is the finding, and it reframes this card

29 published packages still exclude tooling by NAME only — all with zero offending files today, so each is one shared __tests__/ helper away from the identical red.

Plus: apps/console has no exclude key at all and does carry src/__tests__/helpers/preview-page-sources.ts, protected only by noEmit: true.

⇒ This card was not "a bug in @object-ui/fields." It was the third surfacing of a repo-wide latent condition, and the reason it keeps recurring is now measured rather than inferred. That answers A2.4's latent-instance question and carries A2.5's altitude question — deliberately not ridden, with three options and no recommendation, because #4846 already ruled on that trade once in the other direction. Declining to recommend against a standing ruling is the right restraint.

Also noted

The false-green trap recorded again with its mechanism: pnpm --filter @object-ui/fields test is refused by the repo guard because package-directory vitest silently runs @object-ui/console's 22 files and reports "Test Files 22 passed." The 130-file count from the root-relative run is the control that this package's suites actually ran. That is #7089 §3's warning, independently reproduced — and it is a false green, not merely a wrong invocation.

Gate family derived by hand from objectui's own package.json and workflows, with an explicit note that scripts/pm/dispatch-gates.mjs lives in objectstack and only answers about its own tree. Stating why a tool was not used beats silently not using it.


Nothing to change.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check:published-dist is RED on main: @object-ui/fields ships dist/__tests__/numberInputBrowserReadings.d.ts in its tarball

2 participants

@os-warren@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(fields): exclude tooling DIRECTORIES from the emitting program, not just the *.test.* name - #7213

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-6943-fields-tooling-in-dist
Sep 1, 2026
Merged

fix(fields): exclude tooling DIRECTORIES from the emitting program, not just the *.test.* name#7213
os-warren merged 1 commit into
mainfrom
claude/issue-6943-fields-tooling-in-dist

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#6943

@object-ui/fields shipped dist/__tests__/numberInputBrowserReadings.d.ts inside its published tarball. That made check:published-dist red on main since 2026-08-31, and because the same script is the first link in changeset:publish, it also failed the publish command at its first step — surfacing only to the one actor who cannot hand it off.

Which program emits dist — measured, not assumed

This is the question the card was really buying, because packages/fields/tsconfig.json carries a comment warning that tsc there only CHECKS. Editing its exclude could have changed the check program and left the emit untouched: a fix that looks right, reviews clean, and leaves the gate red.

So both legs of build (tsc && vite build && node scripts/build-css.mjs) were run in isolation against a cleaned dist:

legexitfiles written to dist
pnpm exec tsc alone00 — wrote nothing
pnpm exec vite build alone081, including dist/__tests__/numberInputBrowserReadings.d.ts

vite-plugin-dts is the emitting program. It builds its declaration program from this package's own tsconfig.json — the dts() call in vite.config.ts passes no exclude of its own — so the exclude belongs in that tsconfig, and not in the plugin options. The fix is therefore one file plus a changeset.

The change

packages/fields/tsconfig.json now excludes the tooling DIRECTORIES, matching the shape objectui#4836 landed in plugin-grid / plugin-view / plugin-designer:

"exclude": [
"node_modules", "dist",
"**/__tests__/**", "**/__mocks__/**", "**/__benchmarks__/**",
"**/*.test.ts", "**/*.test.tsx"
]

numberInputBrowserReadings.ts holds the measured Chromium/happy-dom readings the number-widget suites share. It carries no assertions, so it is deliberately not a *.test.ts, which is exactly why the name-only list let it through while its 79 neighbours in the same directory were caught.

The name patterns stay — 52 *.test.ts(x) files in this package sit outside any __tests__/ directory, so the directory patterns do not subsume them. __mocks__ and __benchmarks__ match nothing here today and are listed so the table is the directory convention itself, exactly as TOOLING_FILE in scripts/check-phantom-dependencies.mjs spells it, rather than a list to extend next time.

⛔ Not done: adding a fourth NAME pattern such as a BrowserReadings glob. That would have been the same mistake a fourth time.

Gate evidence — red first, then green

Exit codes captured before any pipe, on a full 43-task build. Verdict lines are the gate's own.

BEFORE (pnpm check:published-dist, exit 1):

Inspected 39 published package(s) after a 1s build: 5886 tarball file(s), 5730 of them build output, 1 tooling artifact(s) in build output.
❌ 1 finding(s) across 1 published package(s):
@object-ui/fields [tooling-in-published-output] packages/fields/dist/__tests__/numberInputBrowserReadings.d.ts

AFTER (exit 0):

Inspected 39 published package(s) after a 1s build: 5885 tarball file(s), 5729 of them build output, 0 tooling artifact(s) in build output.
✅ No published package's build output carries tooling material.

The counts moved by exactly one on each axis (5886 to 5885 tarball, 5730 to 5729 build output, 1 to 0 tooling), which is what shows the change reached the emit rather than merely the check. The narrower ablation says the same thing directly: rebuilding the vite build leg alone after the edit produced 80 files instead of 81, 0 tooling artifacts, with dist/index.d.ts still present as a live control that the exclude did not overshoot.

No second gate traded for the first

The likeliest way this repair goes wrong is trading one red gate for another, since scripts/check-type-check-coverage.mjs verifies that tests excluded from a build program are still checked somewhere.

node scripts/check-type-check-coverage.mjs exits 0 both before and after, and its two verdict lines are byte-identical across the change (diff of the two runs is empty):

✅ type-check coverage: 45/46 via `type-check`, 0 via their own build, 0 known-broken (0 errors outstanding), 1 not compiled.
✅ test type-check coverage: 41/41 packages compile their tests, 0 declared debt (0 errors outstanding), 0 with a narrow type-assertion project.

That holds because no coverage actually moves, measured with tsc --listFiles on both programs, each zero carrying a live control in the same query shape:

programbeforeaftercontrol
build (tsc --noEmit)reads the file0 — now excludedsrc/widgets/numberBadInput.tsx still read (1)
test (tsc -p tsconfig.test.json)reads the file1 — still readimporting suite still read (1)

numberInputBrowserReadings.ts is the only file the directory patterns newly remove from the build program, and tsconfig.test.json already reads it as a transitive input of the three suites that import it. So it needs no entry there — the same reason plugin-grid's explainDouble.ts needed none, and the reason the tsconfig.test.json half of the remedy (PR #4845's shape) is not owed on this card.

Everything else run

Union re-run on the final commit 81b778ce3, after the last commit rather than before it:

gateexit
check:published-dist0
check-type-check-coverage.mjs0
check-changeset-presence.mjs0
check-changeset-overwrite.mjs0 (1 added, 0 modified, 0 deleted)
check-changeset-fixed.mjs0
check-changeset-no-major.mjs0
check-control-bytes.mjs0 (5972 files)
check-dist-completeness.mjs --all0
check-phantom-dependencies.mjs0
check-package-self-import.mjs0
pnpm --filter @object-ui/fields type-check0
pnpm exec vitest run packages/fields/0 — 130 files, 2138 tests passed

The vitest run is from the repo ROOT deliberately: pnpm --filter @object-ui/fields test is refused by this repo's own guard (objectui#3378), which exists because running vitest from a package directory silently runs @object-ui/console's 22 files and reports them as green. The 130-file count is the control that this package's suites actually ran.

A changeset is included — a published package's tarball contents change.

Reported, deliberately not ridden

The card raised generalising this into a shared emitting-program exclude, and the dispatch ruled that out of scope. Swept it anyway and filed the reading as #7212: 29 published packages still exclude tooling by NAME only, all with zero offending files today, so each is one shared __tests__/ helper away from the same red. Three rounds of correct per-package repair have left the same trap armed everywhere else, which is the argument that the altitude is wrong — but a cross-package build-config change is its own card with its own review, and no part of it is in this PR.


Generated by Claude Code

…ot just the `*.test.*` name
`@object-ui/fields` shipped `dist/__tests__/numberInputBrowserReadings.d.ts`
inside its published tarball, which made `check:published-dist` red on `main`
and — since the same script is the first link in `changeset:publish` — failed
the publish command at its first step.
`numberInputBrowserReadings.ts` holds the measured Chromium/happy-dom readings
the number-widget suites share. It carries no assertions, so it is deliberately
not a `*.test.ts`, and the name-only exclude list did not catch it while its 79
neighbours in the same directory were kept out.
Which program had to change was measured, not assumed: this package builds with
`tsc && vite build` and the `tsc` leg inherits the root's `noEmit`. Run alone,
`tsc` exited 0 and wrote zero files; `vite build` alone produced the whole
81-file output including the offending declaration. vite-plugin-dts is the
emitting program and builds its declaration program from this package's
`tsconfig.json` (its `dts()` options pass no `exclude`), so the exclude belongs
there. After the change the same vite-only leg emits 80 files and zero tooling
artifacts, with `dist/index.d.ts` still present as a control.
Third instance of the same name-versus-directory mismatch (objectui#4006 here,
objectui#4836 in plugin-grid / plugin-view / plugin-designer), so the table is
now the directory convention itself rather than a list of names to extend.
`__mocks__` / `__benchmarks__` match nothing here today and are listed for that
reason. The name patterns stay: 52 `*.test.ts(x)` files in this package sit
outside any `__tests__/` directory.
No type coverage moves. `numberInputBrowserReadings.ts` is the only file the
directory patterns newly remove from the build program, and the
`tsconfig.test.json` chained off `type-check` already reads it as a transitive
input of the three suites that import it — measured with `--listFiles` before
and after, each zero carrying a live control.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": 6943,
"status": "done",
"branch": "claude/issue-6943-fields-tooling-in-dist",
"pr": "https://github.com/objectstack-ai/objectui/pull/7213",
"premise_still_valid": true,
"summary": "THE EMITTING PROGRAM IS vite-plugin-dts, and it builds its declaration program from packages/fields/tsconfig.json, so the fix went into that tsconfig's `exclude` and nowhere else. Measured rather than assumed, exactly as A2.1 demanded: against a cleaned dist, `pnpm exec tsc` alone exited 0 and wrote ZERO files (it inherits the root's noEmit, so the build's leading `tsc` only checks), while `pnpm exec vite build` alone produced the whole 81-file output including dist/__tests__/numberInputBrowserReadings.d.ts. The dts() call in vite.config.ts passes no `exclude` of its own, which is why the tsconfig's list is what reaches the emit -- and the ablation confirms it end to end rather than by reading: after the edit the same vite-only leg emits 80 files and 0 tooling artifacts. The exclude now names the tooling DIRECTORIES (__tests__, __mocks__, __benchmarks__), matching the shape #4836 landed in plugin-grid/plugin-view/plugin-designer; no fourth NAME pattern was added, no source file was deleted or renamed, and no declared type moved (Clause 2 stayed disengaged). The name patterns are kept because 52 *.test.ts(x) files in this package sit outside any __tests__/ directory. A2.2, A2.3, A2.4 all confirmed on head 56453410f: the exclude was name-only, rootDir '..' was left untouched, and the gate's full output names @object-ui/fields as the only affected package among all 39 published ones.",
"tests": "GATE UNION RE-RUN ON THE FINAL COMMIT 81b778ce3 (after the last commit, not before it). RED FIRST, exit codes captured before any pipe. BEFORE -- `pnpm check:published-dist` exit 1: 'Inspected 39 published package(s) after a 1s build: 5886 tarball file(s), 5730 of them build output, 1 tooling artifact(s) in build output.' + '1 finding(s) across 1 published package(s): @object-ui/fields [tooling-in-published-output] packages/fields/dist/__tests__/numberInputBrowserReadings.d.ts'. AFTER -- exit 0: 'Inspected 39 published package(s) after a 1s build: 5885 tarball file(s), 5729 of them build output, 0 tooling artifact(s) in build output.' + 'No published package's build output carries tooling material.' The counts moved by exactly one on each axis (5886/5885, 5730/5729, 1/0), which is the ablation the card asked for: the change reached the EMIT, not merely the check. Narrower ablation on the vite-only leg: 81 files -> 80, tooling artifacts 1 -> 0, with dist/index.d.ts still present as a live control that the exclude did not overshoot. SECOND GATE NOT TRADED: node scripts/check-type-check-coverage.mjs exits 0 before AND after, and diff of the two runs is EMPTY (byte-identical verdict lines: '45/46 via type-check ... 1 not compiled' and '41/41 packages compile their tests'). Coverage measured directly with tsc --listFiles on both programs, every zero carrying a live control in the same query shape: build program (tsc --noEmit) went 1 -> 0 for numberInputBrowserReadings while control src/widgets/numberBadInput.tsx stayed 1; test program (tsc -p tsconfig.test.json) stayed 1 -> 1 while its control (the importing suite) stayed 1. So no tsconfig.test.json entry is owed -- same reason plugin-grid's explainDouble.ts needed none, and PR #4845's half of the remedy is not engaged. FULL BUILD: turbo run build --filter=!@object-ui/site, 43/43 tasks successful, twice (before and after). PACKAGE SUITES: `pnpm exec vitest run packages/fields/` from the repo ROOT -- 130 test files, 2138 tests, all passed. NOTE: `pnpm --filter @object-ui/fields test` is REFUSED by this repo's own guard (objectui#3378) because package-directory vitest silently runs @object-ui/console's 22 files and reports 'Test Files 22 passed' -- a false green; the 130-file count is the control that this package's suites actually ran. `pnpm --filter @object-ui/fields type-check` exit 0 (script name echoed: 'tsc --noEmit && tsc -p tsconfig.test.json', so no zero-match). REST OF THE FAMILY, all exit 0: check-changeset-presence, check-changeset-overwrite (1 added, 0 modified, 0 deleted), check-changeset-fixed, check-changeset-no-major, check-control-bytes (5972 files), check-dist-completeness --all, check-phantom-dependencies, check-package-self-import. Gate family derived by hand from objectui's own package.json + .github/workflows (scripts/pm/dispatch-gates.mjs lives in objectstack and only answers about its own tree, so it was NOT used here). All heavy runs went through scripts/pm/os-verify-lock.sh.",
"mcp_calls": "9 -- issue_read get, issue_read get_comments, search_issues (1 targeted dedupe), issue_write create (#7212), create_pull_request, pull_request_read get (body readback), 2 report comments, 1 comment readback. REST channel probed first and it is 403 in this container with gh absent, so dedupe went through one targeted MCP search rather than REST + local grep -- channel switch declared. The dedupe result was NON-EMPTY (it returned #4006 and #4836, the two known prior instances), so it is self-validating and needed no separate control.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7212: 29 published packages still exclude tooling by NAME only (app-shell, auth, collaboration, create-plugin, i18n, layout, mobile, permissions, plugin-ai, plugin-calendar, plugin-chatbot, plugin-dashboard, plugin-detail, plugin-editor, plugin-form, plugin-gantt, plugin-kanban, plugin-list, plugin-map, plugin-markdown, plugin-report, plugin-timeline, plugin-tree, providers, react, react-runtime, runner, sdui-parser, types) -- all with ZERO offending files today, so each is one shared __tests__/ helper away from the identical red. This is A2.4's latent-instance question answered and A2.5's altitude question carried across, deliberately NOT ridden: the card body lays out three options (shared base config / static ratchet gate / stay per-package) without a recommendation, since the trade turns on how much emit-semantics modelling the repo wants to own and #4846 already ruled on that once in the other direction. Also recorded there: apps/console has NO exclude key at all and does carry src/__tests__/helpers/preview-page-sources.ts, protected only by its noEmit: true."
]
}

Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3157.9 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-CdYvWGSp.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.33KB5.59KB
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)514.13KB117.19KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
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)33.40KB8.71KB
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)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)207.22KB56.04KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.21KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
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.11KB1.48KB
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.72KB2.24KB
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)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM), reviewer of record

Verdict: ACCEPT. Arming on green. A red gate on main is closed.

⭐ A2.1 answered by measurement, and the answer is more interesting than either guess

I made "which program emits dist?" the first measurement because the package tsconfig inherits noEmit: true and its own comment says vite-plugin-dts writes dist — so editing the tsconfig's exclude risked changing the check program and leaving the gate red.

The answer is both:

the emitting program IS vite-plugin-dts, and it builds its declaration program from packages/fields/tsconfig.json — the dts() call in vite.config.ts passes no exclude of its own, so the tsconfig's list is what reaches the emit.

⇒ The tsconfig edit was correct after all, but for a reason that had to be established rather than assumed. And it was established the right way — against a cleaned dist, tsc alone exited 0 and wrote zero files, while vite build alone produced the whole 81-file output including the offender.

⭐ That is the value of putting a cheap decisive measurement first: had dts() carried its own exclude, the obvious fix would have been silently inert. The measurement cost one build and made the difference unfalsifiable either way.

The ablation proves the change reached the EMIT, not just the check

Red first, then green, with counts moving by exactly one on each axis:

beforeafter
tarball files58865885
build output57305729
tooling artifacts10

⭐ And the narrower leg carries a control against over-excluding: the vite-only run goes 81 → 80 files with tooling 1 → 0, while dist/index.d.ts is still present. A fix that excluded too much would also show "0 tooling artifacts" — the surviving entry point is what distinguishes a correct exclude from a broken emit.

The gate I was most worried about was checked both ways

My order flagged that "the likeliest way this fix goes wrong is trading one red gate for another." Closed properly:

  • check-type-check-coverage.mjs exits 0 before and after, and the diff of the two runs is empty — byte-identical verdict lines. A pass-after is not evidence on its own; an unchanged verdict is.
  • Coverage measured directly with tsc --listFiles on both programs, each zero carrying a live control in the same query shape: the build program went 1 → 0 for the offending file while control numberBadInput.tsx stayed 1; the test program stayed 1 → 1 with its own control unchanged.

⇒ No tsconfig.test.json entry is owed, and that is a measurement rather than an omission — PR #4845's half of the remedy is genuinely not engaged here.

The fix is the right shape

Tooling directories (__tests__, __mocks__, __benchmarks__), matching what #4836 landed in plugin-grid / plugin-view / plugin-designer. ⛔ No fourth name pattern, no file deleted or renamed, rootDir: ".." untouched.

⚠️ And the name patterns were kept, for a measured reason: 52 *.test.ts(x) files in this package sit outside any __tests__/ directory. Replacing rather than extending would have been a fourth instance of the same class in the opposite direction.

#7212 is the finding, and it reframes this card

29 published packages still exclude tooling by NAME only — all with zero offending files today, so each is one shared __tests__/ helper away from the identical red.

Plus: apps/console has no exclude key at all and does carry src/__tests__/helpers/preview-page-sources.ts, protected only by noEmit: true.

⇒ This card was not "a bug in @object-ui/fields." It was the third surfacing of a repo-wide latent condition, and the reason it keeps recurring is now measured rather than inferred. That answers A2.4's latent-instance question and carries A2.5's altitude question — deliberately not ridden, with three options and no recommendation, because #4846 already ruled on that trade once in the other direction. Declining to recommend against a standing ruling is the right restraint.

Also noted

The false-green trap recorded again with its mechanism: pnpm --filter @object-ui/fields test is refused by the repo guard because package-directory vitest silently runs @object-ui/console's 22 files and reports "Test Files 22 passed." The 130-file count from the root-relative run is the control that this package's suites actually ran. That is #7089 §3's warning, independently reproduced — and it is a false green, not merely a wrong invocation.

Gate family derived by hand from objectui's own package.json and workflows, with an explicit note that scripts/pm/dispatch-gates.mjs lives in objectstack and only answers about its own tree. Stating why a tool was not used beats silently not using it.


Nothing to change.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check:published-dist is RED on main: @object-ui/fields ships dist/__tests__/numberInputBrowserReadings.d.ts in its tarball

2 participants

@os-warren@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(fields): exclude tooling DIRECTORIES from the emitting program, not just the *.test.* name - #7213

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-6943-fields-tooling-in-dist
Sep 1, 2026
Merged

fix(fields): exclude tooling DIRECTORIES from the emitting program, not just the *.test.* name#7213
os-warren merged 1 commit into
mainfrom
claude/issue-6943-fields-tooling-in-dist

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#6943

@object-ui/fields shipped dist/__tests__/numberInputBrowserReadings.d.ts inside its published tarball. That made check:published-dist red on main since 2026-08-31, and because the same script is the first link in changeset:publish, it also failed the publish command at its first step — surfacing only to the one actor who cannot hand it off.

Which program emits dist — measured, not assumed

This is the question the card was really buying, because packages/fields/tsconfig.json carries a comment warning that tsc there only CHECKS. Editing its exclude could have changed the check program and left the emit untouched: a fix that looks right, reviews clean, and leaves the gate red.

So both legs of build (tsc && vite build && node scripts/build-css.mjs) were run in isolation against a cleaned dist:

legexitfiles written to dist
pnpm exec tsc alone00 — wrote nothing
pnpm exec vite build alone081, including dist/__tests__/numberInputBrowserReadings.d.ts

vite-plugin-dts is the emitting program. It builds its declaration program from this package's own tsconfig.json — the dts() call in vite.config.ts passes no exclude of its own — so the exclude belongs in that tsconfig, and not in the plugin options. The fix is therefore one file plus a changeset.

The change

packages/fields/tsconfig.json now excludes the tooling DIRECTORIES, matching the shape objectui#4836 landed in plugin-grid / plugin-view / plugin-designer:

"exclude": [
"node_modules", "dist",
"**/__tests__/**", "**/__mocks__/**", "**/__benchmarks__/**",
"**/*.test.ts", "**/*.test.tsx"
]

numberInputBrowserReadings.ts holds the measured Chromium/happy-dom readings the number-widget suites share. It carries no assertions, so it is deliberately not a *.test.ts, which is exactly why the name-only list let it through while its 79 neighbours in the same directory were caught.

The name patterns stay — 52 *.test.ts(x) files in this package sit outside any __tests__/ directory, so the directory patterns do not subsume them. __mocks__ and __benchmarks__ match nothing here today and are listed so the table is the directory convention itself, exactly as TOOLING_FILE in scripts/check-phantom-dependencies.mjs spells it, rather than a list to extend next time.

⛔ Not done: adding a fourth NAME pattern such as a BrowserReadings glob. That would have been the same mistake a fourth time.

Gate evidence — red first, then green

Exit codes captured before any pipe, on a full 43-task build. Verdict lines are the gate's own.

BEFORE (pnpm check:published-dist, exit 1):

Inspected 39 published package(s) after a 1s build: 5886 tarball file(s), 5730 of them build output, 1 tooling artifact(s) in build output.
❌ 1 finding(s) across 1 published package(s):
@object-ui/fields [tooling-in-published-output] packages/fields/dist/__tests__/numberInputBrowserReadings.d.ts

AFTER (exit 0):

Inspected 39 published package(s) after a 1s build: 5885 tarball file(s), 5729 of them build output, 0 tooling artifact(s) in build output.
✅ No published package's build output carries tooling material.

The counts moved by exactly one on each axis (5886 to 5885 tarball, 5730 to 5729 build output, 1 to 0 tooling), which is what shows the change reached the emit rather than merely the check. The narrower ablation says the same thing directly: rebuilding the vite build leg alone after the edit produced 80 files instead of 81, 0 tooling artifacts, with dist/index.d.ts still present as a live control that the exclude did not overshoot.

No second gate traded for the first

The likeliest way this repair goes wrong is trading one red gate for another, since scripts/check-type-check-coverage.mjs verifies that tests excluded from a build program are still checked somewhere.

node scripts/check-type-check-coverage.mjs exits 0 both before and after, and its two verdict lines are byte-identical across the change (diff of the two runs is empty):

✅ type-check coverage: 45/46 via `type-check`, 0 via their own build, 0 known-broken (0 errors outstanding), 1 not compiled.
✅ test type-check coverage: 41/41 packages compile their tests, 0 declared debt (0 errors outstanding), 0 with a narrow type-assertion project.

That holds because no coverage actually moves, measured with tsc --listFiles on both programs, each zero carrying a live control in the same query shape:

programbeforeaftercontrol
build (tsc --noEmit)reads the file0 — now excludedsrc/widgets/numberBadInput.tsx still read (1)
test (tsc -p tsconfig.test.json)reads the file1 — still readimporting suite still read (1)

numberInputBrowserReadings.ts is the only file the directory patterns newly remove from the build program, and tsconfig.test.json already reads it as a transitive input of the three suites that import it. So it needs no entry there — the same reason plugin-grid's explainDouble.ts needed none, and the reason the tsconfig.test.json half of the remedy (PR #4845's shape) is not owed on this card.

Everything else run

Union re-run on the final commit 81b778ce3, after the last commit rather than before it:

gateexit
check:published-dist0
check-type-check-coverage.mjs0
check-changeset-presence.mjs0
check-changeset-overwrite.mjs0 (1 added, 0 modified, 0 deleted)
check-changeset-fixed.mjs0
check-changeset-no-major.mjs0
check-control-bytes.mjs0 (5972 files)
check-dist-completeness.mjs --all0
check-phantom-dependencies.mjs0
check-package-self-import.mjs0
pnpm --filter @object-ui/fields type-check0
pnpm exec vitest run packages/fields/0 — 130 files, 2138 tests passed

The vitest run is from the repo ROOT deliberately: pnpm --filter @object-ui/fields test is refused by this repo's own guard (objectui#3378), which exists because running vitest from a package directory silently runs @object-ui/console's 22 files and reports them as green. The 130-file count is the control that this package's suites actually ran.

A changeset is included — a published package's tarball contents change.

Reported, deliberately not ridden

The card raised generalising this into a shared emitting-program exclude, and the dispatch ruled that out of scope. Swept it anyway and filed the reading as #7212: 29 published packages still exclude tooling by NAME only, all with zero offending files today, so each is one shared __tests__/ helper away from the same red. Three rounds of correct per-package repair have left the same trap armed everywhere else, which is the argument that the altitude is wrong — but a cross-package build-config change is its own card with its own review, and no part of it is in this PR.


Generated by Claude Code

…ot just the `*.test.*` name
`@object-ui/fields` shipped `dist/__tests__/numberInputBrowserReadings.d.ts`
inside its published tarball, which made `check:published-dist` red on `main`
and — since the same script is the first link in `changeset:publish` — failed
the publish command at its first step.
`numberInputBrowserReadings.ts` holds the measured Chromium/happy-dom readings
the number-widget suites share. It carries no assertions, so it is deliberately
not a `*.test.ts`, and the name-only exclude list did not catch it while its 79
neighbours in the same directory were kept out.
Which program had to change was measured, not assumed: this package builds with
`tsc && vite build` and the `tsc` leg inherits the root's `noEmit`. Run alone,
`tsc` exited 0 and wrote zero files; `vite build` alone produced the whole
81-file output including the offending declaration. vite-plugin-dts is the
emitting program and builds its declaration program from this package's
`tsconfig.json` (its `dts()` options pass no `exclude`), so the exclude belongs
there. After the change the same vite-only leg emits 80 files and zero tooling
artifacts, with `dist/index.d.ts` still present as a control.
Third instance of the same name-versus-directory mismatch (objectui#4006 here,
objectui#4836 in plugin-grid / plugin-view / plugin-designer), so the table is
now the directory convention itself rather than a list of names to extend.
`__mocks__` / `__benchmarks__` match nothing here today and are listed for that
reason. The name patterns stay: 52 `*.test.ts(x)` files in this package sit
outside any `__tests__/` directory.
No type coverage moves. `numberInputBrowserReadings.ts` is the only file the
directory patterns newly remove from the build program, and the
`tsconfig.test.json` chained off `type-check` already reads it as a transitive
input of the three suites that import it — measured with `--listFiles` before
and after, each zero carrying a live control.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": 6943,
"status": "done",
"branch": "claude/issue-6943-fields-tooling-in-dist",
"pr": "https://github.com/objectstack-ai/objectui/pull/7213",
"premise_still_valid": true,
"summary": "THE EMITTING PROGRAM IS vite-plugin-dts, and it builds its declaration program from packages/fields/tsconfig.json, so the fix went into that tsconfig's `exclude` and nowhere else. Measured rather than assumed, exactly as A2.1 demanded: against a cleaned dist, `pnpm exec tsc` alone exited 0 and wrote ZERO files (it inherits the root's noEmit, so the build's leading `tsc` only checks), while `pnpm exec vite build` alone produced the whole 81-file output including dist/__tests__/numberInputBrowserReadings.d.ts. The dts() call in vite.config.ts passes no `exclude` of its own, which is why the tsconfig's list is what reaches the emit -- and the ablation confirms it end to end rather than by reading: after the edit the same vite-only leg emits 80 files and 0 tooling artifacts. The exclude now names the tooling DIRECTORIES (__tests__, __mocks__, __benchmarks__), matching the shape #4836 landed in plugin-grid/plugin-view/plugin-designer; no fourth NAME pattern was added, no source file was deleted or renamed, and no declared type moved (Clause 2 stayed disengaged). The name patterns are kept because 52 *.test.ts(x) files in this package sit outside any __tests__/ directory. A2.2, A2.3, A2.4 all confirmed on head 56453410f: the exclude was name-only, rootDir '..' was left untouched, and the gate's full output names @object-ui/fields as the only affected package among all 39 published ones.",
"tests": "GATE UNION RE-RUN ON THE FINAL COMMIT 81b778ce3 (after the last commit, not before it). RED FIRST, exit codes captured before any pipe. BEFORE -- `pnpm check:published-dist` exit 1: 'Inspected 39 published package(s) after a 1s build: 5886 tarball file(s), 5730 of them build output, 1 tooling artifact(s) in build output.' + '1 finding(s) across 1 published package(s): @object-ui/fields [tooling-in-published-output] packages/fields/dist/__tests__/numberInputBrowserReadings.d.ts'. AFTER -- exit 0: 'Inspected 39 published package(s) after a 1s build: 5885 tarball file(s), 5729 of them build output, 0 tooling artifact(s) in build output.' + 'No published package's build output carries tooling material.' The counts moved by exactly one on each axis (5886/5885, 5730/5729, 1/0), which is the ablation the card asked for: the change reached the EMIT, not merely the check. Narrower ablation on the vite-only leg: 81 files -> 80, tooling artifacts 1 -> 0, with dist/index.d.ts still present as a live control that the exclude did not overshoot. SECOND GATE NOT TRADED: node scripts/check-type-check-coverage.mjs exits 0 before AND after, and diff of the two runs is EMPTY (byte-identical verdict lines: '45/46 via type-check ... 1 not compiled' and '41/41 packages compile their tests'). Coverage measured directly with tsc --listFiles on both programs, every zero carrying a live control in the same query shape: build program (tsc --noEmit) went 1 -> 0 for numberInputBrowserReadings while control src/widgets/numberBadInput.tsx stayed 1; test program (tsc -p tsconfig.test.json) stayed 1 -> 1 while its control (the importing suite) stayed 1. So no tsconfig.test.json entry is owed -- same reason plugin-grid's explainDouble.ts needed none, and PR #4845's half of the remedy is not engaged. FULL BUILD: turbo run build --filter=!@object-ui/site, 43/43 tasks successful, twice (before and after). PACKAGE SUITES: `pnpm exec vitest run packages/fields/` from the repo ROOT -- 130 test files, 2138 tests, all passed. NOTE: `pnpm --filter @object-ui/fields test` is REFUSED by this repo's own guard (objectui#3378) because package-directory vitest silently runs @object-ui/console's 22 files and reports 'Test Files 22 passed' -- a false green; the 130-file count is the control that this package's suites actually ran. `pnpm --filter @object-ui/fields type-check` exit 0 (script name echoed: 'tsc --noEmit && tsc -p tsconfig.test.json', so no zero-match). REST OF THE FAMILY, all exit 0: check-changeset-presence, check-changeset-overwrite (1 added, 0 modified, 0 deleted), check-changeset-fixed, check-changeset-no-major, check-control-bytes (5972 files), check-dist-completeness --all, check-phantom-dependencies, check-package-self-import. Gate family derived by hand from objectui's own package.json + .github/workflows (scripts/pm/dispatch-gates.mjs lives in objectstack and only answers about its own tree, so it was NOT used here). All heavy runs went through scripts/pm/os-verify-lock.sh.",
"mcp_calls": "9 -- issue_read get, issue_read get_comments, search_issues (1 targeted dedupe), issue_write create (#7212), create_pull_request, pull_request_read get (body readback), 2 report comments, 1 comment readback. REST channel probed first and it is 403 in this container with gh absent, so dedupe went through one targeted MCP search rather than REST + local grep -- channel switch declared. The dedupe result was NON-EMPTY (it returned #4006 and #4836, the two known prior instances), so it is self-validating and needed no separate control.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7212: 29 published packages still exclude tooling by NAME only (app-shell, auth, collaboration, create-plugin, i18n, layout, mobile, permissions, plugin-ai, plugin-calendar, plugin-chatbot, plugin-dashboard, plugin-detail, plugin-editor, plugin-form, plugin-gantt, plugin-kanban, plugin-list, plugin-map, plugin-markdown, plugin-report, plugin-timeline, plugin-tree, providers, react, react-runtime, runner, sdui-parser, types) -- all with ZERO offending files today, so each is one shared __tests__/ helper away from the identical red. This is A2.4's latent-instance question answered and A2.5's altitude question carried across, deliberately NOT ridden: the card body lays out three options (shared base config / static ratchet gate / stay per-package) without a recommendation, since the trade turns on how much emit-semantics modelling the repo wants to own and #4846 already ruled on that once in the other direction. Also recorded there: apps/console has NO exclude key at all and does carry src/__tests__/helpers/preview-page-sources.ts, protected only by its noEmit: true."
]
}

Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3157.9 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-CdYvWGSp.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.33KB5.59KB
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)514.13KB117.19KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
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)33.40KB8.71KB
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)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)207.22KB56.04KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.21KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
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.11KB1.48KB
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.72KB2.24KB
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)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM), reviewer of record

Verdict: ACCEPT. Arming on green. A red gate on main is closed.

⭐ A2.1 answered by measurement, and the answer is more interesting than either guess

I made "which program emits dist?" the first measurement because the package tsconfig inherits noEmit: true and its own comment says vite-plugin-dts writes dist — so editing the tsconfig's exclude risked changing the check program and leaving the gate red.

The answer is both:

the emitting program IS vite-plugin-dts, and it builds its declaration program from packages/fields/tsconfig.json — the dts() call in vite.config.ts passes no exclude of its own, so the tsconfig's list is what reaches the emit.

⇒ The tsconfig edit was correct after all, but for a reason that had to be established rather than assumed. And it was established the right way — against a cleaned dist, tsc alone exited 0 and wrote zero files, while vite build alone produced the whole 81-file output including the offender.

⭐ That is the value of putting a cheap decisive measurement first: had dts() carried its own exclude, the obvious fix would have been silently inert. The measurement cost one build and made the difference unfalsifiable either way.

The ablation proves the change reached the EMIT, not just the check

Red first, then green, with counts moving by exactly one on each axis:

beforeafter
tarball files58865885
build output57305729
tooling artifacts10

⭐ And the narrower leg carries a control against over-excluding: the vite-only run goes 81 → 80 files with tooling 1 → 0, while dist/index.d.ts is still present. A fix that excluded too much would also show "0 tooling artifacts" — the surviving entry point is what distinguishes a correct exclude from a broken emit.

The gate I was most worried about was checked both ways

My order flagged that "the likeliest way this fix goes wrong is trading one red gate for another." Closed properly:

  • check-type-check-coverage.mjs exits 0 before and after, and the diff of the two runs is empty — byte-identical verdict lines. A pass-after is not evidence on its own; an unchanged verdict is.
  • Coverage measured directly with tsc --listFiles on both programs, each zero carrying a live control in the same query shape: the build program went 1 → 0 for the offending file while control numberBadInput.tsx stayed 1; the test program stayed 1 → 1 with its own control unchanged.

⇒ No tsconfig.test.json entry is owed, and that is a measurement rather than an omission — PR #4845's half of the remedy is genuinely not engaged here.

The fix is the right shape

Tooling directories (__tests__, __mocks__, __benchmarks__), matching what #4836 landed in plugin-grid / plugin-view / plugin-designer. ⛔ No fourth name pattern, no file deleted or renamed, rootDir: ".." untouched.

⚠️ And the name patterns were kept, for a measured reason: 52 *.test.ts(x) files in this package sit outside any __tests__/ directory. Replacing rather than extending would have been a fourth instance of the same class in the opposite direction.

#7212 is the finding, and it reframes this card

29 published packages still exclude tooling by NAME only — all with zero offending files today, so each is one shared __tests__/ helper away from the identical red.

Plus: apps/console has no exclude key at all and does carry src/__tests__/helpers/preview-page-sources.ts, protected only by noEmit: true.

⇒ This card was not "a bug in @object-ui/fields." It was the third surfacing of a repo-wide latent condition, and the reason it keeps recurring is now measured rather than inferred. That answers A2.4's latent-instance question and carries A2.5's altitude question — deliberately not ridden, with three options and no recommendation, because #4846 already ruled on that trade once in the other direction. Declining to recommend against a standing ruling is the right restraint.

Also noted

The false-green trap recorded again with its mechanism: pnpm --filter @object-ui/fields test is refused by the repo guard because package-directory vitest silently runs @object-ui/console's 22 files and reports "Test Files 22 passed." The 130-file count from the root-relative run is the control that this package's suites actually ran. That is #7089 §3's warning, independently reproduced — and it is a false green, not merely a wrong invocation.

Gate family derived by hand from objectui's own package.json and workflows, with an explicit note that scripts/pm/dispatch-gates.mjs lives in objectstack and only answers about its own tree. Stating why a tool was not used beats silently not using it.


Nothing to change.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check:published-dist is RED on main: @object-ui/fields ships dist/__tests__/numberInputBrowserReadings.d.ts in its tarball

2 participants

@os-warren@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(fields): exclude tooling DIRECTORIES from the emitting program, not just the *.test.* name - #7213

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-6943-fields-tooling-in-dist
Sep 1, 2026
Merged

fix(fields): exclude tooling DIRECTORIES from the emitting program, not just the *.test.* name#7213
os-warren merged 1 commit into
mainfrom
claude/issue-6943-fields-tooling-in-dist

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#6943

@object-ui/fields shipped dist/__tests__/numberInputBrowserReadings.d.ts inside its published tarball. That made check:published-dist red on main since 2026-08-31, and because the same script is the first link in changeset:publish, it also failed the publish command at its first step — surfacing only to the one actor who cannot hand it off.

Which program emits dist — measured, not assumed

This is the question the card was really buying, because packages/fields/tsconfig.json carries a comment warning that tsc there only CHECKS. Editing its exclude could have changed the check program and left the emit untouched: a fix that looks right, reviews clean, and leaves the gate red.

So both legs of build (tsc && vite build && node scripts/build-css.mjs) were run in isolation against a cleaned dist:

legexitfiles written to dist
pnpm exec tsc alone00 — wrote nothing
pnpm exec vite build alone081, including dist/__tests__/numberInputBrowserReadings.d.ts

vite-plugin-dts is the emitting program. It builds its declaration program from this package's own tsconfig.json — the dts() call in vite.config.ts passes no exclude of its own — so the exclude belongs in that tsconfig, and not in the plugin options. The fix is therefore one file plus a changeset.

The change

packages/fields/tsconfig.json now excludes the tooling DIRECTORIES, matching the shape objectui#4836 landed in plugin-grid / plugin-view / plugin-designer:

"exclude": [
"node_modules", "dist",
"**/__tests__/**", "**/__mocks__/**", "**/__benchmarks__/**",
"**/*.test.ts", "**/*.test.tsx"
]

numberInputBrowserReadings.ts holds the measured Chromium/happy-dom readings the number-widget suites share. It carries no assertions, so it is deliberately not a *.test.ts, which is exactly why the name-only list let it through while its 79 neighbours in the same directory were caught.

The name patterns stay — 52 *.test.ts(x) files in this package sit outside any __tests__/ directory, so the directory patterns do not subsume them. __mocks__ and __benchmarks__ match nothing here today and are listed so the table is the directory convention itself, exactly as TOOLING_FILE in scripts/check-phantom-dependencies.mjs spells it, rather than a list to extend next time.

⛔ Not done: adding a fourth NAME pattern such as a BrowserReadings glob. That would have been the same mistake a fourth time.

Gate evidence — red first, then green

Exit codes captured before any pipe, on a full 43-task build. Verdict lines are the gate's own.

BEFORE (pnpm check:published-dist, exit 1):

Inspected 39 published package(s) after a 1s build: 5886 tarball file(s), 5730 of them build output, 1 tooling artifact(s) in build output.
❌ 1 finding(s) across 1 published package(s):
@object-ui/fields [tooling-in-published-output] packages/fields/dist/__tests__/numberInputBrowserReadings.d.ts

AFTER (exit 0):

Inspected 39 published package(s) after a 1s build: 5885 tarball file(s), 5729 of them build output, 0 tooling artifact(s) in build output.
✅ No published package's build output carries tooling material.

The counts moved by exactly one on each axis (5886 to 5885 tarball, 5730 to 5729 build output, 1 to 0 tooling), which is what shows the change reached the emit rather than merely the check. The narrower ablation says the same thing directly: rebuilding the vite build leg alone after the edit produced 80 files instead of 81, 0 tooling artifacts, with dist/index.d.ts still present as a live control that the exclude did not overshoot.

No second gate traded for the first

The likeliest way this repair goes wrong is trading one red gate for another, since scripts/check-type-check-coverage.mjs verifies that tests excluded from a build program are still checked somewhere.

node scripts/check-type-check-coverage.mjs exits 0 both before and after, and its two verdict lines are byte-identical across the change (diff of the two runs is empty):

✅ type-check coverage: 45/46 via `type-check`, 0 via their own build, 0 known-broken (0 errors outstanding), 1 not compiled.
✅ test type-check coverage: 41/41 packages compile their tests, 0 declared debt (0 errors outstanding), 0 with a narrow type-assertion project.

That holds because no coverage actually moves, measured with tsc --listFiles on both programs, each zero carrying a live control in the same query shape:

programbeforeaftercontrol
build (tsc --noEmit)reads the file0 — now excludedsrc/widgets/numberBadInput.tsx still read (1)
test (tsc -p tsconfig.test.json)reads the file1 — still readimporting suite still read (1)

numberInputBrowserReadings.ts is the only file the directory patterns newly remove from the build program, and tsconfig.test.json already reads it as a transitive input of the three suites that import it. So it needs no entry there — the same reason plugin-grid's explainDouble.ts needed none, and the reason the tsconfig.test.json half of the remedy (PR #4845's shape) is not owed on this card.

Everything else run

Union re-run on the final commit 81b778ce3, after the last commit rather than before it:

gateexit
check:published-dist0
check-type-check-coverage.mjs0
check-changeset-presence.mjs0
check-changeset-overwrite.mjs0 (1 added, 0 modified, 0 deleted)
check-changeset-fixed.mjs0
check-changeset-no-major.mjs0
check-control-bytes.mjs0 (5972 files)
check-dist-completeness.mjs --all0
check-phantom-dependencies.mjs0
check-package-self-import.mjs0
pnpm --filter @object-ui/fields type-check0
pnpm exec vitest run packages/fields/0 — 130 files, 2138 tests passed

The vitest run is from the repo ROOT deliberately: pnpm --filter @object-ui/fields test is refused by this repo's own guard (objectui#3378), which exists because running vitest from a package directory silently runs @object-ui/console's 22 files and reports them as green. The 130-file count is the control that this package's suites actually ran.

A changeset is included — a published package's tarball contents change.

Reported, deliberately not ridden

The card raised generalising this into a shared emitting-program exclude, and the dispatch ruled that out of scope. Swept it anyway and filed the reading as #7212: 29 published packages still exclude tooling by NAME only, all with zero offending files today, so each is one shared __tests__/ helper away from the same red. Three rounds of correct per-package repair have left the same trap armed everywhere else, which is the argument that the altitude is wrong — but a cross-package build-config change is its own card with its own review, and no part of it is in this PR.


Generated by Claude Code

…ot just the `*.test.*` name
`@object-ui/fields` shipped `dist/__tests__/numberInputBrowserReadings.d.ts`
inside its published tarball, which made `check:published-dist` red on `main`
and — since the same script is the first link in `changeset:publish` — failed
the publish command at its first step.
`numberInputBrowserReadings.ts` holds the measured Chromium/happy-dom readings
the number-widget suites share. It carries no assertions, so it is deliberately
not a `*.test.ts`, and the name-only exclude list did not catch it while its 79
neighbours in the same directory were kept out.
Which program had to change was measured, not assumed: this package builds with
`tsc && vite build` and the `tsc` leg inherits the root's `noEmit`. Run alone,
`tsc` exited 0 and wrote zero files; `vite build` alone produced the whole
81-file output including the offending declaration. vite-plugin-dts is the
emitting program and builds its declaration program from this package's
`tsconfig.json` (its `dts()` options pass no `exclude`), so the exclude belongs
there. After the change the same vite-only leg emits 80 files and zero tooling
artifacts, with `dist/index.d.ts` still present as a control.
Third instance of the same name-versus-directory mismatch (objectui#4006 here,
objectui#4836 in plugin-grid / plugin-view / plugin-designer), so the table is
now the directory convention itself rather than a list of names to extend.
`__mocks__` / `__benchmarks__` match nothing here today and are listed for that
reason. The name patterns stay: 52 `*.test.ts(x)` files in this package sit
outside any `__tests__/` directory.
No type coverage moves. `numberInputBrowserReadings.ts` is the only file the
directory patterns newly remove from the build program, and the
`tsconfig.test.json` chained off `type-check` already reads it as a transitive
input of the three suites that import it — measured with `--listFiles` before
and after, each zero carrying a live control.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": 6943,
"status": "done",
"branch": "claude/issue-6943-fields-tooling-in-dist",
"pr": "https://github.com/objectstack-ai/objectui/pull/7213",
"premise_still_valid": true,
"summary": "THE EMITTING PROGRAM IS vite-plugin-dts, and it builds its declaration program from packages/fields/tsconfig.json, so the fix went into that tsconfig's `exclude` and nowhere else. Measured rather than assumed, exactly as A2.1 demanded: against a cleaned dist, `pnpm exec tsc` alone exited 0 and wrote ZERO files (it inherits the root's noEmit, so the build's leading `tsc` only checks), while `pnpm exec vite build` alone produced the whole 81-file output including dist/__tests__/numberInputBrowserReadings.d.ts. The dts() call in vite.config.ts passes no `exclude` of its own, which is why the tsconfig's list is what reaches the emit -- and the ablation confirms it end to end rather than by reading: after the edit the same vite-only leg emits 80 files and 0 tooling artifacts. The exclude now names the tooling DIRECTORIES (__tests__, __mocks__, __benchmarks__), matching the shape #4836 landed in plugin-grid/plugin-view/plugin-designer; no fourth NAME pattern was added, no source file was deleted or renamed, and no declared type moved (Clause 2 stayed disengaged). The name patterns are kept because 52 *.test.ts(x) files in this package sit outside any __tests__/ directory. A2.2, A2.3, A2.4 all confirmed on head 56453410f: the exclude was name-only, rootDir '..' was left untouched, and the gate's full output names @object-ui/fields as the only affected package among all 39 published ones.",
"tests": "GATE UNION RE-RUN ON THE FINAL COMMIT 81b778ce3 (after the last commit, not before it). RED FIRST, exit codes captured before any pipe. BEFORE -- `pnpm check:published-dist` exit 1: 'Inspected 39 published package(s) after a 1s build: 5886 tarball file(s), 5730 of them build output, 1 tooling artifact(s) in build output.' + '1 finding(s) across 1 published package(s): @object-ui/fields [tooling-in-published-output] packages/fields/dist/__tests__/numberInputBrowserReadings.d.ts'. AFTER -- exit 0: 'Inspected 39 published package(s) after a 1s build: 5885 tarball file(s), 5729 of them build output, 0 tooling artifact(s) in build output.' + 'No published package's build output carries tooling material.' The counts moved by exactly one on each axis (5886/5885, 5730/5729, 1/0), which is the ablation the card asked for: the change reached the EMIT, not merely the check. Narrower ablation on the vite-only leg: 81 files -> 80, tooling artifacts 1 -> 0, with dist/index.d.ts still present as a live control that the exclude did not overshoot. SECOND GATE NOT TRADED: node scripts/check-type-check-coverage.mjs exits 0 before AND after, and diff of the two runs is EMPTY (byte-identical verdict lines: '45/46 via type-check ... 1 not compiled' and '41/41 packages compile their tests'). Coverage measured directly with tsc --listFiles on both programs, every zero carrying a live control in the same query shape: build program (tsc --noEmit) went 1 -> 0 for numberInputBrowserReadings while control src/widgets/numberBadInput.tsx stayed 1; test program (tsc -p tsconfig.test.json) stayed 1 -> 1 while its control (the importing suite) stayed 1. So no tsconfig.test.json entry is owed -- same reason plugin-grid's explainDouble.ts needed none, and PR #4845's half of the remedy is not engaged. FULL BUILD: turbo run build --filter=!@object-ui/site, 43/43 tasks successful, twice (before and after). PACKAGE SUITES: `pnpm exec vitest run packages/fields/` from the repo ROOT -- 130 test files, 2138 tests, all passed. NOTE: `pnpm --filter @object-ui/fields test` is REFUSED by this repo's own guard (objectui#3378) because package-directory vitest silently runs @object-ui/console's 22 files and reports 'Test Files 22 passed' -- a false green; the 130-file count is the control that this package's suites actually ran. `pnpm --filter @object-ui/fields type-check` exit 0 (script name echoed: 'tsc --noEmit && tsc -p tsconfig.test.json', so no zero-match). REST OF THE FAMILY, all exit 0: check-changeset-presence, check-changeset-overwrite (1 added, 0 modified, 0 deleted), check-changeset-fixed, check-changeset-no-major, check-control-bytes (5972 files), check-dist-completeness --all, check-phantom-dependencies, check-package-self-import. Gate family derived by hand from objectui's own package.json + .github/workflows (scripts/pm/dispatch-gates.mjs lives in objectstack and only answers about its own tree, so it was NOT used here). All heavy runs went through scripts/pm/os-verify-lock.sh.",
"mcp_calls": "9 -- issue_read get, issue_read get_comments, search_issues (1 targeted dedupe), issue_write create (#7212), create_pull_request, pull_request_read get (body readback), 2 report comments, 1 comment readback. REST channel probed first and it is 403 in this container with gh absent, so dedupe went through one targeted MCP search rather than REST + local grep -- channel switch declared. The dedupe result was NON-EMPTY (it returned #4006 and #4836, the two known prior instances), so it is self-validating and needed no separate control.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7212: 29 published packages still exclude tooling by NAME only (app-shell, auth, collaboration, create-plugin, i18n, layout, mobile, permissions, plugin-ai, plugin-calendar, plugin-chatbot, plugin-dashboard, plugin-detail, plugin-editor, plugin-form, plugin-gantt, plugin-kanban, plugin-list, plugin-map, plugin-markdown, plugin-report, plugin-timeline, plugin-tree, providers, react, react-runtime, runner, sdui-parser, types) -- all with ZERO offending files today, so each is one shared __tests__/ helper away from the identical red. This is A2.4's latent-instance question answered and A2.5's altitude question carried across, deliberately NOT ridden: the card body lays out three options (shared base config / static ratchet gate / stay per-package) without a recommendation, since the trade turns on how much emit-semantics modelling the repo wants to own and #4846 already ruled on that once in the other direction. Also recorded there: apps/console has NO exclude key at all and does carry src/__tests__/helpers/preview-page-sources.ts, protected only by its noEmit: true."
]
}

Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3157.9 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-CdYvWGSp.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.33KB5.59KB
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)514.13KB117.19KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
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)33.40KB8.71KB
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)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)207.22KB56.04KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.21KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
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.11KB1.48KB
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.72KB2.24KB
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)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM), reviewer of record

Verdict: ACCEPT. Arming on green. A red gate on main is closed.

⭐ A2.1 answered by measurement, and the answer is more interesting than either guess

I made "which program emits dist?" the first measurement because the package tsconfig inherits noEmit: true and its own comment says vite-plugin-dts writes dist — so editing the tsconfig's exclude risked changing the check program and leaving the gate red.

The answer is both:

the emitting program IS vite-plugin-dts, and it builds its declaration program from packages/fields/tsconfig.json — the dts() call in vite.config.ts passes no exclude of its own, so the tsconfig's list is what reaches the emit.

⇒ The tsconfig edit was correct after all, but for a reason that had to be established rather than assumed. And it was established the right way — against a cleaned dist, tsc alone exited 0 and wrote zero files, while vite build alone produced the whole 81-file output including the offender.

⭐ That is the value of putting a cheap decisive measurement first: had dts() carried its own exclude, the obvious fix would have been silently inert. The measurement cost one build and made the difference unfalsifiable either way.

The ablation proves the change reached the EMIT, not just the check

Red first, then green, with counts moving by exactly one on each axis:

beforeafter
tarball files58865885
build output57305729
tooling artifacts10

⭐ And the narrower leg carries a control against over-excluding: the vite-only run goes 81 → 80 files with tooling 1 → 0, while dist/index.d.ts is still present. A fix that excluded too much would also show "0 tooling artifacts" — the surviving entry point is what distinguishes a correct exclude from a broken emit.

The gate I was most worried about was checked both ways

My order flagged that "the likeliest way this fix goes wrong is trading one red gate for another." Closed properly:

  • check-type-check-coverage.mjs exits 0 before and after, and the diff of the two runs is empty — byte-identical verdict lines. A pass-after is not evidence on its own; an unchanged verdict is.
  • Coverage measured directly with tsc --listFiles on both programs, each zero carrying a live control in the same query shape: the build program went 1 → 0 for the offending file while control numberBadInput.tsx stayed 1; the test program stayed 1 → 1 with its own control unchanged.

⇒ No tsconfig.test.json entry is owed, and that is a measurement rather than an omission — PR #4845's half of the remedy is genuinely not engaged here.

The fix is the right shape

Tooling directories (__tests__, __mocks__, __benchmarks__), matching what #4836 landed in plugin-grid / plugin-view / plugin-designer. ⛔ No fourth name pattern, no file deleted or renamed, rootDir: ".." untouched.

⚠️ And the name patterns were kept, for a measured reason: 52 *.test.ts(x) files in this package sit outside any __tests__/ directory. Replacing rather than extending would have been a fourth instance of the same class in the opposite direction.

#7212 is the finding, and it reframes this card

29 published packages still exclude tooling by NAME only — all with zero offending files today, so each is one shared __tests__/ helper away from the identical red.

Plus: apps/console has no exclude key at all and does carry src/__tests__/helpers/preview-page-sources.ts, protected only by noEmit: true.

⇒ This card was not "a bug in @object-ui/fields." It was the third surfacing of a repo-wide latent condition, and the reason it keeps recurring is now measured rather than inferred. That answers A2.4's latent-instance question and carries A2.5's altitude question — deliberately not ridden, with three options and no recommendation, because #4846 already ruled on that trade once in the other direction. Declining to recommend against a standing ruling is the right restraint.

Also noted

The false-green trap recorded again with its mechanism: pnpm --filter @object-ui/fields test is refused by the repo guard because package-directory vitest silently runs @object-ui/console's 22 files and reports "Test Files 22 passed." The 130-file count from the root-relative run is the control that this package's suites actually ran. That is #7089 §3's warning, independently reproduced — and it is a false green, not merely a wrong invocation.

Gate family derived by hand from objectui's own package.json and workflows, with an explicit note that scripts/pm/dispatch-gates.mjs lives in objectstack and only answers about its own tree. Stating why a tool was not used beats silently not using it.


Nothing to change.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check:published-dist is RED on main: @object-ui/fields ships dist/__tests__/numberInputBrowserReadings.d.ts in its tarball

2 participants

@os-warren@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(fields): exclude tooling DIRECTORIES from the emitting program, not just the *.test.* name - #7213

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-6943-fields-tooling-in-dist
Sep 1, 2026
Merged

fix(fields): exclude tooling DIRECTORIES from the emitting program, not just the *.test.* name#7213
os-warren merged 1 commit into
mainfrom
claude/issue-6943-fields-tooling-in-dist

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#6943

@object-ui/fields shipped dist/__tests__/numberInputBrowserReadings.d.ts inside its published tarball. That made check:published-dist red on main since 2026-08-31, and because the same script is the first link in changeset:publish, it also failed the publish command at its first step — surfacing only to the one actor who cannot hand it off.

Which program emits dist — measured, not assumed

This is the question the card was really buying, because packages/fields/tsconfig.json carries a comment warning that tsc there only CHECKS. Editing its exclude could have changed the check program and left the emit untouched: a fix that looks right, reviews clean, and leaves the gate red.

So both legs of build (tsc && vite build && node scripts/build-css.mjs) were run in isolation against a cleaned dist:

legexitfiles written to dist
pnpm exec tsc alone00 — wrote nothing
pnpm exec vite build alone081, including dist/__tests__/numberInputBrowserReadings.d.ts

vite-plugin-dts is the emitting program. It builds its declaration program from this package's own tsconfig.json — the dts() call in vite.config.ts passes no exclude of its own — so the exclude belongs in that tsconfig, and not in the plugin options. The fix is therefore one file plus a changeset.

The change

packages/fields/tsconfig.json now excludes the tooling DIRECTORIES, matching the shape objectui#4836 landed in plugin-grid / plugin-view / plugin-designer:

"exclude": [
"node_modules", "dist",
"**/__tests__/**", "**/__mocks__/**", "**/__benchmarks__/**",
"**/*.test.ts", "**/*.test.tsx"
]

numberInputBrowserReadings.ts holds the measured Chromium/happy-dom readings the number-widget suites share. It carries no assertions, so it is deliberately not a *.test.ts, which is exactly why the name-only list let it through while its 79 neighbours in the same directory were caught.

The name patterns stay — 52 *.test.ts(x) files in this package sit outside any __tests__/ directory, so the directory patterns do not subsume them. __mocks__ and __benchmarks__ match nothing here today and are listed so the table is the directory convention itself, exactly as TOOLING_FILE in scripts/check-phantom-dependencies.mjs spells it, rather than a list to extend next time.

⛔ Not done: adding a fourth NAME pattern such as a BrowserReadings glob. That would have been the same mistake a fourth time.

Gate evidence — red first, then green

Exit codes captured before any pipe, on a full 43-task build. Verdict lines are the gate's own.

BEFORE (pnpm check:published-dist, exit 1):

Inspected 39 published package(s) after a 1s build: 5886 tarball file(s), 5730 of them build output, 1 tooling artifact(s) in build output.
❌ 1 finding(s) across 1 published package(s):
@object-ui/fields [tooling-in-published-output] packages/fields/dist/__tests__/numberInputBrowserReadings.d.ts

AFTER (exit 0):

Inspected 39 published package(s) after a 1s build: 5885 tarball file(s), 5729 of them build output, 0 tooling artifact(s) in build output.
✅ No published package's build output carries tooling material.

The counts moved by exactly one on each axis (5886 to 5885 tarball, 5730 to 5729 build output, 1 to 0 tooling), which is what shows the change reached the emit rather than merely the check. The narrower ablation says the same thing directly: rebuilding the vite build leg alone after the edit produced 80 files instead of 81, 0 tooling artifacts, with dist/index.d.ts still present as a live control that the exclude did not overshoot.

No second gate traded for the first

The likeliest way this repair goes wrong is trading one red gate for another, since scripts/check-type-check-coverage.mjs verifies that tests excluded from a build program are still checked somewhere.

node scripts/check-type-check-coverage.mjs exits 0 both before and after, and its two verdict lines are byte-identical across the change (diff of the two runs is empty):

✅ type-check coverage: 45/46 via `type-check`, 0 via their own build, 0 known-broken (0 errors outstanding), 1 not compiled.
✅ test type-check coverage: 41/41 packages compile their tests, 0 declared debt (0 errors outstanding), 0 with a narrow type-assertion project.

That holds because no coverage actually moves, measured with tsc --listFiles on both programs, each zero carrying a live control in the same query shape:

programbeforeaftercontrol
build (tsc --noEmit)reads the file0 — now excludedsrc/widgets/numberBadInput.tsx still read (1)
test (tsc -p tsconfig.test.json)reads the file1 — still readimporting suite still read (1)

numberInputBrowserReadings.ts is the only file the directory patterns newly remove from the build program, and tsconfig.test.json already reads it as a transitive input of the three suites that import it. So it needs no entry there — the same reason plugin-grid's explainDouble.ts needed none, and the reason the tsconfig.test.json half of the remedy (PR #4845's shape) is not owed on this card.

Everything else run

Union re-run on the final commit 81b778ce3, after the last commit rather than before it:

gateexit
check:published-dist0
check-type-check-coverage.mjs0
check-changeset-presence.mjs0
check-changeset-overwrite.mjs0 (1 added, 0 modified, 0 deleted)
check-changeset-fixed.mjs0
check-changeset-no-major.mjs0
check-control-bytes.mjs0 (5972 files)
check-dist-completeness.mjs --all0
check-phantom-dependencies.mjs0
check-package-self-import.mjs0
pnpm --filter @object-ui/fields type-check0
pnpm exec vitest run packages/fields/0 — 130 files, 2138 tests passed

The vitest run is from the repo ROOT deliberately: pnpm --filter @object-ui/fields test is refused by this repo's own guard (objectui#3378), which exists because running vitest from a package directory silently runs @object-ui/console's 22 files and reports them as green. The 130-file count is the control that this package's suites actually ran.

A changeset is included — a published package's tarball contents change.

Reported, deliberately not ridden

The card raised generalising this into a shared emitting-program exclude, and the dispatch ruled that out of scope. Swept it anyway and filed the reading as #7212: 29 published packages still exclude tooling by NAME only, all with zero offending files today, so each is one shared __tests__/ helper away from the same red. Three rounds of correct per-package repair have left the same trap armed everywhere else, which is the argument that the altitude is wrong — but a cross-package build-config change is its own card with its own review, and no part of it is in this PR.


Generated by Claude Code

…ot just the `*.test.*` name
`@object-ui/fields` shipped `dist/__tests__/numberInputBrowserReadings.d.ts`
inside its published tarball, which made `check:published-dist` red on `main`
and — since the same script is the first link in `changeset:publish` — failed
the publish command at its first step.
`numberInputBrowserReadings.ts` holds the measured Chromium/happy-dom readings
the number-widget suites share. It carries no assertions, so it is deliberately
not a `*.test.ts`, and the name-only exclude list did not catch it while its 79
neighbours in the same directory were kept out.
Which program had to change was measured, not assumed: this package builds with
`tsc && vite build` and the `tsc` leg inherits the root's `noEmit`. Run alone,
`tsc` exited 0 and wrote zero files; `vite build` alone produced the whole
81-file output including the offending declaration. vite-plugin-dts is the
emitting program and builds its declaration program from this package's
`tsconfig.json` (its `dts()` options pass no `exclude`), so the exclude belongs
there. After the change the same vite-only leg emits 80 files and zero tooling
artifacts, with `dist/index.d.ts` still present as a control.
Third instance of the same name-versus-directory mismatch (objectui#4006 here,
objectui#4836 in plugin-grid / plugin-view / plugin-designer), so the table is
now the directory convention itself rather than a list of names to extend.
`__mocks__` / `__benchmarks__` match nothing here today and are listed for that
reason. The name patterns stay: 52 `*.test.ts(x)` files in this package sit
outside any `__tests__/` directory.
No type coverage moves. `numberInputBrowserReadings.ts` is the only file the
directory patterns newly remove from the build program, and the
`tsconfig.test.json` chained off `type-check` already reads it as a transitive
input of the three suites that import it — measured with `--listFiles` before
and after, each zero carrying a live control.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": 6943,
"status": "done",
"branch": "claude/issue-6943-fields-tooling-in-dist",
"pr": "https://github.com/objectstack-ai/objectui/pull/7213",
"premise_still_valid": true,
"summary": "THE EMITTING PROGRAM IS vite-plugin-dts, and it builds its declaration program from packages/fields/tsconfig.json, so the fix went into that tsconfig's `exclude` and nowhere else. Measured rather than assumed, exactly as A2.1 demanded: against a cleaned dist, `pnpm exec tsc` alone exited 0 and wrote ZERO files (it inherits the root's noEmit, so the build's leading `tsc` only checks), while `pnpm exec vite build` alone produced the whole 81-file output including dist/__tests__/numberInputBrowserReadings.d.ts. The dts() call in vite.config.ts passes no `exclude` of its own, which is why the tsconfig's list is what reaches the emit -- and the ablation confirms it end to end rather than by reading: after the edit the same vite-only leg emits 80 files and 0 tooling artifacts. The exclude now names the tooling DIRECTORIES (__tests__, __mocks__, __benchmarks__), matching the shape #4836 landed in plugin-grid/plugin-view/plugin-designer; no fourth NAME pattern was added, no source file was deleted or renamed, and no declared type moved (Clause 2 stayed disengaged). The name patterns are kept because 52 *.test.ts(x) files in this package sit outside any __tests__/ directory. A2.2, A2.3, A2.4 all confirmed on head 56453410f: the exclude was name-only, rootDir '..' was left untouched, and the gate's full output names @object-ui/fields as the only affected package among all 39 published ones.",
"tests": "GATE UNION RE-RUN ON THE FINAL COMMIT 81b778ce3 (after the last commit, not before it). RED FIRST, exit codes captured before any pipe. BEFORE -- `pnpm check:published-dist` exit 1: 'Inspected 39 published package(s) after a 1s build: 5886 tarball file(s), 5730 of them build output, 1 tooling artifact(s) in build output.' + '1 finding(s) across 1 published package(s): @object-ui/fields [tooling-in-published-output] packages/fields/dist/__tests__/numberInputBrowserReadings.d.ts'. AFTER -- exit 0: 'Inspected 39 published package(s) after a 1s build: 5885 tarball file(s), 5729 of them build output, 0 tooling artifact(s) in build output.' + 'No published package's build output carries tooling material.' The counts moved by exactly one on each axis (5886/5885, 5730/5729, 1/0), which is the ablation the card asked for: the change reached the EMIT, not merely the check. Narrower ablation on the vite-only leg: 81 files -> 80, tooling artifacts 1 -> 0, with dist/index.d.ts still present as a live control that the exclude did not overshoot. SECOND GATE NOT TRADED: node scripts/check-type-check-coverage.mjs exits 0 before AND after, and diff of the two runs is EMPTY (byte-identical verdict lines: '45/46 via type-check ... 1 not compiled' and '41/41 packages compile their tests'). Coverage measured directly with tsc --listFiles on both programs, every zero carrying a live control in the same query shape: build program (tsc --noEmit) went 1 -> 0 for numberInputBrowserReadings while control src/widgets/numberBadInput.tsx stayed 1; test program (tsc -p tsconfig.test.json) stayed 1 -> 1 while its control (the importing suite) stayed 1. So no tsconfig.test.json entry is owed -- same reason plugin-grid's explainDouble.ts needed none, and PR #4845's half of the remedy is not engaged. FULL BUILD: turbo run build --filter=!@object-ui/site, 43/43 tasks successful, twice (before and after). PACKAGE SUITES: `pnpm exec vitest run packages/fields/` from the repo ROOT -- 130 test files, 2138 tests, all passed. NOTE: `pnpm --filter @object-ui/fields test` is REFUSED by this repo's own guard (objectui#3378) because package-directory vitest silently runs @object-ui/console's 22 files and reports 'Test Files 22 passed' -- a false green; the 130-file count is the control that this package's suites actually ran. `pnpm --filter @object-ui/fields type-check` exit 0 (script name echoed: 'tsc --noEmit && tsc -p tsconfig.test.json', so no zero-match). REST OF THE FAMILY, all exit 0: check-changeset-presence, check-changeset-overwrite (1 added, 0 modified, 0 deleted), check-changeset-fixed, check-changeset-no-major, check-control-bytes (5972 files), check-dist-completeness --all, check-phantom-dependencies, check-package-self-import. Gate family derived by hand from objectui's own package.json + .github/workflows (scripts/pm/dispatch-gates.mjs lives in objectstack and only answers about its own tree, so it was NOT used here). All heavy runs went through scripts/pm/os-verify-lock.sh.",
"mcp_calls": "9 -- issue_read get, issue_read get_comments, search_issues (1 targeted dedupe), issue_write create (#7212), create_pull_request, pull_request_read get (body readback), 2 report comments, 1 comment readback. REST channel probed first and it is 403 in this container with gh absent, so dedupe went through one targeted MCP search rather than REST + local grep -- channel switch declared. The dedupe result was NON-EMPTY (it returned #4006 and #4836, the two known prior instances), so it is self-validating and needed no separate control.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7212: 29 published packages still exclude tooling by NAME only (app-shell, auth, collaboration, create-plugin, i18n, layout, mobile, permissions, plugin-ai, plugin-calendar, plugin-chatbot, plugin-dashboard, plugin-detail, plugin-editor, plugin-form, plugin-gantt, plugin-kanban, plugin-list, plugin-map, plugin-markdown, plugin-report, plugin-timeline, plugin-tree, providers, react, react-runtime, runner, sdui-parser, types) -- all with ZERO offending files today, so each is one shared __tests__/ helper away from the identical red. This is A2.4's latent-instance question answered and A2.5's altitude question carried across, deliberately NOT ridden: the card body lays out three options (shared base config / static ratchet gate / stay per-package) without a recommendation, since the trade turns on how much emit-semantics modelling the repo wants to own and #4846 already ruled on that once in the other direction. Also recorded there: apps/console has NO exclude key at all and does carry src/__tests__/helpers/preview-page-sources.ts, protected only by its noEmit: true."
]
}

Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3157.9 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-CdYvWGSp.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.33KB5.59KB
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)514.13KB117.19KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
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)33.40KB8.71KB
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)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)207.22KB56.04KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.21KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
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.11KB1.48KB
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.72KB2.24KB
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)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM), reviewer of record

Verdict: ACCEPT. Arming on green. A red gate on main is closed.

⭐ A2.1 answered by measurement, and the answer is more interesting than either guess

I made "which program emits dist?" the first measurement because the package tsconfig inherits noEmit: true and its own comment says vite-plugin-dts writes dist — so editing the tsconfig's exclude risked changing the check program and leaving the gate red.

The answer is both:

the emitting program IS vite-plugin-dts, and it builds its declaration program from packages/fields/tsconfig.json — the dts() call in vite.config.ts passes no exclude of its own, so the tsconfig's list is what reaches the emit.

⇒ The tsconfig edit was correct after all, but for a reason that had to be established rather than assumed. And it was established the right way — against a cleaned dist, tsc alone exited 0 and wrote zero files, while vite build alone produced the whole 81-file output including the offender.

⭐ That is the value of putting a cheap decisive measurement first: had dts() carried its own exclude, the obvious fix would have been silently inert. The measurement cost one build and made the difference unfalsifiable either way.

The ablation proves the change reached the EMIT, not just the check

Red first, then green, with counts moving by exactly one on each axis:

beforeafter
tarball files58865885
build output57305729
tooling artifacts10

⭐ And the narrower leg carries a control against over-excluding: the vite-only run goes 81 → 80 files with tooling 1 → 0, while dist/index.d.ts is still present. A fix that excluded too much would also show "0 tooling artifacts" — the surviving entry point is what distinguishes a correct exclude from a broken emit.

The gate I was most worried about was checked both ways

My order flagged that "the likeliest way this fix goes wrong is trading one red gate for another." Closed properly:

  • check-type-check-coverage.mjs exits 0 before and after, and the diff of the two runs is empty — byte-identical verdict lines. A pass-after is not evidence on its own; an unchanged verdict is.
  • Coverage measured directly with tsc --listFiles on both programs, each zero carrying a live control in the same query shape: the build program went 1 → 0 for the offending file while control numberBadInput.tsx stayed 1; the test program stayed 1 → 1 with its own control unchanged.

⇒ No tsconfig.test.json entry is owed, and that is a measurement rather than an omission — PR #4845's half of the remedy is genuinely not engaged here.

The fix is the right shape

Tooling directories (__tests__, __mocks__, __benchmarks__), matching what #4836 landed in plugin-grid / plugin-view / plugin-designer. ⛔ No fourth name pattern, no file deleted or renamed, rootDir: ".." untouched.

⚠️ And the name patterns were kept, for a measured reason: 52 *.test.ts(x) files in this package sit outside any __tests__/ directory. Replacing rather than extending would have been a fourth instance of the same class in the opposite direction.

#7212 is the finding, and it reframes this card

29 published packages still exclude tooling by NAME only — all with zero offending files today, so each is one shared __tests__/ helper away from the identical red.

Plus: apps/console has no exclude key at all and does carry src/__tests__/helpers/preview-page-sources.ts, protected only by noEmit: true.

⇒ This card was not "a bug in @object-ui/fields." It was the third surfacing of a repo-wide latent condition, and the reason it keeps recurring is now measured rather than inferred. That answers A2.4's latent-instance question and carries A2.5's altitude question — deliberately not ridden, with three options and no recommendation, because #4846 already ruled on that trade once in the other direction. Declining to recommend against a standing ruling is the right restraint.

Also noted

The false-green trap recorded again with its mechanism: pnpm --filter @object-ui/fields test is refused by the repo guard because package-directory vitest silently runs @object-ui/console's 22 files and reports "Test Files 22 passed." The 130-file count from the root-relative run is the control that this package's suites actually ran. That is #7089 §3's warning, independently reproduced — and it is a false green, not merely a wrong invocation.

Gate family derived by hand from objectui's own package.json and workflows, with an explicit note that scripts/pm/dispatch-gates.mjs lives in objectstack and only answers about its own tree. Stating why a tool was not used beats silently not using it.


Nothing to change.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check:published-dist is RED on main: @object-ui/fields ships dist/__tests__/numberInputBrowserReadings.d.ts in its tarball

2 participants

@os-warren@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(fields): exclude tooling DIRECTORIES from the emitting program, not just the *.test.* name - #7213

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-6943-fields-tooling-in-dist
Sep 1, 2026
Merged

fix(fields): exclude tooling DIRECTORIES from the emitting program, not just the *.test.* name#7213
os-warren merged 1 commit into
mainfrom
claude/issue-6943-fields-tooling-in-dist

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#6943

@object-ui/fields shipped dist/__tests__/numberInputBrowserReadings.d.ts inside its published tarball. That made check:published-dist red on main since 2026-08-31, and because the same script is the first link in changeset:publish, it also failed the publish command at its first step — surfacing only to the one actor who cannot hand it off.

Which program emits dist — measured, not assumed

This is the question the card was really buying, because packages/fields/tsconfig.json carries a comment warning that tsc there only CHECKS. Editing its exclude could have changed the check program and left the emit untouched: a fix that looks right, reviews clean, and leaves the gate red.

So both legs of build (tsc && vite build && node scripts/build-css.mjs) were run in isolation against a cleaned dist:

legexitfiles written to dist
pnpm exec tsc alone00 — wrote nothing
pnpm exec vite build alone081, including dist/__tests__/numberInputBrowserReadings.d.ts

vite-plugin-dts is the emitting program. It builds its declaration program from this package's own tsconfig.json — the dts() call in vite.config.ts passes no exclude of its own — so the exclude belongs in that tsconfig, and not in the plugin options. The fix is therefore one file plus a changeset.

The change

packages/fields/tsconfig.json now excludes the tooling DIRECTORIES, matching the shape objectui#4836 landed in plugin-grid / plugin-view / plugin-designer:

"exclude": [
"node_modules", "dist",
"**/__tests__/**", "**/__mocks__/**", "**/__benchmarks__/**",
"**/*.test.ts", "**/*.test.tsx"
]

numberInputBrowserReadings.ts holds the measured Chromium/happy-dom readings the number-widget suites share. It carries no assertions, so it is deliberately not a *.test.ts, which is exactly why the name-only list let it through while its 79 neighbours in the same directory were caught.

The name patterns stay — 52 *.test.ts(x) files in this package sit outside any __tests__/ directory, so the directory patterns do not subsume them. __mocks__ and __benchmarks__ match nothing here today and are listed so the table is the directory convention itself, exactly as TOOLING_FILE in scripts/check-phantom-dependencies.mjs spells it, rather than a list to extend next time.

⛔ Not done: adding a fourth NAME pattern such as a BrowserReadings glob. That would have been the same mistake a fourth time.

Gate evidence — red first, then green

Exit codes captured before any pipe, on a full 43-task build. Verdict lines are the gate's own.

BEFORE (pnpm check:published-dist, exit 1):

Inspected 39 published package(s) after a 1s build: 5886 tarball file(s), 5730 of them build output, 1 tooling artifact(s) in build output.
❌ 1 finding(s) across 1 published package(s):
@object-ui/fields [tooling-in-published-output] packages/fields/dist/__tests__/numberInputBrowserReadings.d.ts

AFTER (exit 0):

Inspected 39 published package(s) after a 1s build: 5885 tarball file(s), 5729 of them build output, 0 tooling artifact(s) in build output.
✅ No published package's build output carries tooling material.

The counts moved by exactly one on each axis (5886 to 5885 tarball, 5730 to 5729 build output, 1 to 0 tooling), which is what shows the change reached the emit rather than merely the check. The narrower ablation says the same thing directly: rebuilding the vite build leg alone after the edit produced 80 files instead of 81, 0 tooling artifacts, with dist/index.d.ts still present as a live control that the exclude did not overshoot.

No second gate traded for the first

The likeliest way this repair goes wrong is trading one red gate for another, since scripts/check-type-check-coverage.mjs verifies that tests excluded from a build program are still checked somewhere.

node scripts/check-type-check-coverage.mjs exits 0 both before and after, and its two verdict lines are byte-identical across the change (diff of the two runs is empty):

✅ type-check coverage: 45/46 via `type-check`, 0 via their own build, 0 known-broken (0 errors outstanding), 1 not compiled.
✅ test type-check coverage: 41/41 packages compile their tests, 0 declared debt (0 errors outstanding), 0 with a narrow type-assertion project.

That holds because no coverage actually moves, measured with tsc --listFiles on both programs, each zero carrying a live control in the same query shape:

programbeforeaftercontrol
build (tsc --noEmit)reads the file0 — now excludedsrc/widgets/numberBadInput.tsx still read (1)
test (tsc -p tsconfig.test.json)reads the file1 — still readimporting suite still read (1)

numberInputBrowserReadings.ts is the only file the directory patterns newly remove from the build program, and tsconfig.test.json already reads it as a transitive input of the three suites that import it. So it needs no entry there — the same reason plugin-grid's explainDouble.ts needed none, and the reason the tsconfig.test.json half of the remedy (PR #4845's shape) is not owed on this card.

Everything else run

Union re-run on the final commit 81b778ce3, after the last commit rather than before it:

gateexit
check:published-dist0
check-type-check-coverage.mjs0
check-changeset-presence.mjs0
check-changeset-overwrite.mjs0 (1 added, 0 modified, 0 deleted)
check-changeset-fixed.mjs0
check-changeset-no-major.mjs0
check-control-bytes.mjs0 (5972 files)
check-dist-completeness.mjs --all0
check-phantom-dependencies.mjs0
check-package-self-import.mjs0
pnpm --filter @object-ui/fields type-check0
pnpm exec vitest run packages/fields/0 — 130 files, 2138 tests passed

The vitest run is from the repo ROOT deliberately: pnpm --filter @object-ui/fields test is refused by this repo's own guard (objectui#3378), which exists because running vitest from a package directory silently runs @object-ui/console's 22 files and reports them as green. The 130-file count is the control that this package's suites actually ran.

A changeset is included — a published package's tarball contents change.

Reported, deliberately not ridden

The card raised generalising this into a shared emitting-program exclude, and the dispatch ruled that out of scope. Swept it anyway and filed the reading as #7212: 29 published packages still exclude tooling by NAME only, all with zero offending files today, so each is one shared __tests__/ helper away from the same red. Three rounds of correct per-package repair have left the same trap armed everywhere else, which is the argument that the altitude is wrong — but a cross-package build-config change is its own card with its own review, and no part of it is in this PR.


Generated by Claude Code

…ot just the `*.test.*` name
`@object-ui/fields` shipped `dist/__tests__/numberInputBrowserReadings.d.ts`
inside its published tarball, which made `check:published-dist` red on `main`
and — since the same script is the first link in `changeset:publish` — failed
the publish command at its first step.
`numberInputBrowserReadings.ts` holds the measured Chromium/happy-dom readings
the number-widget suites share. It carries no assertions, so it is deliberately
not a `*.test.ts`, and the name-only exclude list did not catch it while its 79
neighbours in the same directory were kept out.
Which program had to change was measured, not assumed: this package builds with
`tsc && vite build` and the `tsc` leg inherits the root's `noEmit`. Run alone,
`tsc` exited 0 and wrote zero files; `vite build` alone produced the whole
81-file output including the offending declaration. vite-plugin-dts is the
emitting program and builds its declaration program from this package's
`tsconfig.json` (its `dts()` options pass no `exclude`), so the exclude belongs
there. After the change the same vite-only leg emits 80 files and zero tooling
artifacts, with `dist/index.d.ts` still present as a control.
Third instance of the same name-versus-directory mismatch (objectui#4006 here,
objectui#4836 in plugin-grid / plugin-view / plugin-designer), so the table is
now the directory convention itself rather than a list of names to extend.
`__mocks__` / `__benchmarks__` match nothing here today and are listed for that
reason. The name patterns stay: 52 `*.test.ts(x)` files in this package sit
outside any `__tests__/` directory.
No type coverage moves. `numberInputBrowserReadings.ts` is the only file the
directory patterns newly remove from the build program, and the
`tsconfig.test.json` chained off `type-check` already reads it as a transitive
input of the three suites that import it — measured with `--listFiles` before
and after, each zero carrying a live control.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": 6943,
"status": "done",
"branch": "claude/issue-6943-fields-tooling-in-dist",
"pr": "https://github.com/objectstack-ai/objectui/pull/7213",
"premise_still_valid": true,
"summary": "THE EMITTING PROGRAM IS vite-plugin-dts, and it builds its declaration program from packages/fields/tsconfig.json, so the fix went into that tsconfig's `exclude` and nowhere else. Measured rather than assumed, exactly as A2.1 demanded: against a cleaned dist, `pnpm exec tsc` alone exited 0 and wrote ZERO files (it inherits the root's noEmit, so the build's leading `tsc` only checks), while `pnpm exec vite build` alone produced the whole 81-file output including dist/__tests__/numberInputBrowserReadings.d.ts. The dts() call in vite.config.ts passes no `exclude` of its own, which is why the tsconfig's list is what reaches the emit -- and the ablation confirms it end to end rather than by reading: after the edit the same vite-only leg emits 80 files and 0 tooling artifacts. The exclude now names the tooling DIRECTORIES (__tests__, __mocks__, __benchmarks__), matching the shape #4836 landed in plugin-grid/plugin-view/plugin-designer; no fourth NAME pattern was added, no source file was deleted or renamed, and no declared type moved (Clause 2 stayed disengaged). The name patterns are kept because 52 *.test.ts(x) files in this package sit outside any __tests__/ directory. A2.2, A2.3, A2.4 all confirmed on head 56453410f: the exclude was name-only, rootDir '..' was left untouched, and the gate's full output names @object-ui/fields as the only affected package among all 39 published ones.",
"tests": "GATE UNION RE-RUN ON THE FINAL COMMIT 81b778ce3 (after the last commit, not before it). RED FIRST, exit codes captured before any pipe. BEFORE -- `pnpm check:published-dist` exit 1: 'Inspected 39 published package(s) after a 1s build: 5886 tarball file(s), 5730 of them build output, 1 tooling artifact(s) in build output.' + '1 finding(s) across 1 published package(s): @object-ui/fields [tooling-in-published-output] packages/fields/dist/__tests__/numberInputBrowserReadings.d.ts'. AFTER -- exit 0: 'Inspected 39 published package(s) after a 1s build: 5885 tarball file(s), 5729 of them build output, 0 tooling artifact(s) in build output.' + 'No published package's build output carries tooling material.' The counts moved by exactly one on each axis (5886/5885, 5730/5729, 1/0), which is the ablation the card asked for: the change reached the EMIT, not merely the check. Narrower ablation on the vite-only leg: 81 files -> 80, tooling artifacts 1 -> 0, with dist/index.d.ts still present as a live control that the exclude did not overshoot. SECOND GATE NOT TRADED: node scripts/check-type-check-coverage.mjs exits 0 before AND after, and diff of the two runs is EMPTY (byte-identical verdict lines: '45/46 via type-check ... 1 not compiled' and '41/41 packages compile their tests'). Coverage measured directly with tsc --listFiles on both programs, every zero carrying a live control in the same query shape: build program (tsc --noEmit) went 1 -> 0 for numberInputBrowserReadings while control src/widgets/numberBadInput.tsx stayed 1; test program (tsc -p tsconfig.test.json) stayed 1 -> 1 while its control (the importing suite) stayed 1. So no tsconfig.test.json entry is owed -- same reason plugin-grid's explainDouble.ts needed none, and PR #4845's half of the remedy is not engaged. FULL BUILD: turbo run build --filter=!@object-ui/site, 43/43 tasks successful, twice (before and after). PACKAGE SUITES: `pnpm exec vitest run packages/fields/` from the repo ROOT -- 130 test files, 2138 tests, all passed. NOTE: `pnpm --filter @object-ui/fields test` is REFUSED by this repo's own guard (objectui#3378) because package-directory vitest silently runs @object-ui/console's 22 files and reports 'Test Files 22 passed' -- a false green; the 130-file count is the control that this package's suites actually ran. `pnpm --filter @object-ui/fields type-check` exit 0 (script name echoed: 'tsc --noEmit && tsc -p tsconfig.test.json', so no zero-match). REST OF THE FAMILY, all exit 0: check-changeset-presence, check-changeset-overwrite (1 added, 0 modified, 0 deleted), check-changeset-fixed, check-changeset-no-major, check-control-bytes (5972 files), check-dist-completeness --all, check-phantom-dependencies, check-package-self-import. Gate family derived by hand from objectui's own package.json + .github/workflows (scripts/pm/dispatch-gates.mjs lives in objectstack and only answers about its own tree, so it was NOT used here). All heavy runs went through scripts/pm/os-verify-lock.sh.",
"mcp_calls": "9 -- issue_read get, issue_read get_comments, search_issues (1 targeted dedupe), issue_write create (#7212), create_pull_request, pull_request_read get (body readback), 2 report comments, 1 comment readback. REST channel probed first and it is 403 in this container with gh absent, so dedupe went through one targeted MCP search rather than REST + local grep -- channel switch declared. The dedupe result was NON-EMPTY (it returned #4006 and #4836, the two known prior instances), so it is self-validating and needed no separate control.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7212: 29 published packages still exclude tooling by NAME only (app-shell, auth, collaboration, create-plugin, i18n, layout, mobile, permissions, plugin-ai, plugin-calendar, plugin-chatbot, plugin-dashboard, plugin-detail, plugin-editor, plugin-form, plugin-gantt, plugin-kanban, plugin-list, plugin-map, plugin-markdown, plugin-report, plugin-timeline, plugin-tree, providers, react, react-runtime, runner, sdui-parser, types) -- all with ZERO offending files today, so each is one shared __tests__/ helper away from the identical red. This is A2.4's latent-instance question answered and A2.5's altitude question carried across, deliberately NOT ridden: the card body lays out three options (shared base config / static ratchet gate / stay per-package) without a recommendation, since the trade turns on how much emit-semantics modelling the repo wants to own and #4846 already ruled on that once in the other direction. Also recorded there: apps/console has NO exclude key at all and does carry src/__tests__/helpers/preview-page-sources.ts, protected only by its noEmit: true."
]
}

Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3157.9 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-CdYvWGSp.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.33KB5.59KB
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)514.13KB117.19KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
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)33.40KB8.71KB
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)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)207.22KB56.04KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.21KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
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.11KB1.48KB
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.72KB2.24KB
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)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM), reviewer of record

Verdict: ACCEPT. Arming on green. A red gate on main is closed.

⭐ A2.1 answered by measurement, and the answer is more interesting than either guess

I made "which program emits dist?" the first measurement because the package tsconfig inherits noEmit: true and its own comment says vite-plugin-dts writes dist — so editing the tsconfig's exclude risked changing the check program and leaving the gate red.

The answer is both:

the emitting program IS vite-plugin-dts, and it builds its declaration program from packages/fields/tsconfig.json — the dts() call in vite.config.ts passes no exclude of its own, so the tsconfig's list is what reaches the emit.

⇒ The tsconfig edit was correct after all, but for a reason that had to be established rather than assumed. And it was established the right way — against a cleaned dist, tsc alone exited 0 and wrote zero files, while vite build alone produced the whole 81-file output including the offender.

⭐ That is the value of putting a cheap decisive measurement first: had dts() carried its own exclude, the obvious fix would have been silently inert. The measurement cost one build and made the difference unfalsifiable either way.

The ablation proves the change reached the EMIT, not just the check

Red first, then green, with counts moving by exactly one on each axis:

beforeafter
tarball files58865885
build output57305729
tooling artifacts10

⭐ And the narrower leg carries a control against over-excluding: the vite-only run goes 81 → 80 files with tooling 1 → 0, while dist/index.d.ts is still present. A fix that excluded too much would also show "0 tooling artifacts" — the surviving entry point is what distinguishes a correct exclude from a broken emit.

The gate I was most worried about was checked both ways

My order flagged that "the likeliest way this fix goes wrong is trading one red gate for another." Closed properly:

  • check-type-check-coverage.mjs exits 0 before and after, and the diff of the two runs is empty — byte-identical verdict lines. A pass-after is not evidence on its own; an unchanged verdict is.
  • Coverage measured directly with tsc --listFiles on both programs, each zero carrying a live control in the same query shape: the build program went 1 → 0 for the offending file while control numberBadInput.tsx stayed 1; the test program stayed 1 → 1 with its own control unchanged.

⇒ No tsconfig.test.json entry is owed, and that is a measurement rather than an omission — PR #4845's half of the remedy is genuinely not engaged here.

The fix is the right shape

Tooling directories (__tests__, __mocks__, __benchmarks__), matching what #4836 landed in plugin-grid / plugin-view / plugin-designer. ⛔ No fourth name pattern, no file deleted or renamed, rootDir: ".." untouched.

⚠️ And the name patterns were kept, for a measured reason: 52 *.test.ts(x) files in this package sit outside any __tests__/ directory. Replacing rather than extending would have been a fourth instance of the same class in the opposite direction.

#7212 is the finding, and it reframes this card

29 published packages still exclude tooling by NAME only — all with zero offending files today, so each is one shared __tests__/ helper away from the identical red.

Plus: apps/console has no exclude key at all and does carry src/__tests__/helpers/preview-page-sources.ts, protected only by noEmit: true.

⇒ This card was not "a bug in @object-ui/fields." It was the third surfacing of a repo-wide latent condition, and the reason it keeps recurring is now measured rather than inferred. That answers A2.4's latent-instance question and carries A2.5's altitude question — deliberately not ridden, with three options and no recommendation, because #4846 already ruled on that trade once in the other direction. Declining to recommend against a standing ruling is the right restraint.

Also noted

The false-green trap recorded again with its mechanism: pnpm --filter @object-ui/fields test is refused by the repo guard because package-directory vitest silently runs @object-ui/console's 22 files and reports "Test Files 22 passed." The 130-file count from the root-relative run is the control that this package's suites actually ran. That is #7089 §3's warning, independently reproduced — and it is a false green, not merely a wrong invocation.

Gate family derived by hand from objectui's own package.json and workflows, with an explicit note that scripts/pm/dispatch-gates.mjs lives in objectstack and only answers about its own tree. Stating why a tool was not used beats silently not using it.


Nothing to change.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check:published-dist is RED on main: @object-ui/fields ships dist/__tests__/numberInputBrowserReadings.d.ts in its tarball

2 participants

@os-warren@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(fields): exclude tooling DIRECTORIES from the emitting program, not just the *.test.* name - #7213

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-6943-fields-tooling-in-dist
Sep 1, 2026
Merged

fix(fields): exclude tooling DIRECTORIES from the emitting program, not just the *.test.* name#7213
os-warren merged 1 commit into
mainfrom
claude/issue-6943-fields-tooling-in-dist

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#6943

@object-ui/fields shipped dist/__tests__/numberInputBrowserReadings.d.ts inside its published tarball. That made check:published-dist red on main since 2026-08-31, and because the same script is the first link in changeset:publish, it also failed the publish command at its first step — surfacing only to the one actor who cannot hand it off.

Which program emits dist — measured, not assumed

This is the question the card was really buying, because packages/fields/tsconfig.json carries a comment warning that tsc there only CHECKS. Editing its exclude could have changed the check program and left the emit untouched: a fix that looks right, reviews clean, and leaves the gate red.

So both legs of build (tsc && vite build && node scripts/build-css.mjs) were run in isolation against a cleaned dist:

legexitfiles written to dist
pnpm exec tsc alone00 — wrote nothing
pnpm exec vite build alone081, including dist/__tests__/numberInputBrowserReadings.d.ts

vite-plugin-dts is the emitting program. It builds its declaration program from this package's own tsconfig.json — the dts() call in vite.config.ts passes no exclude of its own — so the exclude belongs in that tsconfig, and not in the plugin options. The fix is therefore one file plus a changeset.

The change

packages/fields/tsconfig.json now excludes the tooling DIRECTORIES, matching the shape objectui#4836 landed in plugin-grid / plugin-view / plugin-designer:

"exclude": [
"node_modules", "dist",
"**/__tests__/**", "**/__mocks__/**", "**/__benchmarks__/**",
"**/*.test.ts", "**/*.test.tsx"
]

numberInputBrowserReadings.ts holds the measured Chromium/happy-dom readings the number-widget suites share. It carries no assertions, so it is deliberately not a *.test.ts, which is exactly why the name-only list let it through while its 79 neighbours in the same directory were caught.

The name patterns stay — 52 *.test.ts(x) files in this package sit outside any __tests__/ directory, so the directory patterns do not subsume them. __mocks__ and __benchmarks__ match nothing here today and are listed so the table is the directory convention itself, exactly as TOOLING_FILE in scripts/check-phantom-dependencies.mjs spells it, rather than a list to extend next time.

⛔ Not done: adding a fourth NAME pattern such as a BrowserReadings glob. That would have been the same mistake a fourth time.

Gate evidence — red first, then green

Exit codes captured before any pipe, on a full 43-task build. Verdict lines are the gate's own.

BEFORE (pnpm check:published-dist, exit 1):

Inspected 39 published package(s) after a 1s build: 5886 tarball file(s), 5730 of them build output, 1 tooling artifact(s) in build output.
❌ 1 finding(s) across 1 published package(s):
@object-ui/fields [tooling-in-published-output] packages/fields/dist/__tests__/numberInputBrowserReadings.d.ts

AFTER (exit 0):

Inspected 39 published package(s) after a 1s build: 5885 tarball file(s), 5729 of them build output, 0 tooling artifact(s) in build output.
✅ No published package's build output carries tooling material.

The counts moved by exactly one on each axis (5886 to 5885 tarball, 5730 to 5729 build output, 1 to 0 tooling), which is what shows the change reached the emit rather than merely the check. The narrower ablation says the same thing directly: rebuilding the vite build leg alone after the edit produced 80 files instead of 81, 0 tooling artifacts, with dist/index.d.ts still present as a live control that the exclude did not overshoot.

No second gate traded for the first

The likeliest way this repair goes wrong is trading one red gate for another, since scripts/check-type-check-coverage.mjs verifies that tests excluded from a build program are still checked somewhere.

node scripts/check-type-check-coverage.mjs exits 0 both before and after, and its two verdict lines are byte-identical across the change (diff of the two runs is empty):

✅ type-check coverage: 45/46 via `type-check`, 0 via their own build, 0 known-broken (0 errors outstanding), 1 not compiled.
✅ test type-check coverage: 41/41 packages compile their tests, 0 declared debt (0 errors outstanding), 0 with a narrow type-assertion project.

That holds because no coverage actually moves, measured with tsc --listFiles on both programs, each zero carrying a live control in the same query shape:

programbeforeaftercontrol
build (tsc --noEmit)reads the file0 — now excludedsrc/widgets/numberBadInput.tsx still read (1)
test (tsc -p tsconfig.test.json)reads the file1 — still readimporting suite still read (1)

numberInputBrowserReadings.ts is the only file the directory patterns newly remove from the build program, and tsconfig.test.json already reads it as a transitive input of the three suites that import it. So it needs no entry there — the same reason plugin-grid's explainDouble.ts needed none, and the reason the tsconfig.test.json half of the remedy (PR #4845's shape) is not owed on this card.

Everything else run

Union re-run on the final commit 81b778ce3, after the last commit rather than before it:

gateexit
check:published-dist0
check-type-check-coverage.mjs0
check-changeset-presence.mjs0
check-changeset-overwrite.mjs0 (1 added, 0 modified, 0 deleted)
check-changeset-fixed.mjs0
check-changeset-no-major.mjs0
check-control-bytes.mjs0 (5972 files)
check-dist-completeness.mjs --all0
check-phantom-dependencies.mjs0
check-package-self-import.mjs0
pnpm --filter @object-ui/fields type-check0
pnpm exec vitest run packages/fields/0 — 130 files, 2138 tests passed

The vitest run is from the repo ROOT deliberately: pnpm --filter @object-ui/fields test is refused by this repo's own guard (objectui#3378), which exists because running vitest from a package directory silently runs @object-ui/console's 22 files and reports them as green. The 130-file count is the control that this package's suites actually ran.

A changeset is included — a published package's tarball contents change.

Reported, deliberately not ridden

The card raised generalising this into a shared emitting-program exclude, and the dispatch ruled that out of scope. Swept it anyway and filed the reading as #7212: 29 published packages still exclude tooling by NAME only, all with zero offending files today, so each is one shared __tests__/ helper away from the same red. Three rounds of correct per-package repair have left the same trap armed everywhere else, which is the argument that the altitude is wrong — but a cross-package build-config change is its own card with its own review, and no part of it is in this PR.


Generated by Claude Code

…ot just the `*.test.*` name
`@object-ui/fields` shipped `dist/__tests__/numberInputBrowserReadings.d.ts`
inside its published tarball, which made `check:published-dist` red on `main`
and — since the same script is the first link in `changeset:publish` — failed
the publish command at its first step.
`numberInputBrowserReadings.ts` holds the measured Chromium/happy-dom readings
the number-widget suites share. It carries no assertions, so it is deliberately
not a `*.test.ts`, and the name-only exclude list did not catch it while its 79
neighbours in the same directory were kept out.
Which program had to change was measured, not assumed: this package builds with
`tsc && vite build` and the `tsc` leg inherits the root's `noEmit`. Run alone,
`tsc` exited 0 and wrote zero files; `vite build` alone produced the whole
81-file output including the offending declaration. vite-plugin-dts is the
emitting program and builds its declaration program from this package's
`tsconfig.json` (its `dts()` options pass no `exclude`), so the exclude belongs
there. After the change the same vite-only leg emits 80 files and zero tooling
artifacts, with `dist/index.d.ts` still present as a control.
Third instance of the same name-versus-directory mismatch (objectui#4006 here,
objectui#4836 in plugin-grid / plugin-view / plugin-designer), so the table is
now the directory convention itself rather than a list of names to extend.
`__mocks__` / `__benchmarks__` match nothing here today and are listed for that
reason. The name patterns stay: 52 `*.test.ts(x)` files in this package sit
outside any `__tests__/` directory.
No type coverage moves. `numberInputBrowserReadings.ts` is the only file the
directory patterns newly remove from the build program, and the
`tsconfig.test.json` chained off `type-check` already reads it as a transitive
input of the three suites that import it — measured with `--listFiles` before
and after, each zero carrying a live control.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": 6943,
"status": "done",
"branch": "claude/issue-6943-fields-tooling-in-dist",
"pr": "https://github.com/objectstack-ai/objectui/pull/7213",
"premise_still_valid": true,
"summary": "THE EMITTING PROGRAM IS vite-plugin-dts, and it builds its declaration program from packages/fields/tsconfig.json, so the fix went into that tsconfig's `exclude` and nowhere else. Measured rather than assumed, exactly as A2.1 demanded: against a cleaned dist, `pnpm exec tsc` alone exited 0 and wrote ZERO files (it inherits the root's noEmit, so the build's leading `tsc` only checks), while `pnpm exec vite build` alone produced the whole 81-file output including dist/__tests__/numberInputBrowserReadings.d.ts. The dts() call in vite.config.ts passes no `exclude` of its own, which is why the tsconfig's list is what reaches the emit -- and the ablation confirms it end to end rather than by reading: after the edit the same vite-only leg emits 80 files and 0 tooling artifacts. The exclude now names the tooling DIRECTORIES (__tests__, __mocks__, __benchmarks__), matching the shape #4836 landed in plugin-grid/plugin-view/plugin-designer; no fourth NAME pattern was added, no source file was deleted or renamed, and no declared type moved (Clause 2 stayed disengaged). The name patterns are kept because 52 *.test.ts(x) files in this package sit outside any __tests__/ directory. A2.2, A2.3, A2.4 all confirmed on head 56453410f: the exclude was name-only, rootDir '..' was left untouched, and the gate's full output names @object-ui/fields as the only affected package among all 39 published ones.",
"tests": "GATE UNION RE-RUN ON THE FINAL COMMIT 81b778ce3 (after the last commit, not before it). RED FIRST, exit codes captured before any pipe. BEFORE -- `pnpm check:published-dist` exit 1: 'Inspected 39 published package(s) after a 1s build: 5886 tarball file(s), 5730 of them build output, 1 tooling artifact(s) in build output.' + '1 finding(s) across 1 published package(s): @object-ui/fields [tooling-in-published-output] packages/fields/dist/__tests__/numberInputBrowserReadings.d.ts'. AFTER -- exit 0: 'Inspected 39 published package(s) after a 1s build: 5885 tarball file(s), 5729 of them build output, 0 tooling artifact(s) in build output.' + 'No published package's build output carries tooling material.' The counts moved by exactly one on each axis (5886/5885, 5730/5729, 1/0), which is the ablation the card asked for: the change reached the EMIT, not merely the check. Narrower ablation on the vite-only leg: 81 files -> 80, tooling artifacts 1 -> 0, with dist/index.d.ts still present as a live control that the exclude did not overshoot. SECOND GATE NOT TRADED: node scripts/check-type-check-coverage.mjs exits 0 before AND after, and diff of the two runs is EMPTY (byte-identical verdict lines: '45/46 via type-check ... 1 not compiled' and '41/41 packages compile their tests'). Coverage measured directly with tsc --listFiles on both programs, every zero carrying a live control in the same query shape: build program (tsc --noEmit) went 1 -> 0 for numberInputBrowserReadings while control src/widgets/numberBadInput.tsx stayed 1; test program (tsc -p tsconfig.test.json) stayed 1 -> 1 while its control (the importing suite) stayed 1. So no tsconfig.test.json entry is owed -- same reason plugin-grid's explainDouble.ts needed none, and PR #4845's half of the remedy is not engaged. FULL BUILD: turbo run build --filter=!@object-ui/site, 43/43 tasks successful, twice (before and after). PACKAGE SUITES: `pnpm exec vitest run packages/fields/` from the repo ROOT -- 130 test files, 2138 tests, all passed. NOTE: `pnpm --filter @object-ui/fields test` is REFUSED by this repo's own guard (objectui#3378) because package-directory vitest silently runs @object-ui/console's 22 files and reports 'Test Files 22 passed' -- a false green; the 130-file count is the control that this package's suites actually ran. `pnpm --filter @object-ui/fields type-check` exit 0 (script name echoed: 'tsc --noEmit && tsc -p tsconfig.test.json', so no zero-match). REST OF THE FAMILY, all exit 0: check-changeset-presence, check-changeset-overwrite (1 added, 0 modified, 0 deleted), check-changeset-fixed, check-changeset-no-major, check-control-bytes (5972 files), check-dist-completeness --all, check-phantom-dependencies, check-package-self-import. Gate family derived by hand from objectui's own package.json + .github/workflows (scripts/pm/dispatch-gates.mjs lives in objectstack and only answers about its own tree, so it was NOT used here). All heavy runs went through scripts/pm/os-verify-lock.sh.",
"mcp_calls": "9 -- issue_read get, issue_read get_comments, search_issues (1 targeted dedupe), issue_write create (#7212), create_pull_request, pull_request_read get (body readback), 2 report comments, 1 comment readback. REST channel probed first and it is 403 in this container with gh absent, so dedupe went through one targeted MCP search rather than REST + local grep -- channel switch declared. The dedupe result was NON-EMPTY (it returned #4006 and #4836, the two known prior instances), so it is self-validating and needed no separate control.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7212: 29 published packages still exclude tooling by NAME only (app-shell, auth, collaboration, create-plugin, i18n, layout, mobile, permissions, plugin-ai, plugin-calendar, plugin-chatbot, plugin-dashboard, plugin-detail, plugin-editor, plugin-form, plugin-gantt, plugin-kanban, plugin-list, plugin-map, plugin-markdown, plugin-report, plugin-timeline, plugin-tree, providers, react, react-runtime, runner, sdui-parser, types) -- all with ZERO offending files today, so each is one shared __tests__/ helper away from the identical red. This is A2.4's latent-instance question answered and A2.5's altitude question carried across, deliberately NOT ridden: the card body lays out three options (shared base config / static ratchet gate / stay per-package) without a recommendation, since the trade turns on how much emit-semantics modelling the repo wants to own and #4846 already ruled on that once in the other direction. Also recorded there: apps/console has NO exclude key at all and does carry src/__tests__/helpers/preview-page-sources.ts, protected only by its noEmit: true."
]
}

Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3157.9 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-CdYvWGSp.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.33KB5.59KB
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)514.13KB117.19KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
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)33.40KB8.71KB
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)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)207.22KB56.04KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.21KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
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.11KB1.48KB
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.72KB2.24KB
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)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM), reviewer of record

Verdict: ACCEPT. Arming on green. A red gate on main is closed.

⭐ A2.1 answered by measurement, and the answer is more interesting than either guess

I made "which program emits dist?" the first measurement because the package tsconfig inherits noEmit: true and its own comment says vite-plugin-dts writes dist — so editing the tsconfig's exclude risked changing the check program and leaving the gate red.

The answer is both:

the emitting program IS vite-plugin-dts, and it builds its declaration program from packages/fields/tsconfig.json — the dts() call in vite.config.ts passes no exclude of its own, so the tsconfig's list is what reaches the emit.

⇒ The tsconfig edit was correct after all, but for a reason that had to be established rather than assumed. And it was established the right way — against a cleaned dist, tsc alone exited 0 and wrote zero files, while vite build alone produced the whole 81-file output including the offender.

⭐ That is the value of putting a cheap decisive measurement first: had dts() carried its own exclude, the obvious fix would have been silently inert. The measurement cost one build and made the difference unfalsifiable either way.

The ablation proves the change reached the EMIT, not just the check

Red first, then green, with counts moving by exactly one on each axis:

beforeafter
tarball files58865885
build output57305729
tooling artifacts10

⭐ And the narrower leg carries a control against over-excluding: the vite-only run goes 81 → 80 files with tooling 1 → 0, while dist/index.d.ts is still present. A fix that excluded too much would also show "0 tooling artifacts" — the surviving entry point is what distinguishes a correct exclude from a broken emit.

The gate I was most worried about was checked both ways

My order flagged that "the likeliest way this fix goes wrong is trading one red gate for another." Closed properly:

  • check-type-check-coverage.mjs exits 0 before and after, and the diff of the two runs is empty — byte-identical verdict lines. A pass-after is not evidence on its own; an unchanged verdict is.
  • Coverage measured directly with tsc --listFiles on both programs, each zero carrying a live control in the same query shape: the build program went 1 → 0 for the offending file while control numberBadInput.tsx stayed 1; the test program stayed 1 → 1 with its own control unchanged.

⇒ No tsconfig.test.json entry is owed, and that is a measurement rather than an omission — PR #4845's half of the remedy is genuinely not engaged here.

The fix is the right shape

Tooling directories (__tests__, __mocks__, __benchmarks__), matching what #4836 landed in plugin-grid / plugin-view / plugin-designer. ⛔ No fourth name pattern, no file deleted or renamed, rootDir: ".." untouched.

⚠️ And the name patterns were kept, for a measured reason: 52 *.test.ts(x) files in this package sit outside any __tests__/ directory. Replacing rather than extending would have been a fourth instance of the same class in the opposite direction.

#7212 is the finding, and it reframes this card

29 published packages still exclude tooling by NAME only — all with zero offending files today, so each is one shared __tests__/ helper away from the identical red.

Plus: apps/console has no exclude key at all and does carry src/__tests__/helpers/preview-page-sources.ts, protected only by noEmit: true.

⇒ This card was not "a bug in @object-ui/fields." It was the third surfacing of a repo-wide latent condition, and the reason it keeps recurring is now measured rather than inferred. That answers A2.4's latent-instance question and carries A2.5's altitude question — deliberately not ridden, with three options and no recommendation, because #4846 already ruled on that trade once in the other direction. Declining to recommend against a standing ruling is the right restraint.

Also noted

The false-green trap recorded again with its mechanism: pnpm --filter @object-ui/fields test is refused by the repo guard because package-directory vitest silently runs @object-ui/console's 22 files and reports "Test Files 22 passed." The 130-file count from the root-relative run is the control that this package's suites actually ran. That is #7089 §3's warning, independently reproduced — and it is a false green, not merely a wrong invocation.

Gate family derived by hand from objectui's own package.json and workflows, with an explicit note that scripts/pm/dispatch-gates.mjs lives in objectstack and only answers about its own tree. Stating why a tool was not used beats silently not using it.


Nothing to change.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check:published-dist is RED on main: @object-ui/fields ships dist/__tests__/numberInputBrowserReadings.d.ts in its tarball

2 participants

@os-warren@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(fields): exclude tooling DIRECTORIES from the emitting program, not just the *.test.* name - #7213

Merged
os-warren merged 1 commit into
mainfrom
claude/issue-6943-fields-tooling-in-dist
Sep 1, 2026
Merged

fix(fields): exclude tooling DIRECTORIES from the emitting program, not just the *.test.* name#7213
os-warren merged 1 commit into
mainfrom
claude/issue-6943-fields-tooling-in-dist

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#6943

@object-ui/fields shipped dist/__tests__/numberInputBrowserReadings.d.ts inside its published tarball. That made check:published-dist red on main since 2026-08-31, and because the same script is the first link in changeset:publish, it also failed the publish command at its first step — surfacing only to the one actor who cannot hand it off.

Which program emits dist — measured, not assumed

This is the question the card was really buying, because packages/fields/tsconfig.json carries a comment warning that tsc there only CHECKS. Editing its exclude could have changed the check program and left the emit untouched: a fix that looks right, reviews clean, and leaves the gate red.

So both legs of build (tsc && vite build && node scripts/build-css.mjs) were run in isolation against a cleaned dist:

legexitfiles written to dist
pnpm exec tsc alone00 — wrote nothing
pnpm exec vite build alone081, including dist/__tests__/numberInputBrowserReadings.d.ts

vite-plugin-dts is the emitting program. It builds its declaration program from this package's own tsconfig.json — the dts() call in vite.config.ts passes no exclude of its own — so the exclude belongs in that tsconfig, and not in the plugin options. The fix is therefore one file plus a changeset.

The change

packages/fields/tsconfig.json now excludes the tooling DIRECTORIES, matching the shape objectui#4836 landed in plugin-grid / plugin-view / plugin-designer:

"exclude": [
"node_modules", "dist",
"**/__tests__/**", "**/__mocks__/**", "**/__benchmarks__/**",
"**/*.test.ts", "**/*.test.tsx"
]

numberInputBrowserReadings.ts holds the measured Chromium/happy-dom readings the number-widget suites share. It carries no assertions, so it is deliberately not a *.test.ts, which is exactly why the name-only list let it through while its 79 neighbours in the same directory were caught.

The name patterns stay — 52 *.test.ts(x) files in this package sit outside any __tests__/ directory, so the directory patterns do not subsume them. __mocks__ and __benchmarks__ match nothing here today and are listed so the table is the directory convention itself, exactly as TOOLING_FILE in scripts/check-phantom-dependencies.mjs spells it, rather than a list to extend next time.

⛔ Not done: adding a fourth NAME pattern such as a BrowserReadings glob. That would have been the same mistake a fourth time.

Gate evidence — red first, then green

Exit codes captured before any pipe, on a full 43-task build. Verdict lines are the gate's own.

BEFORE (pnpm check:published-dist, exit 1):

Inspected 39 published package(s) after a 1s build: 5886 tarball file(s), 5730 of them build output, 1 tooling artifact(s) in build output.
❌ 1 finding(s) across 1 published package(s):
@object-ui/fields [tooling-in-published-output] packages/fields/dist/__tests__/numberInputBrowserReadings.d.ts

AFTER (exit 0):

Inspected 39 published package(s) after a 1s build: 5885 tarball file(s), 5729 of them build output, 0 tooling artifact(s) in build output.
✅ No published package's build output carries tooling material.

The counts moved by exactly one on each axis (5886 to 5885 tarball, 5730 to 5729 build output, 1 to 0 tooling), which is what shows the change reached the emit rather than merely the check. The narrower ablation says the same thing directly: rebuilding the vite build leg alone after the edit produced 80 files instead of 81, 0 tooling artifacts, with dist/index.d.ts still present as a live control that the exclude did not overshoot.

No second gate traded for the first

The likeliest way this repair goes wrong is trading one red gate for another, since scripts/check-type-check-coverage.mjs verifies that tests excluded from a build program are still checked somewhere.

node scripts/check-type-check-coverage.mjs exits 0 both before and after, and its two verdict lines are byte-identical across the change (diff of the two runs is empty):

✅ type-check coverage: 45/46 via `type-check`, 0 via their own build, 0 known-broken (0 errors outstanding), 1 not compiled.
✅ test type-check coverage: 41/41 packages compile their tests, 0 declared debt (0 errors outstanding), 0 with a narrow type-assertion project.

That holds because no coverage actually moves, measured with tsc --listFiles on both programs, each zero carrying a live control in the same query shape:

programbeforeaftercontrol
build (tsc --noEmit)reads the file0 — now excludedsrc/widgets/numberBadInput.tsx still read (1)
test (tsc -p tsconfig.test.json)reads the file1 — still readimporting suite still read (1)

numberInputBrowserReadings.ts is the only file the directory patterns newly remove from the build program, and tsconfig.test.json already reads it as a transitive input of the three suites that import it. So it needs no entry there — the same reason plugin-grid's explainDouble.ts needed none, and the reason the tsconfig.test.json half of the remedy (PR #4845's shape) is not owed on this card.

Everything else run

Union re-run on the final commit 81b778ce3, after the last commit rather than before it:

gateexit
check:published-dist0
check-type-check-coverage.mjs0
check-changeset-presence.mjs0
check-changeset-overwrite.mjs0 (1 added, 0 modified, 0 deleted)
check-changeset-fixed.mjs0
check-changeset-no-major.mjs0
check-control-bytes.mjs0 (5972 files)
check-dist-completeness.mjs --all0
check-phantom-dependencies.mjs0
check-package-self-import.mjs0
pnpm --filter @object-ui/fields type-check0
pnpm exec vitest run packages/fields/0 — 130 files, 2138 tests passed

The vitest run is from the repo ROOT deliberately: pnpm --filter @object-ui/fields test is refused by this repo's own guard (objectui#3378), which exists because running vitest from a package directory silently runs @object-ui/console's 22 files and reports them as green. The 130-file count is the control that this package's suites actually ran.

A changeset is included — a published package's tarball contents change.

Reported, deliberately not ridden

The card raised generalising this into a shared emitting-program exclude, and the dispatch ruled that out of scope. Swept it anyway and filed the reading as #7212: 29 published packages still exclude tooling by NAME only, all with zero offending files today, so each is one shared __tests__/ helper away from the same red. Three rounds of correct per-package repair have left the same trap armed everywhere else, which is the argument that the altitude is wrong — but a cross-package build-config change is its own card with its own review, and no part of it is in this PR.


Generated by Claude Code

…ot just the `*.test.*` name
`@object-ui/fields` shipped `dist/__tests__/numberInputBrowserReadings.d.ts`
inside its published tarball, which made `check:published-dist` red on `main`
and — since the same script is the first link in `changeset:publish` — failed
the publish command at its first step.
`numberInputBrowserReadings.ts` holds the measured Chromium/happy-dom readings
the number-widget suites share. It carries no assertions, so it is deliberately
not a `*.test.ts`, and the name-only exclude list did not catch it while its 79
neighbours in the same directory were kept out.
Which program had to change was measured, not assumed: this package builds with
`tsc && vite build` and the `tsc` leg inherits the root's `noEmit`. Run alone,
`tsc` exited 0 and wrote zero files; `vite build` alone produced the whole
81-file output including the offending declaration. vite-plugin-dts is the
emitting program and builds its declaration program from this package's
`tsconfig.json` (its `dts()` options pass no `exclude`), so the exclude belongs
there. After the change the same vite-only leg emits 80 files and zero tooling
artifacts, with `dist/index.d.ts` still present as a control.
Third instance of the same name-versus-directory mismatch (objectui#4006 here,
objectui#4836 in plugin-grid / plugin-view / plugin-designer), so the table is
now the directory convention itself rather than a list of names to extend.
`__mocks__` / `__benchmarks__` match nothing here today and are listed for that
reason. The name patterns stay: 52 `*.test.ts(x)` files in this package sit
outside any `__tests__/` directory.
No type coverage moves. `numberInputBrowserReadings.ts` is the only file the
directory patterns newly remove from the build program, and the
`tsconfig.test.json` chained off `type-check` already reads it as a transitive
input of the three suites that import it — measured with `--listFiles` before
and after, each zero carrying a live control.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wwHa4aaFybxXrfmfHioDM
@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

os-dev-report

{
"issue": 6943,
"status": "done",
"branch": "claude/issue-6943-fields-tooling-in-dist",
"pr": "https://github.com/objectstack-ai/objectui/pull/7213",
"premise_still_valid": true,
"summary": "THE EMITTING PROGRAM IS vite-plugin-dts, and it builds its declaration program from packages/fields/tsconfig.json, so the fix went into that tsconfig's `exclude` and nowhere else. Measured rather than assumed, exactly as A2.1 demanded: against a cleaned dist, `pnpm exec tsc` alone exited 0 and wrote ZERO files (it inherits the root's noEmit, so the build's leading `tsc` only checks), while `pnpm exec vite build` alone produced the whole 81-file output including dist/__tests__/numberInputBrowserReadings.d.ts. The dts() call in vite.config.ts passes no `exclude` of its own, which is why the tsconfig's list is what reaches the emit -- and the ablation confirms it end to end rather than by reading: after the edit the same vite-only leg emits 80 files and 0 tooling artifacts. The exclude now names the tooling DIRECTORIES (__tests__, __mocks__, __benchmarks__), matching the shape #4836 landed in plugin-grid/plugin-view/plugin-designer; no fourth NAME pattern was added, no source file was deleted or renamed, and no declared type moved (Clause 2 stayed disengaged). The name patterns are kept because 52 *.test.ts(x) files in this package sit outside any __tests__/ directory. A2.2, A2.3, A2.4 all confirmed on head 56453410f: the exclude was name-only, rootDir '..' was left untouched, and the gate's full output names @object-ui/fields as the only affected package among all 39 published ones.",
"tests": "GATE UNION RE-RUN ON THE FINAL COMMIT 81b778ce3 (after the last commit, not before it). RED FIRST, exit codes captured before any pipe. BEFORE -- `pnpm check:published-dist` exit 1: 'Inspected 39 published package(s) after a 1s build: 5886 tarball file(s), 5730 of them build output, 1 tooling artifact(s) in build output.' + '1 finding(s) across 1 published package(s): @object-ui/fields [tooling-in-published-output] packages/fields/dist/__tests__/numberInputBrowserReadings.d.ts'. AFTER -- exit 0: 'Inspected 39 published package(s) after a 1s build: 5885 tarball file(s), 5729 of them build output, 0 tooling artifact(s) in build output.' + 'No published package's build output carries tooling material.' The counts moved by exactly one on each axis (5886/5885, 5730/5729, 1/0), which is the ablation the card asked for: the change reached the EMIT, not merely the check. Narrower ablation on the vite-only leg: 81 files -> 80, tooling artifacts 1 -> 0, with dist/index.d.ts still present as a live control that the exclude did not overshoot. SECOND GATE NOT TRADED: node scripts/check-type-check-coverage.mjs exits 0 before AND after, and diff of the two runs is EMPTY (byte-identical verdict lines: '45/46 via type-check ... 1 not compiled' and '41/41 packages compile their tests'). Coverage measured directly with tsc --listFiles on both programs, every zero carrying a live control in the same query shape: build program (tsc --noEmit) went 1 -> 0 for numberInputBrowserReadings while control src/widgets/numberBadInput.tsx stayed 1; test program (tsc -p tsconfig.test.json) stayed 1 -> 1 while its control (the importing suite) stayed 1. So no tsconfig.test.json entry is owed -- same reason plugin-grid's explainDouble.ts needed none, and PR #4845's half of the remedy is not engaged. FULL BUILD: turbo run build --filter=!@object-ui/site, 43/43 tasks successful, twice (before and after). PACKAGE SUITES: `pnpm exec vitest run packages/fields/` from the repo ROOT -- 130 test files, 2138 tests, all passed. NOTE: `pnpm --filter @object-ui/fields test` is REFUSED by this repo's own guard (objectui#3378) because package-directory vitest silently runs @object-ui/console's 22 files and reports 'Test Files 22 passed' -- a false green; the 130-file count is the control that this package's suites actually ran. `pnpm --filter @object-ui/fields type-check` exit 0 (script name echoed: 'tsc --noEmit && tsc -p tsconfig.test.json', so no zero-match). REST OF THE FAMILY, all exit 0: check-changeset-presence, check-changeset-overwrite (1 added, 0 modified, 0 deleted), check-changeset-fixed, check-changeset-no-major, check-control-bytes (5972 files), check-dist-completeness --all, check-phantom-dependencies, check-package-self-import. Gate family derived by hand from objectui's own package.json + .github/workflows (scripts/pm/dispatch-gates.mjs lives in objectstack and only answers about its own tree, so it was NOT used here). All heavy runs went through scripts/pm/os-verify-lock.sh.",
"mcp_calls": "9 -- issue_read get, issue_read get_comments, search_issues (1 targeted dedupe), issue_write create (#7212), create_pull_request, pull_request_read get (body readback), 2 report comments, 1 comment readback. REST channel probed first and it is 403 in this container with gh absent, so dedupe went through one targeted MCP search rather than REST + local grep -- channel switch declared. The dedupe result was NON-EMPTY (it returned #4006 and #4836, the two known prior instances), so it is self-validating and needed no separate control.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #7212: 29 published packages still exclude tooling by NAME only (app-shell, auth, collaboration, create-plugin, i18n, layout, mobile, permissions, plugin-ai, plugin-calendar, plugin-chatbot, plugin-dashboard, plugin-detail, plugin-editor, plugin-form, plugin-gantt, plugin-kanban, plugin-list, plugin-map, plugin-markdown, plugin-report, plugin-timeline, plugin-tree, providers, react, react-runtime, runner, sdui-parser, types) -- all with ZERO offending files today, so each is one shared __tests__/ helper away from the identical red. This is A2.4's latent-instance question answered and A2.5's altitude question carried across, deliberately NOT ridden: the card body lays out three options (shared base config / static ratchet gate / stay per-package) without a recommendation, since the trade turns on how much emit-semantics modelling the repo wants to own and #4846 already ruled on that once in the other direction. Also recorded there: apps/console has NO exclude key at all and does carry src/__tests__/helpers/preview-page-sources.ts, protected only by its noEmit: true."
]
}

Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3157.9 KB3191.4 KB
Main entry chunk (gzip)142.6 KB350 KB
Entry fileindex-CdYvWGSp.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.33KB5.59KB
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)514.13KB117.19KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
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)33.40KB8.71KB
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)46.92KB12.93KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)190.53KB45.18KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.65KB63.91KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)165.21KB40.37KB
plugin-grid (index.js)207.22KB56.04KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.21KB27.60KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.34KB8.47KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
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.11KB1.48KB
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.72KB2.24KB
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)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-warrenClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM), reviewer of record

Verdict: ACCEPT. Arming on green. A red gate on main is closed.

⭐ A2.1 answered by measurement, and the answer is more interesting than either guess

I made "which program emits dist?" the first measurement because the package tsconfig inherits noEmit: true and its own comment says vite-plugin-dts writes dist — so editing the tsconfig's exclude risked changing the check program and leaving the gate red.

The answer is both:

the emitting program IS vite-plugin-dts, and it builds its declaration program from packages/fields/tsconfig.json — the dts() call in vite.config.ts passes no exclude of its own, so the tsconfig's list is what reaches the emit.

⇒ The tsconfig edit was correct after all, but for a reason that had to be established rather than assumed. And it was established the right way — against a cleaned dist, tsc alone exited 0 and wrote zero files, while vite build alone produced the whole 81-file output including the offender.

⭐ That is the value of putting a cheap decisive measurement first: had dts() carried its own exclude, the obvious fix would have been silently inert. The measurement cost one build and made the difference unfalsifiable either way.

The ablation proves the change reached the EMIT, not just the check

Red first, then green, with counts moving by exactly one on each axis:

beforeafter
tarball files58865885
build output57305729
tooling artifacts10

⭐ And the narrower leg carries a control against over-excluding: the vite-only run goes 81 → 80 files with tooling 1 → 0, while dist/index.d.ts is still present. A fix that excluded too much would also show "0 tooling artifacts" — the surviving entry point is what distinguishes a correct exclude from a broken emit.

The gate I was most worried about was checked both ways

My order flagged that "the likeliest way this fix goes wrong is trading one red gate for another." Closed properly:

  • check-type-check-coverage.mjs exits 0 before and after, and the diff of the two runs is empty — byte-identical verdict lines. A pass-after is not evidence on its own; an unchanged verdict is.
  • Coverage measured directly with tsc --listFiles on both programs, each zero carrying a live control in the same query shape: the build program went 1 → 0 for the offending file while control numberBadInput.tsx stayed 1; the test program stayed 1 → 1 with its own control unchanged.

⇒ No tsconfig.test.json entry is owed, and that is a measurement rather than an omission — PR #4845's half of the remedy is genuinely not engaged here.

The fix is the right shape

Tooling directories (__tests__, __mocks__, __benchmarks__), matching what #4836 landed in plugin-grid / plugin-view / plugin-designer. ⛔ No fourth name pattern, no file deleted or renamed, rootDir: ".." untouched.

⚠️ And the name patterns were kept, for a measured reason: 52 *.test.ts(x) files in this package sit outside any __tests__/ directory. Replacing rather than extending would have been a fourth instance of the same class in the opposite direction.

#7212 is the finding, and it reframes this card

29 published packages still exclude tooling by NAME only — all with zero offending files today, so each is one shared __tests__/ helper away from the identical red.

Plus: apps/console has no exclude key at all and does carry src/__tests__/helpers/preview-page-sources.ts, protected only by noEmit: true.

⇒ This card was not "a bug in @object-ui/fields." It was the third surfacing of a repo-wide latent condition, and the reason it keeps recurring is now measured rather than inferred. That answers A2.4's latent-instance question and carries A2.5's altitude question — deliberately not ridden, with three options and no recommendation, because #4846 already ruled on that trade once in the other direction. Declining to recommend against a standing ruling is the right restraint.

Also noted

The false-green trap recorded again with its mechanism: pnpm --filter @object-ui/fields test is refused by the repo guard because package-directory vitest silently runs @object-ui/console's 22 files and reports "Test Files 22 passed." The 130-file count from the root-relative run is the control that this package's suites actually ran. That is #7089 §3's warning, independently reproduced — and it is a false green, not merely a wrong invocation.

Gate family derived by hand from objectui's own package.json and workflows, with an explicit note that scripts/pm/dispatch-gates.mjs lives in objectstack and only answers about its own tree. Stating why a tool was not used beats silently not using it.


Nothing to change.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check:published-dist is RED on main: @object-ui/fields ships dist/__tests__/numberInputBrowserReadings.d.ts in its tarball

2 participants

@os-warren@claude