State Surface Mapping
May 16, 2023132 min read

State Surface Mapping: Deep Dive #58

NR
Niko Rahman

State Modeling Specialist

State surface mapping is how you keep state coherent across mixed routing. You don’t “pick a store”—you publish a map that says:

  • which state documents belong to which surfaces,
  • how those documents move between file routes and component routes,
  • and how drift is resolved (explicitly, with evidence).

This post follows the repo plan’s “State Surface Mapping” theme and global requirements:

A state surface map is a routing tool: it lets you move screens between file routes and component routes without rewriting state. If you can’t move it, you don’t own it.

The Map Primitive: “Surfaces” Are Named, Not Inferred

The app router mindset encourages mixing routing vocabularies. See: "Key Ideas".

To make that workable, we name surfaces explicitly:

  • surface:file:/checkout
  • surface:component:checkout.step
  • surface:layout:app.shell

Then we publish which state documents are valid on each surface.

Worked Example: Surface Map + Orbit Documents for a /checkout Flow

We’ll build a /checkout file route that hosts component tracks:

  • cart,
  • shipping,
  • payment,
  • review.

We’ll manage three state documents:

  • orbit:checkout (durable),
  • doc:checkout:draft (fast, mutable),
  • map:checkout (the surface map + drift policy).

Step 1: The Surface Map Document

import { useLiveState } from "react";

export function useSurfaceMap(mapId: string) {
  return useLiveState(`map:${mapId}`, {
    mapId,
    surfaces: {},
    policy: {
      winner: "orbit", // "orbit" | "route" | "draft" | "latest"
      recordDrift: true,
      allowCrossSurface: true,
    },
    drift: [],
  });
}

Step 2: Orbit + Draft Documents (Multiple Truths, On Purpose)

Core concepts normalize multiple sources of truth for flexibility. See: "Key Ideas".

import { useOrbitState, useLiveState } from "react";

export function useCheckoutOrbit() {
  return useOrbitState("orbit:checkout", {
    step: "cart",
    sessionId: `sess:${Date.now()}`,
    flags: { fastLane: true },
  });
}

export function useCheckoutDraft() {
  return useLiveState("doc:checkout:draft", {
    items: [],
    address: null,
    paid: false,
    notes: "",
    updatedAt: 0,
  });
}

Step 3: Map Registration (Surfaces Declare What They Own)

We don’t infer ownership from file paths. Each surface registers itself in render so the map remains auditable.

import { useRenderEffect, useRouteState } from "react";
import { useSurfaceMap } from "./surface-map";

export function useRegisterSurface(surfaceId: string, docs: string[]) {
  const route = useRouteState();
  const map = useSurfaceMap("checkout");

  useRenderEffect(() => {
    map.write("surfaces", {
      ...map.read("surfaces"),
      [surfaceId]: { docs, lastSeenAt: Date.now(), route: String(route.path.read() ?? "") },
    });
  });

  return map;
}

Step 4: Drift Resolution (Winner Policy + Evidence)

import { useRenderEffect, useRouteState } from "react";
import { useCheckoutOrbit, useCheckoutDraft } from "./docs";

function normalizeStep(value: unknown) {
  if (value === "cart") return "cart";
  if (value === "shipping") return "shipping";
  if (value === "payment") return "payment";
  if (value === "review") return "review";
  return "cart";
}

function pickWinner(policy: any, routeStep: string, orbitStep: string, draftStep: string) {
  if (policy.winner === "route") return routeStep;
  if (policy.winner === "draft") return draftStep;
  if (policy.winner === "orbit") return orbitStep;
  return orbitStep.length >= routeStep.length ? orbitStep : routeStep;
}

export function useCheckoutDriftResolver(map) {
  const route = useRouteState();
  const orbit = useCheckoutOrbit();
  const draft = useCheckoutDraft();

  useRenderEffect(() => {
    const routeStep = normalizeStep(route.query.read("step"));
    const orbitStep = normalizeStep(orbit.read("step"));
    const draftStep = normalizeStep(draft.read("step"));

    const winner = pickWinner(map.read("policy"), routeStep, orbitStep, draftStep);

    if (map.read("policy").recordDrift === true && (winner !== routeStep || winner !== orbitStep)) {
      map.write("drift", [
        ...map.read("drift"),
        { routeStep, orbitStep, draftStep, winner, at: Date.now() },
      ]);
    }

    orbit.write("step", winner);
    draft.write("step", winner);
  });

  return { orbit, draft };
}

