Skip to content

docs(changelog): scope the 4.4.0 entry to the patterns addition - #67

Merged
hexplus merged 9 commits into
mainfrom
chore/warning-improvement-islands
Sep 7, 2026
Merged

docs(changelog): scope the 4.4.0 entry to the patterns addition#67
hexplus merged 9 commits into
mainfrom
chore/warning-improvement-islands

Conversation

@hexplus

@hexplus hexplus commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Description

Patterns reach no-build pages for the first time, plus two latent bugs the work uncovered and an example that no longer needs a build step.

cdn.full.global.js — patterns for no-build pages

A <script> tag resolves no specifiers, so a no-build page could not reach sibujs/patterns at all: machine and its siblings were unavailable to exactly the audience islands are aimed at. They now ship in a second CDN bundle — a superset of the default one, same Sibu global, namespace kept as Sibu.patterns, core spread last so it wins any collision. New export paths: sibujs/cdn-full and sibujs/cdn-full-dev.

A separate artifact rather than a merge. Merging patterns into cdn.global.js charged every no-build page +13.0% gzip for code it never calls. That was measured, caught in review, and reverted before release.

Sizes are zlib level 9, matching the budget test — the default level is not reproducible across zlib builds:

bundle before after
cdn.global.js 80,202 B / 26,330 B gzip 76,490 B / 26,118 B (−4.6% / −0.8%)
cdn.dev.global.js — / 30,199 B gzip 85,526 B / 30,005 B (−0.6%)
cdn.full.global.js 85,972 B / 29,605 B
cdn.full.dev.global.js 95,539 B / 33,661 B

The default bundle came out smaller than it went in, because it also lost esbuild's globalName wrapper. A test asserts both raw ≤ 80,202 B and gzip ≤ 26,330 B + 2%, because raw size can stay flat while transfer size grows if the new bytes compress worse.

Fix: validateProps and assertType ran in production browsers

patterns/contracts.ts read process.env.NODE_ENV directly. That is not a define target, and process does not exist in a browser — which the guards read as development: validateProps validated and warned on every production page, and assertType threw in exactly the builds its doc comment promised it would be a no-op. Latent for as long as the file existed, invisible while the module was bundler-only.

Fixing it properly took three passes, each caught in review:

  1. Gating on the imported DEV const made the behaviour right but left the code in the bundle — esbuild folds the const after dead-code elimination, emitting if (!1) { … throw new TypeError(\[SibuJS Contract] ${r}`) }. Both gates now lead with a bare SIBU_DEV`, which is substituted early.
  2. Moving validation to a second pass removed the dev-only errors allocation but reordered user callbacks — defaults and validators are caller-supplied, so a later property's factory stopped seeing what an earlier property's validator wrote. It is now two whole loops, one per mode, each finishing a property before starting the next.
  3. A shared applyDefault helper still normalized shorthand entries into { type: def } in production — validation-only work, and a place an allocation hides from a toString() guard. Both paths are inlined and the production loop skips shorthand entries outright.

Production now contains the props copy, one schema-entry iteration, and object-form default application. No validation-only allocations, validator calls, warnings, or internal contract diagnostic strings remain. Shorthand validator entries are skipped without normalization:

function wr(e,n){let t={...e};for(let[r,o]of Object.entries(n)){if(typeof o=="function")continue;let i=o.default;t[r]==null&&i!==void 0&&(t[r]=typeof i=="function"?i():i)}return t}

Behaviour change worth knowing: a browser build that defines nothing now treats itself as production, so these two stop validating there. That matches every other diagnostic in the library and the functions' documented contract, but it is a real change for anyone relying on the accident. Consumers who define __SIBU_DEV__ are unaffected.

Relatedly, a default factory that depends on a validator's side effect will differ between modes, because validation is compiled out. The JSDoc now says so.

Not asserted absent: "… is required" and "… must be one of:". Those are the return values of the exported validators.required and validators.oneOf, which run in production by design.

Fix: the CDN builds no longer take esbuild's globalName

globalName: "Sibu" emits var Sibu = (() => { … })() after the module body, overwriting whatever the body installed with the module's export namespace. Harmless while the two matched; silently wrong the moment a bundle merged anything in. Both entry points now assign globalThis.Sibu themselves — which also makes them self-register in a worker, and ~3.7 KB smaller.

The chess example runs from the CDN, with no build step

It imported ../../dist/index.js, so it only ran after npm run build — a build step in the one demo whose subject is that islands need none. It now loads cdn.full.global.js from a <script> tag and takes machine from Sibu, so it exercises the artifact this PR adds, with no local reduced copy left behind. The missing-runtime error names the full bundle, since the core-only one would install Sibu and leave machine undefined. Setup dropped from four commands to one.

Also in the example:

  • Captured-piece trays no longer outline the none placeholder. ctx.attr serializes false to the string "false", so a presence selector matched both states. The stylesheet matches on the value, which also keeps the server-rendered markup correct before hydration.
  • Palette reworked to the site's indigo scale. The outline glyphs (♙♘♗♖♕♔) are hollow and show the square beneath, so a white piece only looked white on a pale square; both sides now render solid glyphs coloured by data-side.

Related Issue

Closes #

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Checklist

  • I have read CONTRIBUTING.md
  • My code builds without errors
  • I have tested my changes
  • I have updated documentation if needed

Verification

  • npm run build — 7 build steps, clean
  • npm run typecheck / npm run typecheck:tests / npm run lint — clean, 682 files
  • npm test6552 passing, 1 skipped
  • npx playwright test --project=chromium125 passing (Playwright 1.61.1)

Several test changes fix tests that passed while proving nothing. Each was verified by mutation — breaking the thing under test and confirming the assertion fails:

  • Behaviour over string-matching. The hand-maintained diagnostic marker list missed the validateProps leak for a release. Tests now run the published IIFEs and pass a spy validator, asserting it is never invoked in production with the dev bundle as positive control — "it did not warn" also passes for a branch that ran and stayed quiet.
  • A regression test for callback ordering, asserting both the call sequence and the resulting value, so a future "optimisation" back to two passes fails rather than silently reordering user code.
  • The smoke test was reading prose. html.indexOf("cdn.global.js") matched the explanatory HTML comment above the tag — and could never have matched the real tag, since "cdn.full.global.js" does not contain that substring. Detection is now attribute-level over comment-stripped HTML, with seven extraction tests at module scope that run without a server or build.
  • The browser suite was testing the wrong code. Once the example loaded from unpkg it exercised the published release, not the working tree. Requests are intercepted and served from dist/, every request is recorded, and toEqual([EXPECTED_CDN]) rejects duplicates and extras — a scalar would have accepted a page downloading two runtimes if the expected one came last. Filenames are checked against an allowlist before becoming a filesystem path.
  • Piece assertions check colour, not just shape — one glyph per type means toHaveText("♞") passes for a black knight too, and the promotion test exists to prove hxg8=N produces a white one.
  • The chess smoke test asserts the inverse of what it used to — no /dist/ in the island's import graph — plus tests that the runtime tag precedes the deferred island and that the missing-runtime error names the full bundle.
  • tests-browser/cdn-full.spec.ts covers the new artifact in a real browser: machine driving a core effect across the patterns/core boundary, and the negative that cdn.global.js exposes neither machine nor patterns.

@hexplus
hexplus merged commit 02935f8 into main Sep 7, 2026
5 checks passed
@hexplus
hexplus deleted the chore/warning-improvement-islands branch September 7, 2026 16:58
Sign up for free to 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.

1 participant