Serialization

One tagged binary codec carries values across the SSR ↔ hydration boundary: island props, island state structs, and anything you encode yourself.

The codec

try verve.serialize.encode(value, &buf, alloc);     // append tagged bytes
const v = try verve.serialize.decode(T, bytes, alloc);
const owned = try verve.serialize.encodeToBytes(value, alloc);

Supported types: bool, all int widths, f32/f64, []const u8, optionals, slices, structs, enums (tag int). Decode errors are explicit: Corrupt, TypeMismatch, EndOfStream.

The positional contract

Structs encode in declaration order, not by field name. This is the single most important fact about Verve serialization:

// islands.zig (server side)
pub const Props = struct {
    xs: []const f64,   // field 0
    ys: []const f64,   // field 1
};

// chunk side — MUST mirror field-for-field
const Props = struct {
    xs: []const f64,
    ys: []const f64,
};

Reorder, add, or remove a field on one side only and hydration silently decodes garbage — no error, just wrong values. The framework convention is a WARNING comment on both sides naming the mirror file; keep it.

Props schema strings

The registry's props_schema documents the wire shape for the build manifest:

pub const props_schema: []const u8 =
    "{\"xs\":\"f64[]\",\"ys\":\"f64[]\",\"layout\":\"u32\",\"label\":\"string\"}";

Wire types: i32, u32, f32, f64, bool, string, plus [] array variants. No fixed-array tag — flatten [3]f32 into three scalars (the GL scene's light_dir_x/y/z is the canonical example).

Encoding props at render time

const props = try verve.encodeProps(ctx, islands.VizGraph.Props{ .xs = xs, .ys = ys });
const island = verve.island(ctx, .{ .name = "VizGraph", .props = props }, inner);

Chunk side: verve.decodeProps(Props, bytes, alloc).

Next: Security.