Skip to content

research: fragility audit + production-readiness hardening (router is the hotspot, keep auto-elision) #976

Description

@vivek7405

Problem

Production-readiness question raised: what is the most fragile subsystem of WebJs, is it elision, and should auto-elision be replaced with a manual 'use client'-style opt-in? This issue records the investigation and scopes the hardening needed before calling the framework production ready.

Findings

Two evidence sweeps were run: a full git log fix-clustering pass (259 fix: commits across 1172 total) and a code read of the elision analyzer.

Elision is not the most fragile feature. The client router / soft-nav reconciliation is, by a wide margin:

Subsystem fix: count
Client router / reconciliation / navigation 37
CLI / scaffold 31
Hydration / components / signals 22
SSR / streaming / suspense 15
Dev server / watch / reload 12
Caching 10
Serializer / rich types 9
TypeScript stripper 8
Server actions / RPC / seed 6
Elision 5
Bun parity 4

The router is still generating bugs recently (#907 reconcile-into-hydrated-components, #910 slot re-projection on reuse, #936 stylesheet stripping + fragment parse context). Recurring theme: a soft nav corrupting already-hydrated DOM.

On the 'use client' question: keep auto-elision, do not switch. Rationale:

Correctness observation worth flagging: because WebJs ships raw Web Components plus request-time type stripping plus automatic elision instead of a compiler that would fail naturally, a lot of correctness is outsourced to 19 check.js lint rules. Guardrail trust therefore matters more than in a compiled framework.

Answers to the open questions (full detail in the deep-dive comments below)

The five open questions have been investigated. Each has a dedicated deep-dive comment on this issue with file:line references.

  • Router reconciliation invariants + fuzz harness (comment 1). Derived the full 21-invariant set for packages/core/src/router-client.js, each mapped to its enforcing code and its origin (roughly half incident-hardened, half by design). A property-based harness is feasible and low-friction: the module already exports a test seam (_applySwap, _reconcileChildren, _diffElementInPlace, _keyOf, _LIVE_ATTRS, nav-token controls at L3541-3622) that the existing 145 KB unit suite drives under linkedom. Recommended shape: tier 1 fast-check + linkedom over the pure reconciler (identity / LIVE_ATTRS / permanents / head merge oracles); tier 2 a small browser generative harness for the ~5 invariants linkedom cannot express (DSD setHTMLUnsafe, real slot reprojection, hydration symbol, shadow upgrade, view transitions).
  • e2e nav-matrix holes (comment 2). Coverage is dense on the incident clusters; 10 holes remain, top priority: live view-transition nav (only stubbed today), shadow-DOM preservation across a soft nav, streamed Suspense under popstate / abort, the 422 page-action swap into a deep nested layout, and any focus-invariant assertion (zero today).
  • Elision contract tests (comment 3). The static interactive override and static shadow always-ship rule ARE analyser-tested, but the two documented residuals are unasserted: no test uses a runtime-computed .register(tagVar) tag, none pairs that shape with the override rescuing it, none covers the external-stylesheet :defined residual, and no serve/e2e test proves the override verdict is honoured end-to-end (no fixture app has a static interactive = true component). Confirmed the differential guard masks the whole JS-loaded set, so over-ship is invisible to it by construction.
  • Dev-time elision visibility (comment 4). Verdicts are already in scope at first request (dev.js:882-884, the same if (dev) block that logs orphan warnings + timing), so a server-console first-request summary is trivial and a browser-console push is small (a dev-only inline script at boot emission, or a new SSE event on the existing SseHub, mirroring the webjs-error overlay channel).
  • Production-readiness scorecard (comment 5). Only 7 open issues repo-wide; the sole live code debt in non-router subsystems is the streaming-RPC e2e flake (de-flake the streaming-RPC e2e (mid-stream snapshot races the stream end) #972/dogfood: de-flake the #489 streaming-RPC e2e (mid<final race) #973). Ratings: serializer 5/5, caching 5/5, TS stripper 5/5, core hydration 4.5/5, Bun parity 4/5 (one documented permanent gap: 103 Early Hints), actions/RPC/CSRF/seed 4/5 (flaky streaming e2e), dev server 4/5 (most-iterated; dogfood: dev live-reload infinite loop (browser refreshes ~5x/sec), reconnect treated as edit #968 closed with no named regression guard).

Recommended hardening (recorded here, not yet filed)

Left unfiled at the maintainer's direction; capture the candidates for later triage.

  1. Router reconciliation property / fuzz harness (tier 1 fast-check + linkedom over the exported seams; tier 2 browser generative for the DSD / slot / hydration / stylesheet / view-transition invariants).
  2. Fill the 10 e2e soft-nav holes, prioritizing shadow-DOM across soft nav, streaming under popstate / abort, live view transitions, the nested-layout 422, and focus.
  3. Elision residual contract tests (dynamic-tag elided without override / ships with static interactive = true; external-stylesheet :defined; an end-to-end override-ships fixture in examples/blog or test/e2e/fixtures/).
  4. Dev-time elision visibility (first-request server-console summary + a browser-console push, reusing the verdicts at dev.js:882-884).
  5. Misc hardening: a generated-corpus parity fuzz for the TS stripper, a named regression guard for the dogfood: dev live-reload infinite loop (browser refreshes ~5x/sec), reconnect treated as edit #968 dev reconnect loop, and a Tailwind stale-CSS watcher-crash test.
  6. Streaming-RPC e2e flake is already tracked as de-flake the streaming-RPC e2e (mid-stream snapshot races the stream end) #972/dogfood: de-flake the #489 streaming-RPC e2e (mid<final race) #973; no new issue needed.

Implementation notes (for a later implementing agent)

This is a research / decision record plus a scoping issue. It stays In Progress while the hardening direction is confirmed.

Metadata

Metadata

Assignees

Labels

researchResearch/design/decision record (no code); filter these to read design history

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions