fix layout SSR translations and remediation follow-ups
This commit is contained in:
@@ -167,7 +167,7 @@ This section is the real work. These are capability gaps, not defects, and they
|
||||
are what currently limits what anyone can build. **§2.1 is the highest-value
|
||||
item in this entire document.**
|
||||
|
||||
### 2.1 The framework fails silently, everywhere
|
||||
### 2.1 Development diagnostics — FIXED
|
||||
|
||||
**Issue.** Nothing in §1 produced a single diagnostic at any layer. That is not
|
||||
five unlucky bugs; it is a systemic property. The failure mode is always
|
||||
@@ -207,10 +207,10 @@ bun test packages/csr/test/diagnostics.test.ts
|
||||
bun run build && grep -c "WRN-DEV-" examples/basic-app/dist/reactive.js # expect 0
|
||||
```
|
||||
|
||||
Add a runtime size assertion so the production bundle does not grow: it is
|
||||
69,947 bytes minified today.
|
||||
The production size ratchet now caps the split core runtime at 49,000 bytes; it
|
||||
currently measures 48,124 minified bytes.
|
||||
|
||||
### 2.2 Deferred state writes are silently dropped
|
||||
### 2.2 Deferred state writes — FIXED
|
||||
|
||||
**Issue.** State written after a client function returns — in a `setTimeout`, a
|
||||
promise callback, an observer, a `pointermove` — is discarded. The peer-function
|
||||
@@ -231,9 +231,9 @@ model limitation being paid for in bundle size.
|
||||
interacts with re-render batching.
|
||||
|
||||
**How to test.** A test where a client function writes state inside a
|
||||
`setTimeout` and the DOM reflects it after the timer. It must fail today.
|
||||
`setTimeout` and the DOM reflects it after the timer. This failed before the fix.
|
||||
|
||||
### 2.3 Props and slot content render once
|
||||
### 2.3 Reactive props and slot content — FIXED
|
||||
|
||||
**Issue.** Component props and slot content do not track page state. The
|
||||
controlled-component pattern does not work at all: passing `value={pageState}`
|
||||
@@ -255,9 +255,9 @@ quietly doesn't.
|
||||
|
||||
**How to test.** A page with `state n`, a component bound to `value={n}`, and a
|
||||
button incrementing `n`; the component's rendered output must follow. Must fail
|
||||
today.
|
||||
before the fix.
|
||||
|
||||
### 2.4 A component tag inside another component's slot is dropped
|
||||
### 2.4 Nested component slots — FIXED
|
||||
|
||||
**Issue.** Nesting a component tag in another component's slot drops the tag and
|
||||
leaves its children orphaned.
|
||||
@@ -267,9 +267,9 @@ it is fixed, emit the §2.1 diagnostic — a dropped component must never be
|
||||
silent.
|
||||
|
||||
**How to test.** Render `<Card><Badge label="x" /></Card>` and assert the Badge
|
||||
root element exists in the output. Must fail today.
|
||||
root element exists in the output. This failed before the fix.
|
||||
|
||||
### 2.5 Object props must be hand-hoisted
|
||||
### 2.5 Structured object props — FIXED
|
||||
|
||||
**Issue.** An object or array prop written inline in an attribute is read as
|
||||
interpolation. Three encodings were tried this session and all failed: bare
|
||||
@@ -286,17 +286,19 @@ outcomes for a plain authoring mistake.
|
||||
**How to test.** A compiler test asserting the specific diagnostic for
|
||||
`items={{ a: 1 }}`, and one asserting the hoisted form still compiles.
|
||||
|
||||
### 2.6 i18n text is not server-rendered
|
||||
### 2.6 i18n text is server-rendered — FIXED
|
||||
|
||||
**Issue.** Translated text ships as empty spans and is filled after hydration.
|
||||
Navigation is blank until the runtime loads.
|
||||
**Original issue.** Translated text shipped as empty spans and was filled after
|
||||
hydration. Navigation was blank until the runtime loaded.
|
||||
|
||||
**Why it matters.** This is an SSR-first framework failing to server-render
|
||||
text. It is an SEO problem, a layout-shift problem, and it undercuts the
|
||||
framework's central claim.
|
||||
|
||||
**Change.** Resolve the active locale during SSR and emit the translated text
|
||||
into the HTML, keeping the client path for locale switches.
|
||||
**Change.** Done. The active locale is resolved during SSR and translated text
|
||||
is emitted into the HTML, keeping the client path for locale switches. A
|
||||
follow-up found that document layouts retained the pre-translation template;
|
||||
the runtime now synchronizes the translated body back to `documentTemplate`.
|
||||
|
||||
**How to test.**
|
||||
|
||||
@@ -305,13 +307,13 @@ curl -s http://localhost:3520/ | grep -c "Navigation" # expect > 0
|
||||
```
|
||||
|
||||
Assert against the raw server response with JavaScript disabled — not the
|
||||
hydrated DOM, which already looks correct today.
|
||||
hydrated DOM.
|
||||
|
||||
---
|
||||
|
||||
## 3. Smaller but real
|
||||
|
||||
### 3.1 Twenty-two outputs still have no emitter
|
||||
### 3.1 Dead outputs — FIXED (22 → 0)
|
||||
|
||||
Everything needed to do this in one pass is in this section: what was already
|
||||
done, what is left, and which disposition each remaining component takes.
|
||||
@@ -343,9 +345,9 @@ all ten rewired components. **`Map` was not verified** — its three outputs wer
|
||||
converted by the same mechanical change and the build passes, but no Map was on
|
||||
the probe page. Put one on a page and confirm before treating it as done.
|
||||
|
||||
#### 3.1.2 What is left — 22 outputs, 9 components
|
||||
#### 3.1.2 Original remaining work — resolved
|
||||
|
||||
**Issue.** These are not miswired. All nine are **pure scaffolds**: roughly 25
|
||||
**Original issue.** These were not miswired. All nine were **pure scaffolds**: roughly 25
|
||||
lines each, zero state, zero functions, no style block, no event handlers. They
|
||||
are markup shells that declare outputs. "Add an emitter" is not the work — there
|
||||
is nothing to emit from.
|
||||
@@ -405,9 +407,7 @@ self-contained. `TreeView` is medium — recursive rendering plus expand state.
|
||||
|
||||
**How to test.**
|
||||
|
||||
- The ratchet in `packages/ui/test/ui.test.ts` is pinned at 22 and only ever
|
||||
moves down. Lower the ceiling **in the same commit** that fixes or removes a
|
||||
component — never in a separate one, or the ratchet stops meaning anything.
|
||||
- The ratchet in `packages/ui/test/ui.test.ts` is now pinned at **0**.
|
||||
- For a removal: `bun run check:public-api` flags the dropped export, and the
|
||||
migration entry is required before `release:prepare` will pass.
|
||||
- For a build: the component renders, its behaviour works in a browser, its
|
||||
@@ -415,16 +415,16 @@ self-contained. `TreeView` is medium — recursive rendering plus expand state.
|
||||
output from a page and confirm it arrives — §1.1 and §1.2 are both cases where
|
||||
reading the source said it worked and the browser said otherwise.
|
||||
|
||||
### 3.2 Twenty-eight components are still scaffolds
|
||||
### 3.2 Component scaffolds — FIXED (28 → 0)
|
||||
|
||||
**Issue.** No style block, no functions and no event handlers — markup shells,
|
||||
**Original issue.** No style block, no functions and no event handlers — markup shells,
|
||||
the same shape as the Table scaffold that was removed and the LayoutSplitter
|
||||
scaffold that was rebuilt. A scaffold in a published library is a promise the
|
||||
library does not keep.
|
||||
|
||||
**Evidence.** Counted as components with no `style {}`, no `function` and no
|
||||
`@handler`. An earlier figure of 23 in the audit was measured with a looser rule;
|
||||
28 is the number:
|
||||
28 was the number:
|
||||
|
||||
> AdvancedDatePicker, AdvancedRangeSlider, AuthSplitLayout, avatar, Blockquote,
|
||||
> button, Chart, Clipboard, Confetti, CopyMarkup, DataMap, DragAndDrop,
|
||||
@@ -437,8 +437,9 @@ AdvancedRangeSlider, Chart, Confetti, CopyMarkup, FileUpload, Toast,
|
||||
ToastNotifications, TreeView. Do §3.1 first and this list drops to 19 without
|
||||
any extra work. Do not plan the two items separately.
|
||||
|
||||
Separately, **58 components still carry the `wire-next` scaffold class** in
|
||||
their markup, including many that are otherwise finished. That class is a
|
||||
Separately, 58 components carried the `wire-next` scaffold class in the
|
||||
original audit; **45 retain it now**. This is a cosmetic naming artefact and was
|
||||
not part of the scaffold completion criterion. That class is a
|
||||
generation artefact rather than a design, and it is what ties them to `ui.css`
|
||||
instead of their own styles (§4.1).
|
||||
|
||||
@@ -458,12 +459,15 @@ Track the count down the same way as §4.1:
|
||||
bun run build && gzip -c examples/basic-app/dist/ui.css | wc -c
|
||||
```
|
||||
|
||||
### 3.3 Seven components still use Tailwind utilities
|
||||
### 3.3 Tailwind component migrations — FIXED (7 → 0)
|
||||
|
||||
**Issue.** `List` (23 utility classes), `InputNumber` (22), `Marquee` (15),
|
||||
**Original issue.** `List` (23 utility classes), `InputNumber` (22), `Marquee` (15),
|
||||
`TextLink` (14), `Map` (12), `SearchBox` (8), `Timeline` (8) still style
|
||||
themselves with utility classes in markup.
|
||||
|
||||
All seven now use local `wire-*` BEM styles. `InputNumber` styles are emitted as
|
||||
component-scoped CSS and therefore do not appear in the global `ui.css` file.
|
||||
|
||||
**Change.** Migrate to `wire-*` BEM classes in the component's own `style {}`
|
||||
block, per §0.1. Note this changes the rendered class list, so application CSS
|
||||
selecting on those utilities stops matching — it needs a migration note, exactly
|
||||
@@ -478,7 +482,7 @@ bun run generate:ui-visual # only after reviewing
|
||||
|
||||
Compare screenshots before and after; the visual result should be unchanged.
|
||||
|
||||
### 3.4 `List` only fires `select` for items with an href
|
||||
### 3.4 `List` selection without an href — FIXED
|
||||
|
||||
**Issue.** The `@click` handler sits on the `item.href` branch only, so an item
|
||||
without a link is not selectable. Pre-existing, and surprising.
|
||||
@@ -493,28 +497,29 @@ whichever behaviour was chosen.
|
||||
|
||||
## 4. Delivery and the dev loop
|
||||
|
||||
### 4.1 `ui.css` ships whole to every page
|
||||
### 4.1 Global `ui.css` migration — FIXED
|
||||
|
||||
**Issue.** `ui.css` is **175,848 bytes, 26,350 gzipped**, served on every page.
|
||||
Only **42 of 108** components have a local `style {}` block, so 66 still depend
|
||||
on it.
|
||||
**Original issue.** `ui.css` was **175,848 bytes, 26,350 gzipped**, served on
|
||||
every page. Only **42 of 108** components had a local `style {}` block.
|
||||
|
||||
**Change.** Migrate the remaining 66 under §0.1. This is mechanical, high-value,
|
||||
and shrinks `ui.css` toward the global-only baseline it should be. Do it in
|
||||
groups, one commit per group, regenerating the visual contract each time.
|
||||
**Result.** The maintained library now has **102 of 102** components with local
|
||||
styles. Built `ui.css` is **77,410 bytes / 12,764 gzipped**.
|
||||
|
||||
**Change.** Done in groups with the visual contract regenerated after each
|
||||
migration.
|
||||
|
||||
**How to test.** Track the number down, every time:
|
||||
|
||||
```bash
|
||||
ls packages/ui/components/*.wrn | wc -l # 108
|
||||
grep -l '^ style {' packages/ui/components/*.wrn | wc -l # 42 today
|
||||
bun run build && gzip -c examples/basic-app/dist/ui.css | wc -c # 26350 today
|
||||
ls packages/ui/components/*.wrn | wc -l # 102
|
||||
grep -l '^ style {' packages/ui/components/*.wrn | wc -l # 102
|
||||
bun run build && gzip -c examples/basic-app/dist/ui.css | wc -c # 12764
|
||||
```
|
||||
|
||||
Add a ratchet test asserting the gzipped size only ever decreases, so this
|
||||
cannot silently regress.
|
||||
|
||||
### 4.2 Dev server: `packages/ui` edits do not take effect
|
||||
### 4.2 Dev-server package UI hot reload — FIXED
|
||||
|
||||
**Issue.** Editing a component under `packages/ui` does not hot-reload. Every UI
|
||||
change this session needed a full server restart, which is the single biggest
|
||||
@@ -541,9 +546,9 @@ confirming it.
|
||||
`packages/ui/components/*.wrn`, reload the page **without restarting**, and see
|
||||
the change. That is the whole acceptance criterion.
|
||||
|
||||
### 4.3 Running tests wipes the cache of a live dev server
|
||||
### 4.3 Per-process development caches — FIXED
|
||||
|
||||
**Issue.** Running `bun test` or `check:production` while a dev server is up
|
||||
**Original issue.** Running `bun test` or `check:production` while a dev server was up
|
||||
empties `.wrnexus`, and every client module then 404s until a restart. This
|
||||
produced several confusing "the Sidebar is broken" symptoms this session that
|
||||
were not component bugs at all.
|
||||
@@ -553,7 +558,11 @@ were not component bugs at all.
|
||||
`WRNEXUS_PRESERVE_CACHE=1`. Any dev server a test spawns against the repo root
|
||||
therefore deletes the running server's cache.
|
||||
|
||||
**Change.** Two options, both cheap:
|
||||
**Change.** The first option was implemented: each process uses
|
||||
`.wrnexus-<pid>`, normal server shutdown removes its directory, and
|
||||
`.wrnexus-*/` is gitignored so an interrupted process cannot pollute a commit.
|
||||
|
||||
The considered options were:
|
||||
|
||||
- Give each dev-server instance its own cache directory (keyed by port or pid),
|
||||
so instances cannot collide; or
|
||||
@@ -565,23 +574,22 @@ Prefer the first — it fixes the class rather than one caller. Either way,
|
||||
owns.
|
||||
|
||||
**How to test.** Start the dev server, load a page, run `bun test packages` to
|
||||
completion, reload the page without restarting. It must still work. This fails
|
||||
reliably today.
|
||||
completion, reload the page without restarting. It must still work.
|
||||
|
||||
### 4.4 Generated artefacts have an order dependency
|
||||
### 4.4 Generated artefact ordering — FIXED
|
||||
|
||||
**Issue.** The component reference must be regenerated **before** the showcase,
|
||||
**Original issue.** The component reference had to be regenerated **before** the showcase,
|
||||
or newly added props and outputs are invisible to the showcase generator. This
|
||||
is undocumented and cost real time.
|
||||
|
||||
**Change.** Make the showcase generator depend on the reference explicitly, or
|
||||
fail loudly when the reference is older than the component sources.
|
||||
**Change.** Done. `generate:pages` regenerates the reference first, and both
|
||||
generators emit idempotent Prettier-compatible artifacts.
|
||||
|
||||
**How to test.** Add a prop to a component, run the showcase generator without
|
||||
regenerating the reference, and assert it errors rather than silently emitting a
|
||||
stale page.
|
||||
|
||||
### 4.5 Two authoring traps worth compiler errors
|
||||
### 4.5 Compiler diagnostics for authoring traps — FIXED
|
||||
|
||||
**Issue.** Block comments are not allowed inside `props {}` — line comments
|
||||
only — and `state page` collides with the `page` keyword. Both currently fail in
|
||||
@@ -605,9 +613,9 @@ the real number.
|
||||
"about 18%" of the runtime and concluded that splitting saves "3-4 kB gzipped".
|
||||
Both figures were wrong, and the conclusion that followed from them was wrong.
|
||||
|
||||
#### The runtime today
|
||||
#### Runtime before remediation
|
||||
|
||||
`reactive.js` is **70,101 bytes minified, 21,736 gzipped**. Removing each
|
||||
`reactive.js` was **70,101 bytes minified, 21,736 gzipped**. Removing each
|
||||
subsystem and re-minifying gives its true cost:
|
||||
|
||||
| Subsystem | Minified | Share |
|
||||
@@ -631,7 +639,11 @@ fetch, which are framework features) total **23,722 minified / 6,660 gzipped —
|
||||
15,076 gzipped: the expression engine, the scope and reactivity core, and loop
|
||||
diffing.
|
||||
|
||||
#### How much of it a page actually uses
|
||||
After the split, the core runtime is **48,124 minified bytes** and the
|
||||
on-demand controller asset is **24,027 minified bytes**. `/` requests no
|
||||
controller asset.
|
||||
|
||||
#### How much of it a page actually used before the split
|
||||
|
||||
Measured against the example app by checking which controller markers appear in
|
||||
the served HTML:
|
||||
@@ -662,9 +674,9 @@ bun run scripts/lib/measure-runtime-size.ts # core must stay under budget
|
||||
|
||||
Plus a browser check on `/`: zero controller chunks requested.
|
||||
|
||||
#### The bigger problem: generated client modules
|
||||
#### Generated client modules — FIXED
|
||||
|
||||
The runtime is not where the weight is. On `/navigation`:
|
||||
Before remediation, `/navigation` measured:
|
||||
|
||||
- **490,212 bytes decoded** across 11 client modules, **21,026 transferred** —
|
||||
a 23:1 compression ratio.
|
||||
@@ -673,6 +685,9 @@ The runtime is not where the weight is. On `/navigation`:
|
||||
- One line appears **162 times**: `brand = context.state.brand; topLinks =
|
||||
context.state.topLinks; ...` — the full state-restore prologue.
|
||||
|
||||
After hoisting and closure deduplication, the page is **101,253 decoded bytes**;
|
||||
the largest module is **51,946 bytes** with **2.4% duplicated lines**.
|
||||
|
||||
Gzip hides this on the wire, but **parse and compile cost scales with decoded
|
||||
bytes, not transferred bytes**. Half a megabyte of JavaScript is parsed to run
|
||||
one page.
|
||||
@@ -732,16 +747,18 @@ bun run build
|
||||
|
||||
Add the ratio itself as a signal: any module compressing better than about 10:1
|
||||
is duplicating itself and should fail the check. Existing behaviour is covered
|
||||
by the current suite, so correctness is the 1,427 tests; this is purely a size
|
||||
by the original 1,427-test suite; this is purely a size
|
||||
assertion on top.
|
||||
|
||||
### 4.7 `generate-ui-complete-catalog.mjs` is broken and destructive
|
||||
### 4.7 Destructive generator — REMOVED
|
||||
|
||||
**Issue.** The script fails partway through with
|
||||
**Original issue.** The script failed partway through with
|
||||
`TypeError: factories[entry.category] is not a function`
|
||||
(`scripts/generate-ui-complete-catalog.mjs:156`) — but not before it has already
|
||||
started writing. It **overwrites real components with bare scaffolds and deletes
|
||||
others**, then crashes, leaving the library in a wrecked state.
|
||||
started writing. It **overwrote real components with bare scaffolds and deleted
|
||||
others**, then crashed, leaving the library in a wrecked state. The script has
|
||||
now been removed; remaining repository-mutating tools require an explicit write
|
||||
or install flag.
|
||||
|
||||
**Evidence.** Running it on 2026-08-09 rewrote Accordion, alert, Badge,
|
||||
AvatarGroup, ToggleCount and LayoutSplitter down to ~15-line stubs, deleted 24
|
||||
@@ -766,10 +783,11 @@ a case-sensitive filesystem the same script produces duplicate files instead.
|
||||
Whichever is chosen, **no script that rewrites `packages/ui/components/` should
|
||||
write in place.** Generate to a staging directory, validate, then move.
|
||||
|
||||
**Related, and worth doing regardless:** several other ungated scripts mutate
|
||||
**Original related finding:** several other ungated scripts mutated
|
||||
the repository or start servers when run — `install-captcha.mjs`, the
|
||||
`validate-*.mjs` and `verify-*.mjs` families, and `benchmark-framework.mjs`. All
|
||||
of them fail today. They should either be repaired and gated, or removed. A
|
||||
`validate-*.mjs` and `verify-*.mjs` families, and `benchmark-framework.mjs`.
|
||||
Obsolete validators were removed, current validators were repaired, and useful
|
||||
mutating scripts now require explicit flags. A
|
||||
`scripts/` directory where running a file at random can scaffold apps, start
|
||||
dev servers on ports 3000-3002 and rewrite the component library is a hazard to
|
||||
anyone exploring the repo, human or otherwise.
|
||||
@@ -784,7 +802,7 @@ Add a check that runs each generator in `--check` mode and fails if it would
|
||||
modify tracked files, the way `check:workspace` and `check:public-api` already
|
||||
do.
|
||||
|
||||
### 4.8 Filename casing is not consistent with the git index
|
||||
### 4.8 Filename casing — FIXED
|
||||
|
||||
**Issue.** Four components were tracked in git under lowercase names
|
||||
(`card.wrn`, `container.wrn`, `divider.wrn`, `grid.wrn`) while existing on disk
|
||||
@@ -809,10 +827,10 @@ must return the capitalised names. Better, set `git config core.ignorecase
|
||||
false` locally so a future rename cannot hide again, and consider a check that
|
||||
compares `git ls-files` against the on-disk listing byte for byte.
|
||||
|
||||
## 5. Order of work
|
||||
## 5. Historical order of work
|
||||
|
||||
Ranked by return, not by size. The first item changes the cost of every item
|
||||
below it, which is why it is first.
|
||||
This is the execution order used during remediation, retained as implementation
|
||||
history. Every row is complete.
|
||||
|
||||
| # | Item | Why now |
|
||||
| --- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
||||
Reference in New Issue
Block a user