Repository navigation
Commit 93125ae
Fixes #22044
Clause-②: no
This is the source-side payback for the maintainer's ruling on
objectui#11717. The record is comment `6027998226`, and the selection
was 「A 抬预算+回收卡 (Recommended)」. objectui raised its console first-screen
budget to let 17.7.0 in (objectui#11742), and this card brings it back
down.
**Measured on objectui's console at objectui `c0862c1c`, which resolves
17.7.0:** the first screen's eager closure is **226,238 bytes gzip
smaller**, and all of the change is in the `vendor-objectstack` chunk.
The 17.7.0 bump added +182,909 bytes gzip to that chunk. No entry's
export list changes, and no declaration or runtime value changes. Only
the bytes a bundler keeps change.
## What grew between the tags, module by module
**Instrument:** source-map attribution of each published ESM entry file.
The tarball ships an `.mjs.map` for each entry, and each generated byte
is assigned to the source module of the mapping it falls under. The
comparison is npm `@objectstack/spec@17.6.0` against
`@objectstack/spec@17.7.0`. All figures are raw bytes.
**`dist/shared/index.mjs`:** 980,505 to 1,503,348 bytes (+522,843), with
the same 79 exports.
| module in `./shared` | 17.6.0 | 17.7.0 | delta |
| --- | --: | --: | --: |
| `src/ui/component.zod.ts` | 277,752 | 395,904 | +118,152 |
| `src/ui/view.zod.ts` | 42,538 | 156,126 | +113,588 |
| `src/data/field.zod.ts` | 11,426 | 122,268 | +110,842 |
| `src/ui/dashboard.zod.ts` | 0 | 38,152 | +38,152 |
| `src/conversions/registry.ts` | 357,075 | 391,897 | +34,822 |
| `src/ui/chart.zod.ts` | 0 | 31,088 | +31,088 |
| `src/ui/report.zod.ts` | 0 | 21,701 | +21,701 |
| `src/ui/bulk-action.zod.ts` | 131 | 17,034 | +16,903 |
| 32 other modules, net | | | +37,595 |
**`dist/browser/index.mjs`** (the root a browser bundler loads):
2,348,750 to 2,593,955 bytes (+245,205).
| module in the browser root | delta |
| --- | --: |
| `src/ui/component.zod.ts` | +118,152 |
| `src/conversions/registry.ts` | +34,822 |
| `src/automation/approval.zod.ts` | +23,623 |
| `src/ai/agent.zod.ts` | +10,024 |
| `src/system/job.zod.ts` | +10,022 |
| `src/ui/chart.zod.ts` | +9,605 |
| `src/ui/page.zod.ts` | +4,549 |
| other modules, net | +34,408 |
**How `./shared` reaches those modules.** This is the esbuild metafile
of the 17.7.0 source, shortest path. `shared/index.ts` imports
`shared/metadata-collection.zod.ts`, which imports
`conversions/apply.ts` (for `normalizeStackInput`). That imports
`conversions/registry.ts`, and from there the schemas the conversions
read:
- `ui/view.zod.ts`, then `chart.zod.ts` and `bulk-action.zod.ts`;
- through `conversions/walk.ts`: `ui/component.zod.ts`, then
`dashboard.zod.ts` and `report.zod.ts`.
`data/field.zod.ts` is also reached through
`shared/field-type-suggestion.ts`. The shared source itself grew very
little: its own modules are under 10 KB of the delta.
## Why the console kept it
**Every top-level call stays.** Each published entry is one flat file
(`tsup.config.ts`, `splitting: false`). A consumer's bundler therefore
keeps every top-level call it cannot prove pure, together with
everything that call references.
**Two calls build the conversion table at load:**
- `18: inApplicationOrder(MAJOR_18_CONVERSIONS)`;
- the `Object.keys(CONVERSIONS_BY_MAJOR)` chain that flattens into
`ALL_CONVERSIONS`.
Unmarked, they kept every conversion, and every schema a conversion
reads, in every bundle of every entry whose graph reaches the table.
They did so whether or not the bundle reads the table.
**The console never reads the table, and it carried two copies.** In the
control's `vendor-objectstack` chunk, each of the 129 conversion ids
appears as an `id:` property exactly **2** times: the root's copy and
`./shared`'s copy. In the candidate's chunk the count is **0**. That is
why the recovery is larger than `./shared`'s growth alone. The table was
already in both copies at 17.6.0, and 17.7.0's new conversions made each
copy larger.
**Consumer probe.** This probe isolates `./shared` from the rest of the
chunk. It is not the acceptance instrument. Setup:
- rolldown 1.0.3, platform browser, minified, gzip -9;
- one bundle per spec entry, importing exactly the names objectui's
non-test source at `c0862c1c` value-imports from that entry;
- spec's own source maps chained, so each retained byte is attributed to
its module.
| entry | 17.6.0 | 17.7.0 | this head |
| --- | --: | --: | --: |
| `./shared` | 120,147 | 191,561 | **47,491** |
| `./ui` | 132,642 | 165,099 | 165,156 |
| root (browser) | 312,154 | 342,786 | 342,842 |
| `./system` (browser) | 283,598 | 310,749 | 310,794 |
| all 13 entries objectui imports | 1,140,385 | 1,315,136 | 1,171,318 |
- In `./shared` at this head, `src/conversions/registry.ts` keeps 0
bytes, down from 201,720 at 17.7.0.
- The root probe still keeps the table. Its name set includes
`defineStack` / `composeStacks`, which `@object-ui/types` and
`@object-ui/core` re-export, but nothing the console bundles calls
either of them. The console reading above shows the root's copy gone as
well.
## What changed
- **`packages/spec/src/conversions/registry.ts`:** both load-time calls
carry a `@__PURE__` annotation. The `CONVERSIONS_BY_MAJOR` docblock says
why, says that the next major's `inApplicationOrder(…)` takes the same
mark, and names the pin. No conversion, no order and no value changes.
- **Why source annotations, not the `pure-schema-construction` build
plugin.** The plugin marks calls by the name of the callee, everywhere
under `src/`. The `ALL_CONVERSIONS` initializer is a `map` / `sort` /
`flatMap` chain on `Object.keys`, and marking those names would mark
every such call in the package. Two explicit marks on the two calls are
the narrowest change. `tsup.config.ts` is untouched, so the chunk layout
and the published file set are unchanged (below).
- **Pin:** a new case in
`packages/spec/scripts/pure-schema-construction.test.ts`. It builds
`./shared` through tsup with the package's own main options, then builds
two consumers of that output:
- the names objectui's console imports from `./shared` keep **none** of
the 129 conversion ids;
- `normalizeStackInput`, the control, keeps **all** of them, which
proves the instrument sees the table whenever a bundle keeps it.
This is a test case, not a gate and not a byte ceiling.
- **`packages/spec/scripts/conversions-major18-merge.test.ts`:** its
`WIRING` string, a textual pin of the major-18 wiring line, now spells
the annotation.
- **`.changeset/22044-shared-entry-drops-conversion-table.md`:**
`@objectstack/spec` `patch`. The published bytes change and the contract
does not.
## Console reading: the acceptance instrument
objectui `c0862c1c` in a detached worktree, built the way the `Bundle
Analysis` job builds it:
```text
pnpm install --frozen-lockfile
pnpm turbo run build --filter='./packages/*'
pnpm --filter @object-ui/console build
```
Each reading comes from `apps/console/dist/eager-closure.json`, which
reports gzip bytes.
| build | eager closure gzip | `vendor-objectstack` gzip |
`vendor-objectstack` raw | table copies in the chunk |
| --- | --: | --: | --: | --: |
| (a) control: the installed npm 17.7.0 | 3,591,131 | 1,604,474 |
5,399,508 | 2 |
| (b) `OBJECTSTACK_SPEC_DIST` set to `main` `9a0401fd`, built |
3,591,379 | 1,604,719 | 5,400,408 | not counted |
| (c) `OBJECTSTACK_SPEC_DIST` set to this head `eb2152b86b`, built |
**3,365,141** | **1,378,418** | 4,522,189 | **0** |
- **Recovered, (b) − (c), same injection path and same objectui
commit:** 226,238 gzip on the eager closure, and 226,301 gzip / 878,219
raw on `vendor-objectstack`. The other 330 eager chunks moved by +63
gzip net (import-hash churn), and the eager chunk count is 331 in all
three builds.
- **Against the published control, (a) − (c):** 225,990 gzip.
- **Repeatability:** (a) and (c) were each built twice and read the same
both times.
- **Why `OBJECTSTACK_SPEC_DIST`:** it is objectui's own injection, the
one the Console Pin Gate uses. Reading (b) shows it moves the control by
only +248 gzip, which is `main`'s drift since the tag plus the injection
path.
## Export parity
The guard is that the export surface does not change. Every comparison
below sets `main` `9a0401fd` (built in its own worktree) against this
head (built):
- **`pnpm --filter @objectstack/spec check:api-surface`:** exit 0,
"public API surface + factory signatures unchanged ✓".
- **Runtime export lists:** `Object.keys` of every `.mjs` and `.js` file
the exports map names, browser arms included. 48 entry files and 7,864
names, **0 differing**. Control leg: npm 17.6.0 against this head reads
16 differing (exit 1).
- **Declaration tree:** compared in the order-insensitive form the
`tsup.config.ts` DTS docblock prescribes (sort union members,
property-only type-literal members, statements and specifiers; strip
chunk hashes; pair chunks by content). 130 files, 38 entries and 62
chunk contents on each side: **identical**. Control leg: npm 17.6.0
against 17.7.0 reads 52 differences (exit 1).
- **Published file set:** `dist/` holds 228 files on both sides, with
chunk hashes stripped, and the lists are identical. The chunk layout
does not move, so `publish-smoke` is not owed and `needs:pack-smoke` is
not needed.
- **Per-entry bytes:** each entry file that carries the table grows by
32 raw bytes, the two annotations. For example, `dist/shared/index.mjs`
goes from 1,510,391 to 1,510,423 bytes.
## Verification record
The final head is `eb2152b86b`. Its last commit adds only the changeset,
so readings taken at `08392e53a4` cover the same `src/` and `scripts/`
tree.
- **Reverse verification of the pin.** Both annotations were committed
first, then each was removed in turn through
`scripts/ablation-replace.mjs`, at `08392e53a4`:
- removing the `ALL_CONVERSIONS` mark ("mutation landed", anchor count 1
to 0) turned the pin red, with 129 ids kept against the expected `[]`.
Restore: "blob == HEAD (d707d83) and `git diff HEAD` is empty".
- removing the `inApplicationOrder` mark turned it red with the 62
major-18 ids kept. Restored the same way.
- The pin builds from `src/` through tsup, so no `dist/` sits between
the mutation and the assertion.
- **Tests:**
| suite | head | result |
| --- | --- | --- |
| `@objectstack/spec` local project, full (`pnpm --filter
@objectstack/spec test`) | `eb2152b86b` | 619 files, 18,486 passed, 1
todo |
| `scripts/pure-schema-construction.test.ts` | `08392e53a4` | 7 passed
(the new case included) |
| `scripts/conversions-major18-merge.test.ts`, repo project |
`08392e53a4` | 12 passed |
The rest of the `repo` project is declared to CI.
- **Typecheck:** `pnpm --filter @objectstack/spec typecheck` (tsc,
scripts and test layer) passed at `eb2152b86b`.
- **Build:** `pnpm --filter @objectstack/spec build` (the JS and DTS
passes, `check-dts-emitted`, `check-dts-references`) exited 0 at
`d017de4582` and at `eb2152b86b`.
- **Gates**, at `eb2152b86b`:
- `node scripts/pm/dispatch-gates.mjs --commands` derives 86 families.
`--ran` reconciles them: 86 run, 0 NOT-MEASURED, 0 UNRUN. Every recorded
exit code is 0.
- Three of the 86 first answered PREREQUISITE NOT MET (exit 3) and were
re-run green after `pnpm turbo run build`: `check:dual-build-cjs-loads`,
`check:lean-entry-closure` and `@objectstack/lint`
`check:doc-formula-expressions`.
- Also exit 0: `pnpm --filter @objectstack/spec check:generated` ("All
15 generated artifacts are up to date").
- The five families the dispatch's lead list named beyond this
derivation also exited 0: `check:authorable-surface`,
`check:yaml-examples`, the `check-governed-queue-guard` self-test,
`check:console-injection` and `check:console-sha`. The last two skipped
for want of `packages/console/dist`, so they are **NOT MEASURED** here.
- **Lint**, narrowed and proven, at `eb2152b86b`:
- Scope: `eslint --no-inline-config --format json` over the 3 changed
`.ts` files, which is every changed file in eslint's population
(`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`). The changeset is outside it.
- Result: 3 files, 0 errors, 0 warnings.
- Why the narrowing excludes nothing: `eslint.config.mjs` enables no
type-aware linting (no `parserOptions.project`, no typed rules), so this
diff cannot move any untouched file's verdict. The full-tree `pnpm lint`
is CI's.
## Acceptance notes
- **objectui's half.** Lowering the ceiling and `BASELINE` by the
recovered amount is objectui's follow-up, once it resolves a release
that carries this change. The reading to lower by is the (b) − (c) line
above. No objectui commit, branch or PR was made: the objectui worktree
was used for measurement only, and was removed.
- **What is left in `./shared` after this change** (consumer probe:
144,609 raw / 47,491 gzip for the console's 5 names). Most of it is
argument expressions of calls the build marks pure. A `strictObject({ …
})` call is dropped when unused, but arguments that have side effects
still run and are kept, for example bare `z.*` chains,
`.optional().describe(…)` on an imported schema, and `componentSlot(…)`.
- The largest remainders: `src/ui/component.zod.ts` 34,556,
`src/ui/view.zod.ts` 18,973, `src/data/filter.zod.ts` about 18,000.
- This is how the build marks construction, not something this card's
scope covers. Noted, not filed. Carrier: none.
- **Copies across entries.** In the per-entry probe,
`src/ui/component.zod.ts` is kept at about 157 to 159 KB raw in each of
`./ui`, the root and `./system`, because each flat entry carries its own
copy.
- Whether the console keeps more than one copy is NOT MEASURED: its
build emits no module-level map. That would be the shared-chunk route,
which redraws the published file set, and the `tsup.config.ts` docblock
asks for that review first.
- Noted, not filed. Carrier: none.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01GV6oYwgc1kWiUCb1YaprQ7)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent a7a48b7 commit 93125ae
4 files changed
Lines changed: 66 additions & 5 deletions
File tree
- .changeset
- packages/spec
- scripts
- src/conversions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
53 | | - | |
54 | | - | |
| 53 | + | |
| 54 | + | |
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| 20 | + | |
20 | 21 | | |
21 | 22 | | |
22 | 23 | | |
| |||
114 | 115 | | |
115 | 116 | | |
116 | 117 | | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
117 | 147 | | |
118 | 148 | | |
119 | 149 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14832 | 14832 | | |
14833 | 14833 | | |
14834 | 14834 | | |
| 14835 | + | |
| 14836 | + | |
| 14837 | + | |
| 14838 | + | |
| 14839 | + | |
| 14840 | + | |
| 14841 | + | |
| 14842 | + | |
| 14843 | + | |
| 14844 | + | |
| 14845 | + | |
| 14846 | + | |
| 14847 | + | |
| 14848 | + | |
| 14849 | + | |
| 14850 | + | |
| 14851 | + | |
14835 | 14852 | | |
14836 | 14853 | | |
14837 | 14854 | | |
| |||
14904 | 14921 | | |
14905 | 14922 | | |
14906 | 14923 | | |
14907 | | - | |
| 14924 | + | |
14908 | 14925 | | |
14909 | 14926 | | |
14910 | | - | |
14911 | | - | |
| 14927 | + | |
| 14928 | + | |
| 14929 | + | |
| 14930 | + | |
| 14931 | + | |
14912 | 14932 | | |
14913 | 14933 | | |
14914 | 14934 | | |
0 commit comments