You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
The analyzer is engineered fail-safe: every ambiguity ships JS. A manual annotation inverts the failure mode onto every app author, and in a no-build framework there is no compiler to catch a forgotten annotation, so a missed "ship me" directive fails silently (SSRs fine, never hydrates). That is exactly the failure elision's conservatism avoids.
'use client' semantics do not map. WebJs modules are isomorphic, there is no server/client component split to annotate.
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).
Recommended hardening (recorded here, not yet filed)
Left unfiled at the maintainer's direction; capture the candidates for later triage.
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).
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.
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/).
Dev-time elision visibility (first-request server-console summary + a browser-console push, reusing the verdicts at dev.js:882-884).
Differential guard: test/elision/differential-elision.test.js, plus e2e cases in test/e2e/e2e.test.mjs (gated WEBJS_E2E=1).
Landmine: the differential test masks the JS-loaded set (importmap, boot script, preload hints) so only the dangerous direction (SSR output changed) can fail. Over-ship is invisible to it by design.
Dev-visibility verdicts: dev.js:882-884 (compute), L2146-2148 (SSR emit), L1001 (timing log seam). SseHub at listener-core.js:343; webjs-error browser listener at dev.js:2842.
This is a research / decision record plus a scoping issue. It stays In Progress while the hardening direction is confirmed.
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 logfix-clustering pass (259fix: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:
fix:countThe 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:'use client'semantics do not map. WebJs modules are isomorphic, there is no server/client component split to annotate.test/elision/differential-elision.test.js), the lifecycle/sigil drift guards, and scanner fuzzing. The two remaining wrongly-elide residuals (a runtime-computed tag string, an external-stylesheet:definedrule) already have thestatic interactive = trueescape hatch.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.jslint 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.
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 1fast-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 (DSDsetHTMLUnsafe, real slot reprojection, hydration symbol, shadow upgrade, view transitions).static interactiveoverride andstatic shadowalways-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:definedresidual, and no serve/e2e test proves the override verdict is honoured end-to-end (no fixture app has astatic interactive = truecomponent). Confirmed the differential guard masks the whole JS-loaded set, so over-ship is invisible to it by construction.dev.js:882-884, the sameif (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 existingSseHub, mirroring thewebjs-erroroverlay channel).Recommended hardening (recorded here, not yet filed)
Left unfiled at the maintainer's direction; capture the candidates for later triage.
fast-check+ linkedom over the exported seams; tier 2 browser generative for the DSD / slot / hydration / stylesheet / view-transition invariants).static interactive = true; external-stylesheet:defined; an end-to-end override-ships fixture inexamples/blogortest/e2e/fixtures/).dev.js:882-884).Implementation notes (for a later implementing agent)
packages/server/src/component-elision.js(~1259 lines), the signal list is inanalyzeComponentSource()andanalyzeElision(). Import-only emission (Elide page/layout modules that ship only to import interactive components #605) and inert classification (Stabilize: elision analyzer masks comments/strings (no false signals from prose) #179) live inanalyzeElision()around L1135. Thestatic interactive/static shadowsignals areINTERACTIVITY_STATIC_FIELDSat L153, applied L570-575. Escape-hatch env reads inpackages/server/src/dev.js(readElideEnabled,elideEnvOverride).packages/core/src/router-client.js. Recent fix landmarks: fix: client router must not reconcile into hydrated components #907, fix: client router re-projects slotted content of a reused component #910, dogfood: CSS drops on client-router soft nav on real Android Chrome (styled on refresh) #936. Test seam re-exports at L3541-3622.test/elision/differential-elision.test.js, plus e2e cases intest/e2e/e2e.test.mjs(gatedWEBJS_E2E=1).dev.js:882-884(compute), L2146-2148 (SSR emit), L1001 (timing log seam).SseHubatlistener-core.js:343;webjs-errorbrowser listener atdev.js:2842.This is a research / decision record plus a scoping issue. It stays In Progress while the hardening direction is confirmed.