0014. Pages render synchronously from one store

Status: Accepted (2026-10-01)

Context

About 300 page-driving tests, in openWebviewPage suites and the e2e suites, act on a page and read what it drew in the same tick: they send a state message or click a control, then query the DOM on the next line. The template pages could be tested that way because every redraw assigned app.innerHTML before the handler returned.

Preact’s own way to change what a component draws is a hook: a useState update is queued and drawn in a microtask, and a useEffect runs after paint. A page built from component state would draw after the test had already read it. Every one of those tests would have to wait, and a page’s first frame would depend on scheduling.

Decision

Each page keeps one store, holding the snapshot its host sent and its own UI state, such as which lists are unfolded. A host message or a reader’s action changes the store, which calls Preact’s top-level render(<Page …/>, app). That call diffs and patches synchronously, so the page is drawn before the handler returns.

Components take everything they draw as props. Hooks are limited to refs and useLayoutEffect, which runs synchronously at commit. Nothing a test or the first frame observes comes from useState or useEffect.

src/webview/shared/page.ts holds the store (startPage), the delegated data-action listener, and the place restore that runs after each draw.

Consequences

Alternatives considered