v4.3.0 — the theme/engine split is complete (#174)
This release finishes the work that makes one promise to every downstream repo: upgrading upstream never costs you your theme again.
Why upgrades used to lose your UI
Your custom look lived in forked engine files — the layout, the homepage, checkout. Every git merge upstream/main hit conflicts in those exact files, and every resolution either dropped your brand (upstream side) or silently dropped upstream's fixes (your side). Measured on a real fork: the homepage alone diverged 23 → 592 lines.
What replaces that, as of this release
Your theme now lives in code upstream never touches, registered through seams the engine promises to keep:
| Your customization | Where it lives now |
|---|---|
| Header / footer | setChrome({ header, footer }) — components in src/lib/deployment/ |
| Homepage | setChrome({ home }) — the route file is engine-owned; your markup is a registered component |
| Checkout fields (e.g. Thai tax invoice) | registerCheckoutSlots() — three slots, contribution flows into billingAddress end-to-end |
| Fonts, meta, verification tags | src/app.head.html — injected on every page, deployment-owned |
| Colors, radius, display font, logo | /admin/settings theme tokens — operator config, zero code |
| Everything else visual | README.md, wrangler.toml, src/lib/deployment/** are fork-side by contract |
All registrations go in src/lib/plugins/registrations.ts (loaded by server and client — registering elsewhere causes SSR-then-snap-back; see chrome.ts).
The guarantee, in writing and in CI
docs/THEME-CONTRACT.md— the full contract: every seam above, the props your components receive, Paraglide message-key stability (a shipped key is never removed within a MAJOR), which files are fork-side, and the route add/add precedence rule.THEME_CONTRACT_VERSION = 1.0.0(src/lib/theme-contract.ts) — removals of any contract surface are a MAJOR bump; additions are MINOR.pnpm run guard:contractruns in CI on every commit: if an engine change deletes anything a theme may depend on (a slot, a props field, a message key, a building-block component), the build fails with the item named until the MAJOR is bumped explicitly. A contract break can no longer ship as a quiet refactor.
How to upgrade a themed fork to this release
- Merge:
git fetch upstream && git merge upstream/main. Expect conflicts ONLY in files you own (README.md,wrangler.toml, and — one last time — any engine file you forked for looks). Keep your side for the first two. - For each engine file you forked visually (homepage, header, footer): move your markup into
src/lib/deployment/YourComponent.svelte, take upstream's version of the engine file, and register yours:then// src/lib/deployment/chrome.ts import { setChrome } from "$lib/components/www/chrome"; import YourHome from "./YourHome.svelte"; setChrome({ home: YourHome });
import "$lib/deployment/chrome";fromsrc/lib/plugins/registrations.ts. - Move colors/radius/fonts you had hard-coded in CSS into
/admin/settingstheme tokens where possible. - Run the gate:
pnpm test && pnpm run guard:contract && pnpm build. - Done — your next upstream merge should be conflict-free. The reference fork (
codustry/khaopad-example) did exactly this migration in this release cycle, and its Step-7 sync was the first fully mechanical, zero-conflict merge in the project's history.
Worked example: khaopad-example@cc1e16b is the complete homepage migration, commit-sized.
Also in this release
- Step 5 (#187) — theme tokens as operator config: background/foreground/accent colors, radius, display font, all strictly validated server-side and at render (nothing style-breaking can reach the style attribute), SSR-first so no flash of default theme.
- Step 6 (#188) — homepage behind the seam, SSR-verified byte-identical for unthemed installs.
- Step 7 (#189) — the contract: docs, version constant, CI guard (floor semantics, negative-tested).
- From the v4.2.x line already on main: tax-entity fields persist end-to-end on billing addresses (#171/#185), credential endpoints rate-limited (#182/#184/#186),
/admin/profilefor self-service password change (#178).
1007 tests; CSS-inventory, head, and contract guards all green; deployed and verified on the reference deployment.