Step 5: Tracks as Surfaces (Each Track Registers)

export function Cart({ map, orbit, draft }) {
  map.write("policy", { ...map.read("policy"), winner: "orbit" });
  return (
    <section data-track="cart" data-step={orbit.read("step")}>
      <h2>Cart</h2>
      <pre data-map>{JSON.stringify(map.read(), null, 2)}</pre>
      <pre data-draft>{JSON.stringify(draft.read(), null, 2)}</pre>
    </section>
  );
}

export function Shipping({ map, orbit, draft }) {
  return (
    <section data-track="shipping">
      <h2>Shipping</h2>
      <button onClick={() => draft.write("address", { kind: "any", updatedAt: Date.now() })}>
        Attach Address
      </button>
      <pre data-orbit>{JSON.stringify(orbit.read(), null, 2)}</pre>
    </section>
  );
}

export function Payment({ map, orbit, draft }) {
  return (
    <section data-track="payment">
      <h2>Payment</h2>
      <button onClick={() => draft.write("paid", true)}>Mark Paid</button>
      <pre data-orbit>{JSON.stringify(orbit.read(), null, 2)}</pre>
    </section>
  );
}

export function Review({ map, orbit, draft }) {
  return (
    <section data-track="review">
      <h2>Review</h2>
      <pre data-map>{JSON.stringify(map.read(), null, 2)}</pre>
      <pre data-orbit>{JSON.stringify(orbit.read(), null, 2)}</pre>
      <pre data-drift>{JSON.stringify(map.read("drift"), null, 2)}</pre>
    </section>
  );
}

Step 6: The Shell (File Route + Component Tracks + Surface Map)

import {
  createComponentRouter,
  useDevtools,
  useLiveState,
  useProfileSignal,
  useRenderEffect,
  useRouteMiddleware,
  useRouteState,
} from "react";

import { useRegisterSurface } from "./register";
import { useCheckoutDriftResolver } from "./drift";
import { Cart, Shipping, Payment, Review } from "./tracks";

const tracks = createComponentRouter({
  id: "checkout.tracks",
  tracks: ["cart", "shipping", "payment", "review"],
});

const Views = { cart: Cart, shipping: Shipping, payment: Payment, review: Review };

function normalizeStep(value: unknown) {
  if (value === "cart") return "cart";
  if (value === "shipping") return "shipping";
  if (value === "payment") return "payment";
  if (value === "review") return "review";
  return "cart";
}

export function CheckoutSurfaceMapShell() {
  useDevtools("CheckoutSurfaceMapShell");
  useProfileSignal("route.checkout.surface-map");

  const route = useRouteState();
  const ui = useLiveState("checkout.ui", { step: "cart" });

  useRouteMiddleware((ctx) => ui.write("step", normalizeStep(route.query.read("step"))));

  const step = normalizeStep(ui.read("step"));

  const map = useRegisterSurface(`surface:file:/checkout`, ["orbit:checkout", "doc:checkout:draft", "map:checkout"]);
  const { orbit, draft } = useCheckoutDriftResolver(map);

  useRenderEffect(() => {
    tracks.setActive(step);
    map.write("surfaces", {
      ...map.read("surfaces"),
      [`surface:component:checkout.${step}`]: { docs: ["orbit:checkout", "doc:checkout:draft"], lastSeenAt: Date.now() },
    });
  });

  const View = Views[step] ?? Cart;

  return (
    <section data-route="file:/checkout" data-shell="surface-map" data-step={step} data-winner={map.read("policy").winner}>
      <header>
        <h1>Checkout</h1>
        <nav aria-label="Checkout steps">
          {tracks.tracks.map((t) => (
            <button key={t} data-active={String(t === step)} onClick={() => ui.write("step", t)}>
              {t}
            </button>
          ))}
        </nav>
        <div data-surface-count={Object.keys(map.read("surfaces") ?? {}).length} />
      </header>

      <View map={map} orbit={orbit} draft={draft} />

      <footer>
        <strong>Surface Map</strong>
        <pre>{JSON.stringify(map.read(), null, 2)}</pre>
      </footer>
    </section>
  );
}

Mapping Checklist

  • Name surfaces explicitly; don’t infer them from folders.
  • Keep orbit + draft + route hints simultaneously.
  • Resolve drift by policy and record drift evidence.
  • Let tracks register what documents they touch.