How a downstream app consumes the shared @kolu/surface stack (@kolu/surface · surface-app · surface-remote · surface-mcp) — declaring a typed reactive surface, serving it, consuming it (SolidJS hooks or a CLI), and mirroring a remote surface over ssh. Grounded in the real consumers: kolu, drishti, odu, and the TUIs. Load when wiring a surface server/client/mirror, or reaching for makeSession / a link / the `.use()` hooks. CHANGING the framework is gated separately — `.claude/rules/surface.md`...
81 stars
0 votes
0 copies
2 views
Added September 5, 2026
testinggoshellreactnodeapi
Works with
cli
api
mcp
Security analysis
A92/100
mediumInstalls packages at runtime which could introduce malicious dependencies
Installs into .claude/skills of the current project.
Are you the author of Surface?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/juspay-surface)
---
name: surface
description: >-
How a downstream app consumes the shared @kolu/surface stack (@kolu/surface ·
surface-app · surface-remote · surface-mcp) — declaring a typed reactive surface,
serving it, consuming it (SolidJS hooks or a CLI), and mirroring a remote surface over
ssh. Grounded in the real consumers: kolu, drishti, odu, and the TUIs. Load
when wiring a surface server/client/mirror, or reaching for makeSession / a link /
the `.use()` hooks. CHANGING the framework is gated separately —
`.claude/rules/surface.md` (a paired, CI-green drishti PR pinned to final kolu HEAD).
---
# Using @kolu/surface (downstream consumer guide)
Declare a typed reactive surface once; the framework derives the Effect RPC group, wires the
server, and binds the Solid client hooks. **This is the consumer guide** — *changing* the
framework needs a paired drishti PR (`.claude/rules/surface.md`).
## Who uses it — match the closest consumer, don't hand-roll
| Consumer | Shape | Notable |
| --- | --- | --- |
| **kolu** (`client`+`server`) | one browser ⇄ one Node server, ONE ws | single-tier; two sibling surfaces; uses `surface`+`surface-app` only (**not** `-remote`) |
| **drishti** (`srid/drishti`) | browser ⇄ Node ⇄ ssh agent mirror | the canonical twin; 3 workspaces (common/agent/app) |
| **odu** (`juspay/odu`) | CI runner: stdio lanes → unix-socket fan-in → CLI/MCP | serve+consume+mirror over every transport at once; `surface-mcp` projection |
| **kolu-cli** (`kolu ls/create/send/wait/…`) | one-shot CLI verbs, no browser | the primary CLI consumer: a PURE padi client — transport-blind `{client,dispose}`, unix-socket local, ssh remote (`--host`); **no `.use()` hooks**. `padi-tui`/`kaval-tui` are the same shape, retiring |
## The spine (real import paths)
- **Define** — `defineSurface({cells,collections,streams,events,procedures})` (`@kolu/surface/define`). Many surfaces over one transport: `composeSurfaceContracts(map)` + sibling clients, never merged. Several flat GROUPS onto one wire (a sibling bundle beside a keyed map, hand-written root procedures, an unprefixed root surface): `mergeDisjointGroups({label: group})` — the one counted merge, which names the tag and both halves on a collision. Never a bare `RpcGroup.merge` (last-writer-wins, silent).
- **Serve** — `implementSurface(surface, deps)` / `implementSurfaces(map, base, perKeyDeps)` (`@kolu/surface/server`) returns a supervised runtime `{ router, ctx, done, close }`. `router` is the FINAL top-level router — **serve it directly** (no `implement(surface.contract).router(...)` re-wrap; a second finalize double-prefixes `/surface/surface/…` and 404s). Observe `done` (owned-fault channel) and own `close` at teardown. Serve on a SHARED caller-owned publisher via `implementSurfaceOnPublisher` / `implementSurfacesOnPublisher`. A ROOTED bundle whose sibling set MOVES while it is served is `implementRootedSurfaces(core, base, coreDeps)` — `mount(key, surface, deps)` returns a `MountedSurface {key, surface, ctx, drop()}` (the registration carries its own undo), `group`/`handlers` are LIVE reads (read them as a pair in one turn), composition is INCREMENTAL (survivors keep handler identity, stores and sources), and a drop refuses at a record an open connection already captured — in-flight subscriptions included (`SurfaceSiblingDropped`). Live-ness is a property of the ROOTED door because a rootless wire's reserved probes target the FIRST sibling, which a moving roster can drop.
- **Consume (SolidJS)** — `surfaceClient(surface, link)` / `surfaceClients(link, map)` (`@kolu/surface/solid`) → `client.cells.X.use({authority,initial,onError})`, `.collections.X.use({keys,onError})` then `.byKey(id)?.()` / `.keys()`, `.streams.X.use(inputFn,{onError})` with `.pending()`/`.error()`, `.events.X.use(inputFn,handler)`.
- **Consume (CLI/TUI)** — no reactive hooks; raw awaited `conn.client.surface.<verb>(…)` + async-iterator iteration; a live board uses `mirrorRemoteSurface(spec, client, {collections,streams}, {log})` (`@kolu/surface/mirror`) into plain callbacks.
## Links (transport, swappable)
`websocketLink(ws)` (`/links/websocket`) · `stdioLink` (`/links/stdio`) · `unixSocketLink({socketPath})` (`/links/unix-socket`) · `directLink(router)` (`/links/direct`, in-process identity, for tests). Serve side: `serveOverStdio` (`/peer-server`), `serveOverUnixSocket` (`/unix-socket`), and for browsers **`serveSurfaceApp`** (`@kolu/surface-app/serve`) — **don't hand-roll a listener**: it owns the whole order (origin gate → upgrade → stale-tab check → heartbeat enrolment → serve) plus the shell layers, the bind and the scope-registered teardown, and it reads the inbound frame cap from `RPC_MAX_FRAME_BYTES` so a consumer cannot undercut the wire. TLS (`tls`), an HTTP `middleware`, an app's own `routes` and a dist-less dev shell (`clientDist` is optional) are all options — kolu's own listener rides it. Reach past it to the granular `acceptSurfaceSocket` + `serveSurfaceSocket` only when the app picks WHICH runtime serves each upgrade — `?host=` per-host dispatch (drishti), the one decision the accept seam leaves at the call site. CLIs keep ONE transport-blind `Connection = {client, dispose}` so every command is written once across local vs ssh. `serveSurfaceApp` and `serveOverUnixSocket` take an optional per-face `expose` (`@kolu/surface/expose`: `exposeFace(surface, map)` / `exposeFaces` for a bundle / `exposeRootedFaces` for a root beside a bundle, the same `ExposeMap` `serveSurfaceAsMcp` takes and the same `classifyExpose` grammar), so a verb can be reachable over the unix socket and not to browsers. WHICH faces take a map, which gate themselves, and what a key grants are stated once — in that module's header. Read it there rather than from memory.
## Mirror a remote surface (drishti / pulam-web / odu)
1. **Dial the host** — `makeSession({connectOnce: sshConnector<contract>({host, binary, resolveDrvPath})})` (`@kolu/surface-remote`): long-lived, `nix copy`s the agent closure, runs `<bin> --stdio` over ssh, reconnects. `buildRemotePool` fans out N hosts; one-shot CLIs use `dialAgentOnce` instead.
2. **Mirror inward** — `pumpRemoteSurface({source, session, makeSink, …})` (`-remote`) folds the remote agent's frames into a local `implementSurface` re-serve via a `SurfaceSink` (`makeSink`, `@kolu/surface/mirror`). The parent implements the *same* surface; a remotely-unobservable cell (e.g. connection state) is parent-authoritative.
3. **Re-serve** — the local re-served surface (a FINAL `implementSurface` router) served on `/rpc/ws`, accepted via `acceptSurfaceSocket` (`@kolu/surface-app/server`). Browsers connect with `connectSurface` (`@kolu/surface-app/solid`), which bundles socket + `websocketLink` + `surfaceClient` + a default-on liveness heartbeat. `connectSurfaces` is the multi-surface twin, and its `core: {surface, name}` slot carries an UNPREFIXED root beside the siblings — typed client, health enrolment under `name`, and the reserved probes addressed to the root's bare `surface/system/*`. A rooted app has no reason to hand-assemble the seam. A SIBLING ROSTER CHANGE IS A REDIAL, not an update — Effect RPC bakes the `RpcGroup` into the client at `openWireLink`, so new tags need a new wire at both ends; `conn.redial(surfaces)` owns it, re-using this connection's own options, keeping the root, and dialling the replacement BEFORE releasing the old wire.
## Gotchas (hard-won, all real)
- **Procedures call off the FULL link, not the scoped client** — `surfaceClients` per-key `.rpc` is typed `unknown`; reach the root link for raw procedures.
- **Raw streaming** — `unenrolledStreamCall(client.X, input, {signal, onRetry})` (`@kolu/surface/client`) carries the reconnect (`STREAM_RETRY`) context; a bare `client.X(…)` silently loses it. There is **no `stream` namespace** (`.claude/rules/streaming.md` is stale on that point).
- **Consume streams fine-grained** — value-bearing → `.streams.use()` (replace-each-frame); delta-accumulate → `mirrorRemoteSurface` / `createSubscription`+`reduce`. Never coarse-read-and-copy: same-shape frames coalesce and the view freezes.
- **Snapshot-then-deltas + fail-fast** — a cell always opens with a snapshot; `firstFrameOrThrow` (`@kolu/surface/first-frame`) treats an empty stream as a link failure, never a silent empty.
- **Liveness is on by construction** — framework-reserved `surface.system.live`; `connectSurface` / `HostSession` / `createServerLifecycle` default their watchdog to it (`probeSurfaceLive`). Don't nominate your own probe unless you mean to.
- **Version skew** — gate on `isContractVersionCompatible` (major.minor), never a string `==`.
- **Nix-baked deps** — odu declares no `@kolu/*` in `package.json`; they're symlinked at Nix build (the bake-in-via-Nix convention). A bare `pnpm install` won't resolve them.
## Reference
Runnable end-to-end: `packages/surface/example/` (its `mini-ci` stdio example is odu's seed). Full API + rationale: each package's `README.md`. Match the closest consumer above; don't reinvent a primitive the table already shows how to use.