Building a larger feature

Individual examples show one subsystem each. Real pages compose several — server-rendered data, an interactive island, animation, maybe a live feed — on a single route. The constraint that shapes everything is the island memory model, so start there.

See it live: showcase (viz + animation on one page) and dashboard (nested routing).

The rule that drives the architecture

Island chunks share the main client's linear memory and run on a 4 KB stack, and a page may host one stateful chunk — any chunk that keeps module-level globals. Break the rule and two islands stomp each other's state. So the design move for every feature page is the same:

Render as much as possible server-side; make exactly one thing an island.

Everything verve.viz draws is SSR SVG. Every verve.anim declarative tween is zero-WASM. Forms degrade through actionForm. That leaves one genuinely interactive concern to spend your stateful chunk on.

Worked example: the showcase page

The showcase composes three subsystems and stays legal:

// 1. A graph computed in Zig, serialized as typed props, revealed on hydrate.
const props = try verve.encodeProps(ctx, islands_reg.VizGraph.Props{
    .xs = xs, .ys = ys,
});
const graph = verve.island(ctx, .{ .name = "VizGraph", .props = props },
    ctx.div().children(.{ graph_svg }));   // SSR SVG is the fallback content

// 2. Declarative entrance animation — no island, runs in the bridge.
section.animate(anim.from(a, ".panel").opacity(0).y(24)
    .scrollTrigger(.{ .start = .{ .viewport = .{ .pct = 80 } } }));

The VizGraph island is the one stateful chunk; the animation is declarative, so it adds zero chunks. The SVG inside the island tag is the no-JS fallback — it's already correct before hydration, and the chunk only reveals and animates it in.

Data flow, end to end

  1. SSR computes the real state (ctx.serverFn-style reads) and renders it — the page is correct and indexable with JS off.
  2. Typed props (verve.encodeProps against a registry Props struct) hand structured data to the chunk without a per-chunk JSON parser. The positional codec means field order in islands.zig, core/*, and the chunk must match exactly.
  3. Hydrate wires interactivity: the chunk registers signals, binds z-on-click exports, opens an SSE channel if the feature is live.
  4. Round-trips go through server functions (serverFnPost), whose JSON replies are enveloped {"value": …} — read .value first.

When a feature spans pages

The dashboard shows the other axis: a feature that is several routes. verve.Route.layout wraps child routes in a shared shell; ctx.outlet marks where the child renders; path params (:section), ctx.activeClass for nav state, and .protect(guard) for route guards complete it — all server-side, no island required for the navigation itself.

A checklist for feature pages

  • What is the one genuinely interactive thing? That's your island.
  • Can the rest be SSR + declarative anim + actionForm? It almost always can.
  • Does the page work with JavaScript disabled? If not, the SSR fallback is incomplete.
  • Do the Props field orders match across all three copies?
  • If it's live, is posting a server function and receiving an SSE frame — not the same channel?

Next: Advanced WebGL.