mrly.net

State tiers

The rule: state belongs to the kernel if replaying the journal without it would change what the screen means. It's shell-local if it only changes this second's look.

Tier 1 - kernel + journal (deterministic, replayable)

Every verb not prefixed `journal.` is stamped with `now`, pushed to the journal, then run. Replay fires every stamped call bare, in order, on boot. Covers user app verbs, `nav.*`, `sys.shot`, `sys.dismiss`, and the host `.set` calls a pick produces.

Beat - the 8/s `setInterval` drives the focused app's `slot.beat` call. Beats are journaled like any verb, but consecutive step beats coalesce: the tail row's `n` grows in place (capped at the verb's kernel clamp), so a run journals as one "advance n" row rewritten and persisted every beat. Replay only needs the stamped calls, not the original cadence - visual speed and replayed effects can diverge, never the outcome.

Compaction - once the journal hits 500 calls, the shell freezes the kernel directly (bypassing the journal), stores the freeze as a snapshot, and drops the trailing calls. A saved journal is "snapshot + up to 500 calls." Freeze/thaw carries the route, tick, the kernel's own 100-entry call ring, notices, and every app's saved state. Only one app is ever open; `nav.open` replaces the route.

Tier 2 - shell-intercepted verbs (client-side, never reaches the kernel)

reset/import guards). Never journaled.

screen, spec in SPLASH.md). Pure client state; beats pause while hidden.

the kernel. Import only becomes real state on the next boot's replay.

Tier 3 - plain shell locals (pixels only)

visibility window is a local timer. The build-mismatch discard toast has no kernel notice behind it at all.

floating overlay left.

cached on the DOM node. Deterministic only when `settings.fill` is pinned off `"random"`.

`setInterval`, decoupled from kernel `tick`; replays show different frames.

between kernel keyframes on rAF (mandelbrot, julia, solids). Cosmetic only: reduced motion switches it off and honest 8/s stepping returns. Frames are never journaled.

shell plays them out across the beat window. Pixels only.

Known leaks vs the labeled-film claim

The claim: every screenshot is a labeled projection of known kernel state, so a lived session replays into perfectly labeled film. True for kernel-known pixels, but a handful of unjournaled things break it for a raw browser screenshot: the confirm dialog, toast timing (plus the unjournaled discard toast), random tints, the mark frame, and mid-tween projector/strip frames.

`sys.shot` is the exception, fully inside the claim: an ordinary journaled verb that copies the focused app's `frame` fact - a palette-indexed grid the kernel already knows - onto the photos wall, never pixels from the live page. PNG bytes exist only at egress. A `sys.shot` capture replays bit-for-bit; a real browser screenshot does not.

The face shot (docs/FACE.md) is a surface-side projection of known kernel state - a pure read over `peek`, never a journaled verb.