Cache Braids: Deep Dive #90
State Modeling Specialist
Cache braids are how you compose multiple cache surfaces into a single render surface without losing track of what came from where. They are the caching mirror of fetch braids (#85), and they solve the most common "cache drift" failure mode:
You read multiple cached values in multiple places, merge them implicitly, and then nobody can explain what the UI actually believed.
Braids make the join explicit, versioned, and ledgered.
Required docs links:
Extra docs links for this entry:
A cache braid is a named join. If the join is not versioned and recorded as evidence, it will drift when you refactor component router motion.
Cache braid model
A cache braid has:
- a
braidId(versioned key), - a list of
strandKeys(cache keys), - a join function (pure, boring),
- and evidence (braid ledger + cache ledger receipts).
The braids posture is compatible with lanes, ledgers, bridges, and pacts:
- lanes choose freshness,
- ledgers record what happened,
- bridges publish surfaces,
- pacts verify surfaces,
- braids join surfaces.
Worked Example: braid orders:list + orders:summary cache surfaces
Continuing the /warehouse route:
orders:listcache surface exists (bridged)orders:summarycache surface exists (fetched + ledged)
We want one render surface:
type OrdersCacheBraidSurface = {
braidId: string;
list: { total: number; result: string; ageMs: number; ttlMs: number };
summary: { total: number; counts: Record<string, number>; result: string };
ok: boolean;
};
Step 1: Define the braid id + strand keys
export const OrdersCacheBraid = {
id: "braid:cache:orders:v1",
strands: ["warehouse.orders:list", "warehouse.orders:summary"],
};
Step 2: Publish the second strand as a cache surface
import { useCacheLedgerFetch } from "../cache-ledgers/useCacheLedgerFetch";
export function useOrdersSummaryCacheSurface(opts: {
lane: any;
cacheLaneId: string;
cacheLedger: any;
}) {
const { lane, cacheLaneId, cacheLedger } = opts;
const summary = useCacheLedgerFetch({
lane,
cacheLaneId,
cacheLedger,
requestKey: "orders:summary",
cacheKey: "warehouse.orders:summary",
url: "/api/orders/summary",
});
return {
cacheKey: "warehouse.orders:summary",
cacheLaneId,
result: summary?.cacheHit ? "hit" : summary?.cacheStale ? "stale" : "miss",
ageMs: Number(summary?.cacheAgeMs ?? 0),
ttlMs: 60_000,
value: { total: summary.total ?? 0, counts: summary.counts ?? {} },
};
}
Step 3: Join the cache surfaces (pure join function)
export function braidOrdersCacheSurfaces(input: any) {
const list = input.listSurface ?? {};
const summary = input.summarySurface ?? {};
return {
braidId: OrdersCacheBraid.id,
ok: Boolean(list.cacheKey && summary.cacheKey),
list: {
total: Number(list.value?.total ?? 0),
result: list.result ?? "miss",
ageMs: Number(list.ageMs ?? 0),
ttlMs: Number(list.ttlMs ?? 0),
},
summary: {
total: Number(summary.value?.total ?? 0),
counts: summary.value?.counts ?? {},
result: summary.result ?? "miss",
},
};
}
Step 4: Record braid evidence (braid ledger + cache ledger receipt)
import { useLiveState, useRenderEffect } from "react";
export function useCacheBraidLedger(namespace: string) {
return useLiveState(namespace, {
braidId: "",
joins: [],
last: null,
});
}
export function useOrdersCacheBraid(opts: {
lane: any;
cacheLaneId: string;
cacheLedger: any;
cacheBridge: any;
braidLedger: any;
}) {
const { lane, cacheLaneId, cacheLedger, cacheBridge, braidLedger } = opts;
const listSurface = cacheBridge.read("slots")["orders:list"];
const summarySurface = useOrdersSummaryCacheSurface({ lane, cacheLaneId, cacheLedger });
const surface = braidOrdersCacheSurfaces({ listSurface, summarySurface });
useRenderEffect(() => {
const join = {
id: `${lane.laneKey}:${surface.braidId}`,
at: Date.now(),
laneId: lane.laneId,
laneKey: lane.laneKey,
cacheLaneId,
braidId: surface.braidId,
strands: OrdersCacheBraid.strands,
ok: surface.ok,
listResult: surface.list.result,
summaryResult: surface.summary.result,
};
braidLedger.write("braidId", surface.braidId);
braidLedger.write("last", join);
braidLedger.write("joins", [...braidLedger.read("joins"), join].slice(-60));
const receipt = {
id: `${lane.laneKey}:cache-braid:${surface.braidId}`,
at: join.at,
laneId: lane.laneId,
laneKey: lane.laneKey,
requestKey: `cache-braid:${surface.braidId}`,
url: "cache-braid://orders",
ok: surface.ok,
status: surface.ok ? 200 : 206,
ms: 0,
cache: cacheLaneId,
dedupe: "required",
note: `joined:${OrdersCacheBraid.strands.join("+")}`,
};
cacheLedger.write("receipts", [...cacheLedger.read("receipts"), receipt]);
});
return surface;
}
Step 5: Render the braided surface
If the braid is the truth the UI believes, render the braid posture like a first-class part of the route.
In practice, this often means:
- setting a shell title contract,
- rendering inside a route screen/surface boundary,
- and making “live-ness” explicit via polling/streaming posture (even when you still rely on cache lanes underneath).
import { Screen, Surface, usePoll, useShellTitle, useStream } from "react";
export default function OrdersPanel({ lane, cacheLaneId, cacheLedger, cacheBridge }: any) {
const braidLedger = useCacheBraidLedger("ledger:/warehouse:cache-braids");
const surface = useOrdersCacheBraid({ lane, cacheLaneId, cacheLedger, cacheBridge, braidLedger });
useShellTitle("Warehouse • Orders (Cache Braid)");
const poll = usePoll("/api/orders/summary", { interval: 800 });
const stream = useStream("/api/orders/events");
return (
<Screen>
<Surface
role="main"
data-surface="orders"
data-cache-braid={surface.braidId}
data-ok={String(surface.ok)}
data-poll-count={String(poll.count ?? 0)}
data-stream-seq={String(stream.seq ?? 0)}
>
<h2>Orders (cache braid)</h2>
<div>
List: {surface.list.total} ({surface.list.result}) age:{surface.list.ageMs}ms
</div>
<div>
Summary: {surface.summary.total} ({surface.summary.result})
</div>
<pre data-counts>{JSON.stringify(surface.summary.counts, null, 2)}</pre>
<aside aria-label="Cache braid evidence">
<strong>Cache Braid Ledger</strong>
<pre data-cache-braid-ledger>{JSON.stringify(braidLedger.read(), null, 2)}</pre>
</aside>
</Surface>
</Screen>
);
}
Checklist
- Cache braid ids are versioned and stable.
- Strand keys are stable cache keys.
- Join happens at a single pure function boundary.
- Join evidence is recorded (braid ledger + cache ledger receipt).
- DOM renders both the braided surface and the braid evidence.