4b910f19bf61d31ab19e3c6931f5785057ba997b
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4b910f19bf |
🔍 fix: apply the code review — two visible regressions, and the gates behind them
Nine review angles, fourteen verified findings, all but the byte-table generator applied. The two that mattered most were regressions of mine: - a:hover repainted button-styled anchors, and in dark theme --accent-text equals --accent-solid — hovering the hero CTA drew its label in its own background colour. Verified invisible before (computed color == computed background) and distinct after: 10.39:1 dark, 6.90:1 light. The buttons and the skip link now re-assert their label colours on hover. - Footer links had lost every non-hover affordance: the text-decoration:none carve-out plus body-coloured links left a 2.37:1 shade difference as the only cue. The carve-out is deleted — .nav-links a and .brand already declare none themselves — and the accent rule is back on the footer. Also on the page: #ref-filter, the one text input, moves to --border-ui (it still had the 1.68:1 border the token's own comment calls decorative); focusable code surfaces get the --accent-on-code ring at -2px offset, inside the .code overflow clip; #output .out-err drops #ff8095, the last surviving colour of the deleted indigo palette; the two rgba(255,106,126) washes become color-mix over --err-line so a grep for the token finds them. The theme machinery loses a whole block: the dark media query is guarded with :not([data-theme="light"]), so an explicit light toggle falls through to the bare :root palette and the 21-token hand-copy in [data-theme="light"] is gone. Verified in all four system/toggle combinations. The page stops contradicting the repo: it claimed cereale/min "cannot tree-shake — nothing left to shake" while src/treeshake.test.ts proves the opposite on every run. Corrected here and in FRAMEWORKS.md, with the measured figures (1,837 vs 1,996 bytes for one decorator). The release facts the page was hand-bumping — both tgz names, "0.4.0 lives in the repository", the sixty-eight — now fill from meta.js/the bundle like the version badge always has. The workflows close three holes: - The docs sync gate was blind to NEW untracked build outputs (git diff does not report them; demonstrated). Both workflows now run check:docs-sync, one shared script that fails on anything porcelain reports — which also ends the copy-paste divergence between them. - pages.yml deploys on CI succeeding on main (workflow_run) instead of on the push itself, so a deploy implies green tests, not just in-sync docs. The deploy job refuses refs other than main, closing the workflow_dispatch any-branch deploy, and the build job drops pages/id-token — npm postinstall scripts no longer run alongside an OIDC grant. - The Junie action is pinned to the commit behind v1.7.4 rather than the tag, which is the immutability the previous comment promised but a mutable ref cannot deliver. Verified: every fix confirmed in a rendered browser in both themes; 72 contrast pairs still pass; no overflow at 20 widths; 268 tests, build, check:types, check:docs, actionlint all green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK |
||
|
|
c48a106a05 |
🌳 fix: make cereale/min tree-shake too, and correct what I overclaimed
An audit of the last commit found two real problems and several claims of mine that went further than the evidence. **cereale/min was not tree-shakable.** scripts/build-bundle.mjs used esbuild's `minify: true`, whose minifyWhitespace pass strips comments — /*#__PURE__*/ annotations included. The published entry point therefore reproduced exactly the bug the previous commit fixed: one decorator came out at 5,066 bytes with all 26 unrelated rule messages, against 1,837 from the per-module entry. Every source-level check stayed green, because they all bundled src/ and the annotations are stripped on the way into dist/. It is now minified for syntax and identifiers but not whitespace: 33.9 KB raw and 9.6 KB gzipped against 26.0/8.7, so about a kilobyte over the wire for a file that behaves correctly however it is used. One decorator via cereale/min is now 1,996 bytes. The build asserts the annotation count survives, and treeshake.test.ts now bundles the published artifact as well as the source — the gap that let this through. **sideEffects was partly inert.** `./dist/cjs/metadata.js` could never match: the build writes dist/cjs/package.json, which becomes the nearest descriptor for everything beneath it, so bundlers read sideEffects from there. That file now carries its own declaration. `./src/metadata.ts` was missing while src/ is published, which declared the Symbol.metadata install droppable in the source tree. Five module-scope caches in utils.ts are annotated for the same reason as the rules. I checked the audit's third blocker — that the Symbol.metadata install is dropped by bundlers — and it is not. It survives every case where it is load-bearing (a decorator import, toPlainSync, modelOf, and a decorated model bundled with an app). It is dropped only when importing nothing but flattenErrors, which needs no metadata, so that is correct. Corrections to my own wording: - "all three bundlers land within 20 bytes" held only for the one-decorator row; larger imports differ by up to a few hundred bytes - "measured through three bundlers, and pinned by a test" read as though the test covered all three; it covers esbuild, on source and on dist - "every rule is a top-level call" — 30 of the 68 are - the docs page "loads nothing from the network" — it fetches its own vendored compiler, same-origin, on first Run. It has no third-party dependencies, which is the claim I should have made - cereale/min's size, everywhere it appears Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK |
||
|
|
2a2f7345ad |
🌳 perf: make the library actually tree-shakable
Importing one decorator pulled in the message and validator of all 68 — 4,909 bytes instead of 1,837 through esbuild, 4,823 instead of 1,818 through webpack. Nothing failed and nothing warned. The library was simply about three times heavier than it needed to be in every consumer's bundle. Every rule is a top-level call: `export const IsString = rule(...)`. rollup proves such a call side-effect-free by reading the factory, which is why it was already emitting 1,823 bytes — and why a single-bundler measurement would have shown no problem at all. esbuild and webpack do not do that analysis and keep the call. Thirty declarations now carry /*#__PURE__*/, which tsc preserves into the ESM emit, and all three bundlers now land within 20 bytes of each other. Measured, minified, esbuild / rollup / webpack: flattenErrors 287 / 292 / 291 one decorator 1837 / 1823 / 1818 validateSync 3722 / 3554 / 3823 toPlainSync 7744 / 7769 / 7832 toInstanceSync 7900 / 7942 / 7956 a typical DTO 10395 / 10402 / 10372 everything 26266 / 25671 / 26879 The serializer and deserializer drop independently — read JSON and you do not pay for writing it. Both mapping entry points keep the validator, because `validate` defaults to true and that is a real reference rather than a missed optimisation. The annotations are a promise to the bundler, so I checked the three factories they cover: rule, pattern and affix each return a closure and touch nothing outside themselves. A false promise here would mean silent deletion in someone else's production build. src/treeshake.test.ts pins the property. It asserts content rather than only bytes — it names the rules that must not appear — and one case asserts everything IS present when everything is used, so a "shaken" result cannot come from a bundle that failed to build. That mattered: two earlier passes at this measurement reported a clean sweep of shaken symbols because rollup had failed to resolve its entry and grep was reading missing files as absence. Strip the annotations and the test fails with `"must be a latitude" should have been shaken out`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK |
||
|
|
b4f0657d09 |
📦 feat: add cereale/min, and a verified framework guide
**cereale/min** — the library flattened into one minified ES module, 25.5 KB / 8.6 KB gzipped, for import maps, <script type="module">, Deno and Workers. Built from dist/esm/index.js, so the decorator lowering and the ES2025 target are tsc's; esbuild only flattens and minifies. It is an addition rather than a replacement, and the measurement is the reason. Bundled through esbuild the flat and per-module builds land within 2 bytes of each other; through rollup + terser the flat one is 165 bytes smaller; unused decorators tree-shake out of both. With the size argument a wash, per-module stays the default import for the one thing it does better — readable stack traces without source maps. (My first pass at that measurement reported "shaken" for every symbol because both rollup builds had failed and grep was reading missing files as absence. The check now asserts the bundle is non-empty and that a *used* symbol is present, so it can tell a real result from a broken harness.) **FRAMEWORKS.md** — a recipe per framework, each one run before it was written, with the versions and date verified against. Angular works, which was not obvious: the CLI scaffolds experimentalDecorators: true, but ngtsc erases @Component and @Injectable into static properties rather than leaning on TypeScript's decorator emit. Flip the flag and both systems coexist. Verified with ngc on Angular 21.2 with strictTemplates — templates still type-check and a wrong cereale rule is still TS1240 inside the Angular build. Next.js cannot work inline, structurally: it derives both the SWC parser's decorator support and the transform mode from the one flag, so on gives legacy emit and off makes @ a syntax error. NestJS cannot either — its DI needs design:type from emitDecoratorMetadata. Both have the same answer: keep the cereale classes in a package compiled by tsc and import the built output. Verified inside a program with BOTH legacy flags on, alongside @Injectable() — mapping and validation work, and the compile-time guarantee still holds where the rules are written. Also verified: Bun 1.3 needs no configuration, and a real Vite 8 build with the plugin works where the same build without it silently leaves decorator syntax in the bundle. Version 0.4.0: cereale/min is a new public entry point, and cutting a minor keeps the existing v0.3.0 tag meaningful instead of force-moving it onto a commit it was never cut from. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK |