kolu
Docs

The client half

The server half teaches how one surface crosses a machine. The client half is the other problem: a browser facing many surfaces of the same shape, keyed by host, with the set changing while you watch. drishti — one browser fronting an ssh fleet — is the exemplar; this page is about what its client can and cannot take for granted.

The shape of the map clientOne browser talks over one socket that carries enveloped frames of map key and input. The map holds one entries collection — the sole authority on membership — plus a per-key slot for each host, reached as app.entry(host). One slot is warming and one has failed while the others stay connected.browserone tabONE socket{ mapKey, input }every frame envelopedthe map — appentriesthe ONE authority on membership · absence = not a memberper-key slots — app.entry(host)…zestcells · collectionsconnectedlabcells · collectionsconnectednaiveintentunreachable — retryingwarmingstale-boxa standing faultfailed
One browser, one socket carrying enveloped { mapKey, input } frames. The map is an entries membership collection — the sole authority on presence — plus a per-key slot for each host reached through app.entry(host)…. One slot warms and one fails on their own while the rest stay connected.

One contract, many hosts

Every host serves the identical entry surface. That is the leverage: you type the surface once, and the map form keys every member so one socket can carry all instances. A client reaches any host’s cells or streams through the entry lens — app.entry("zest").cells.daemonStatus.use(…) — and the type is the same for every host because the contract is the same for every host. There is no per-host client to write, no N-way branch. The fleet is the single-surface client, keyed.

The hard part a fleet adds is not the plumbing; it is a single invariant: the UI must never show host A’s data under host B’s name. Every fact on the wire is true of one host. The client half exists to make that misattribution unconstructible rather than merely avoided by care.

entries is the one authority on presence

The map publishes exactly one collection of membership: entries. It is the single authority on who is in the fleet, and it makes one uncompromising commitment — absence means absence. A host not in entries is not in the map. There is no absent status arm to keep in sync, because a collection already expresses absence by not containing the key. One writer publishes membership and status together, so the two can never disagree; dual-authority for membership is simply not expressible.

Reading one host stays total. Ask entry(key).state() for any key — present or not — and you get an honest value, not-a-member included; you never get a nullable entry() you might forget to guard. Existence is a value you render, not an exception you catch. Switching which host a tab views is then a plain app signal: useEntry(activeHost) re-keys the whole readout when the signal changes, the old host’s subscriptions dispose, the new host’s populate — “switch the chip, every readout follows” is true by wiring, not by discipline.

Liveness on by construction

A fleet client faces a subtler lie than a broken pipe: a host the server last saw as connected, presented as green while our link to the server is dead. A connected published by the server is only as trustworthy as the transport carrying it. So the map floors every status on real transport liveness — with the link dead, a stale connected downgrades to warming. No status renders green over a dead transport, and live is a required input to the fold so it can’t be forgotten.

This is why the client never hand-rolls a probe. The framework-reserved liveness round-trip is the one probe, and the watchdog-backed handle from connectSurface carries it by default; the map slices its scoped view after that guard, so every entry inherits the parent’s watchdog by construction. A hand-rolled “is this host up?” check would be a second opinion about liveness — and two opinions about liveness is exactly how a green dot ends up floating over a dead socket.

Where it costs you

Making misattribution unconstructible has a price: you read every per-host fact through the entry lens, never off a bare client you cached yourself. That is one more indirection on every read, and it forbids the shortcut of stashing “the current host’s client” in a variable — because a stashed client is exactly how host B’s data ends up under host A’s name after a switch. The framework trades the convenience of a flat client for a guarantee the flat client can’t make. For a single host it is overhead; for a fleet, it is the only version that stays honest when the set changes underfoot. Why entries carries the arms it does is Entry contracts.