Skip to content

Commit 482d944

Browse files
committed
update to universal cbi improvement
1 parent d6deeec commit 482d944

1 file changed

Lines changed: 46 additions & 1 deletion

File tree

lang/docs/universal-cbi-improvements.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,52 @@ Tests are golden-string comparisons of generated JS text (`lang/tests/compiler_p
411411
Add: (1) structural tests asserting `switch`/`while`/`throw`/`%` appear in output (or that a diagnostic
412412
fires); (2) a headless-DOM harness that executes the generated JS and asserts the hydrated DOM (this is
413413
how the todo demo was validated during this analysis); (3) SSR-content tests (`<h3>5 left</h3>`, not
414-
`<h3> left</h3>`); (4) move every `universal_failures.md` case into the suite.
414+
`<h3> left</h3>`); (4) move every `universal_failures.md` case into the suite.
415+
416+
### 3.10 Reactive render model: pitfalls that break conditional UI (verified, cdm `ErrorOverlay`)
417+
418+
While building `ErrorOverlay` (a global uncaught-error catcher) in
419+
`lang/libs/components/src/ErrorOverlay.ch`, two framework behaviors repeatedly prevented the
420+
overlay from ever appearing. Both are direct consequences of the runtime's
421+
**"component bodies run once; only derived/conditional JSX nodes re-render"** model (the
422+
`$_ucs` computed-signal patcher in `defaultUniversalSetup`).
423+
424+
**Pitfall A — `#css` style helpers are server-only and crash at hydration.**
425+
`#css { … }` helpers such as `error_overlay_styles(page : &mut HtmlPage) : *char` take the SSR
426+
`page` pointer. On the client the component is invoked as `factory(props)` with **no `page`
427+
argument**, so calling the helper throws during hydration → the component hits the error
428+
boundary (`$__uni_render_fallback`) and renders nothing. Symptom: the component is silently
429+
absent and nothing appears, with no visible error.
430+
- **Workaround (proven):** do not use `#css` helpers inside `#universal` components that must
431+
render on the client. Use inline `style={{ … }}` objects (string/number values) or plain
432+
`class="…"` strings, matching the existing app dialogs (e.g. `CdmApp`'s Tools dialog uses
433+
`class` + inline `style`). For reusable styling, emit the CSS once in a top-level
434+
`<style>`/theme and reference class names instead of per-component `#css` helpers.
435+
436+
**Pitfall B — visibility toggles must be a JSX conditional child, never control-flow
437+
`if`/`return`.**
438+
`if(!open) { return null }` is evaluated **once at mount** and is not a reactive binding, so
439+
flipping `open` later does nothing. Storing the conditional in a local `var overlay = open ? …
440+
: null` and then `return overlay` is also one-time — the local is not wrapped in a computed
441+
signal. Only a conditional used **directly as a JSX child expression** becomes a `$_ucs`
442+
computed that re-evaluates when its state dependencies change.
443+
- **Workaround (proven):** render `{open && errors.length > 0 ? <div …>…</div> : null}` inline
444+
as a child (wrap in an inert `<div style={{display:"contents"}}>` if a single root is
445+
needed). All state reads (`open`, `errors`, `selected`, `copied`) must happen *inside* that
446+
conditional so they subscribe. Never `return` a precomputed local.
447+
448+
**Net result:** once both pitfalls were avoided (inline styles + inlined conditional child),
449+
`window.__reportError(msg, stack)` and the `window.addEventListener("error" /
450+
"unhandledrejection")` handlers installed in the component's `useEffect` correctly flip
451+
`open` and the modal renders. This is the pattern every show/hide component (dialogs, toasts,
452+
dropdowns, modals) in this framework must follow.
453+
454+
Suggested roadmap additions:
455+
- (N) In `#universal` components, hard-warn (or reject) `#css` helper usage in positions that
456+
execute during client hydration — or make `#css` a no-op that returns a stable class name on
457+
the client.
458+
- (N) Document the "conditional child, not `if`/`return`" rule in the component authoring guide
459+
and ideally lint for early-`return`-on-state patterns.
415460

416461
---
417462

0 commit comments

Comments
 (0)