Electricity — identified, and their progress
A tracker for kolu's electricities — infrastructure pulled out from beneath the app into its own thing — and where each stands.
“Decompose based on volatility” — Löwy’s directive: encapsulate each area likely to change behind a stable interface, rather than split a system by what it does. “Electricity” is his own example, and the source of our term: “the electricity that powers a house … is highly volatile: power can be AC or DC; 110 volts or 220 volts … produced by solar panels … a generator … or plain grid connectivity … All that volatility is encapsulated behind a receptacle … all the user sees is an opaque receptacle.” The body does the same — blood-pressure, salinity, and pulse volatility “encapsulated behind the service called the heart.” An electricity, in kolu, is one such volatility lifted into its own
@kolu/*package; a receptacle is the socket a consumer plugs into. — Juval Löwy, Software System Decomposition, ch. 2 of Righting Software.
The electricities
| Electricity | Owns (volatility) | PR | Progress |
|---|---|---|---|
| @kolu/surface | Live client↔server state — Cell/Collection/Stream/Event over oRPC, reconnect, fine-grained reconcile. |
#805 | done · 2nd consumer: drishti |
| @kolu/surface-app | The app shell for surface apps — fresh delivery + server/build identity + the connection/update lifecycle model + desktop-install. Owns the stale-tab handshake gate (#1231) and now the connection plumbing (#1234) — the pid-echo, the partysocket construction, and the upgrade gate, lifted from both consumers into three composable primitives (see archived notes). The restart axis already lived here, so each leak plugs into a receptacle the package had. |
#1154 · #1231 · #1234 | done · 2nd consumer: drishti |
| @kolu/surface-nix-host | HostSession — nix copy a closure to a host, realise, run --stdio over ssh. |
#984 | done · also in drishti |
| @kolu/artifact-sdk | Two volatilities under one roof — the agnostic anchoring core (W3C quote re-find; speaks only Range/Document/ShadowRoot) and the sandboxed-iframe parent↔frame bridge (opaque-origin postMessage, now carrying comments + in-frame navigation + back/forward input). See the re-eval note below. |
#922 | done re-eval |
| @kolu/transcript-core + transcript-html | Transcript model + rendering. | #744 | done |
| @kolu/solid-pierre | SolidJS adapters for Pierre tree/diff. | #823 | done |
| persistedPref | Validated localStorage (validate-on-read; closed the zoom-NaN + maximize bugs). |
#1089 | done |
| @kolu/log · @kolu/html-escape | Zero-dep leaf types every package can import without dragging the domain tree. | #1089 | done |
dom/ + injectable isMac |
walkShadowRoots relocated to a neutral wall; keybind core no longer reads the UA singleton. |
#1089 | done |
| createSharedRoot | Lazy app-scoped reactive singleton (7 consumers). 100% agnostic SolidJS. | #974 | done publish? |
| @kolu/serve-dir | Fetch-native file serving from an absolute root — streaming byte-range (206/416), content-type, lexical traversal guard; a pure (root, relPath, request) → Response. Its own package, zero workspace deps (node:fs/path/stream + the focused mrmime MIME table) — the dependency arrow points out (kolu-server → @kolu/serve-dir). Agnostic of terminals/git/kolu; the consumer injects the root and composes the artifact-sdk <script> decorator downstream. A 20-agent prior-art survey found no static-serve library fits the serving shape (all pipe to a Node res, breaking the Fetch-Response + decorator composition; none takes a per-request absolute root or does a realpath guard), so the serving is owned here — but the MIME table leans on mrmime (the separable commodity the survey explicitly didn’t cover). That’s load-bearing for decomposition: a complete table means adding a format to kolu’s *_EXTENSIONS classifier needs no edit here (mrmime already types it), so the ext↔MIME shared-volatility the lens debated is dissolved for every mrmime-known format. For the mrmime gap set (.m4v/.ico, and any future classifier entry mrmime doesn’t know) the coupling is instead contained-by-test: a 2-entry OVERRIDES in serve-dir supplies the MIME, and the coverage invariant in iframePreviewRoute.test.ts is load-bearing, not a thin sanity check — delete an OVERRIDES row and the classifier still routes the file to a <video>/<img> appliance while serve-dir answers application/octet-stream, so only that test catches the silent download regression. Path safety is two-stage by volatility: the lexical guard (decode-then-split + path.relative containment) is built in (pure, universal), while the realpath/symlink guard is an injected realpathGuard the consumer supplies (fs-touching, threat-model-specific) — kolu-server wires kolu-git’s assertRealpathUnder, so the package stays agnostic without dropping the security boundary. A leaf (bounded algorithm, not a transport-grade volatility). |
#1225 | done ②/③ proof-pending |
| @kolu/terminal-protocol | The VT/device-query protocol policy both terminal clients and the server must agree on — the query-reply suppression grammars (whole-payload predicate for the browser’s onData, streaming boundary-aware stripper for kolu-tui’s raw tty), the headless forward/drop rule, the answered/silent device-query matrix as data (executed against a real headless by @kolu/pty-host’s contract tests, so policy and implementation can’t drift), the bracketed-paste delimiters, and the snapshot-reciprocal TTY reset. Born when the second terminal client (kolu-tui attach, #1255) made the fragmentation visible: the same concept lived in kolu-common, pty-host, and pty-tui, held in lockstep by prose — and the browser may not depend on @kolu/pty-host, so a leaf both sides import is the only receptacle that dissolves the cross-references. Hashed into the pty-host staleKey (a protocol change is observable daemon behaviour; pinned by buildId.closure.test.ts). A leaf (bounded protocol tables, not a transport-grade volatility) — the serve-dir tier, not the surface tier. |
#1255 | done |
| @kolu/xterm-kit | The accumulated high-level xterm.js machinery — pinned _core internals with two failure philosophies, in-place scrollback surgery + seam-token write path, the mirror anchoring lifted from kaval, and the SolidJS lifecycle (WebGL single-owner + context-loss recovery, owner-correct async dispose across await) — as a runtime-neutral core + /solid adapter; kaval and the client as two consumers. (archived plan). |
#1795 (PR 1) #1808 (PR 2) |
core graduated <Xterm> wrapper |
| osfacts + osfacts-client | How an OS answers “which processes in this subtree hold listening TCP sockets, and on what addresses?” — one Rust binary on both platforms; TypeScript client is osfacts-client (no @kolu scope; exactly one npm runtime dep, effect, pinned to the same version both consumers already pin — its spawning verbs hand back Effects, so an abandoned read kills its child instead of running out a five-second deadline). The former @kolu/port-scan package dissolved: OS volatility lives in osfacts; kolu policy (bind judge, U-row blindness) in padi; browser-safe PortInfo + fold in @kolu/terminal-vocab. Test ② measured: darwin’s mechanism changed three times before settling on osfacts. Consumers call scanSubtreePorts (padi) / snapshotSubtree (client) and learn none of the OS detail. Test ①: pid → identity join is the CALLER’s. Test ③ closed: drishti shipped as the second consumer of osfacts-client (#122), retiring its own hand-rolled readers. |
#1982 · #1996 | extracted ③ two consumers |
| @kolu/port-forward | How a port that answers only on some other machine’s loopback is made to answer here — an ssh -L child on a connection of its own for a remote target, an in-process TCP relay for this machine’s own loopback, behind one ForwardManager receptacle (create idempotent by target · cancel · list · dispose · onLost). Test ② is the strong one and it is measured: the mechanism was rebuilt mid-PRT0 when a shared ssh master was proved unable to give the one property the whole feature rests on — a live sshd showed -O forward listeners outliving their requester by the full ControlPersist window, with no -O list to re-adopt them — so lifetime won and each forward took its own connection. Consumers call create(target) and learn none of it. Test ① passes by construction: a target is (host, port) where host is any ssh destination, so nothing of terminals, tiles or kolu’s host model crosses the boundary. Test ③ is PROVEN, not pending — PRT0 shipped a standalone TUI consumer (vazhi; since removed) beside the library with no kolu running, and PRT2’s Inspector embed was the second genuinely independent app: embedding it changed the library’s mechanism API by exactly one field (lastLocalPort, so a restarted dev server comes back on the URL you already have), and BOTH of the shape changes the plan predicted a second consumer would force — a list() subscription, a tagged error union — turned out unwanted when that second consumer actually arrived. Production consumer today is kolu’s Inspector only. |
#1975 (PR0) · #1991 (PRT2) | done · consumer: kolu’s Inspector |
| holdOneLink (candidate — recorded, not extracted) | One live connection to a dialed surface — memoized across concurrent callers, dropped the instant the transport announces its close (identity-guarded, so a late announcement from a disposed predecessor cannot stomp the successor a redial already published), re-dialed on demand, disposed under a teardown latch. Test ② is measured, not asserted: the axis has moved twice in this tree — #1719 split dead-vs-transient transport tags, and #2117 moved discovery→announcement. Test ① passes by construction: nothing of terminals, padi or MCP crosses it. Test ③ is where it stands: three hand-rolled implementations today — surface-mcp’s shared connection slot and its ResourcePusher attachment (packages/surface-mcp/src/{server,pusher}.ts), and surface-daemon-supervisor’s holdConnection (endpoint.ts, which already does the identity-guarded onClose→drop in almost the same words); @kolu/port-forward publishes the same verb as a required member (onLost). odu’s redialingAClient is deliberately not counted, and the reason cuts against this row rather than for it: it opens a fresh .ci/odu.sock for every upstream call and holds nothing, so it has no slot to invalidate and pays a dial per call to avoid needing one. That is a real alternative to this receptacle, not an instance of it — a consumer can buy the same freshness by making dials cheap enough not to memoize. The evidence it is one concept and not three: koluCliConnectionOf (kolu-cli/src/connect.ts) exists only because two packages declare the same handle, so the boundary can only be crossed by carrying each wire over by hand — under a shared HeldLink<C> it collapses to a client-narrowing wrapper with no per-field carry and no drift class. Home would be @kolu/surface beside links/ + isDeadTransportError — not a new package: a parallel receptacle for an axis the supervisor already encapsulates is the duplicated-receptacle trap. Extraction is deliberately deferred (prove-then-extract); this row exists so the candidate cannot evaporate. |
#2117 | candidate · ③ population 3 in-tree, extraction unproven |
| @kolu/solid-browser | The Code tab is a browser — location/address model, back-forward history, cross-content-type link interception, GitHub-relative resolution, sandboxed-iframe lifecycle. git is an injected resolver; the renderer packages plug in. (archived plan). | #1191 | phases 1+2 primitives + createBrowser history shipped · <Browser>/gitResolver deferred, ③ proof-pending |
Plus external electricity correctly leaned on instead of hand-rolled: Corvu (dialog/drawer/focus-trap/scroll-lock), @solid-primitives/* (resize-observer, media). Origin: a 41-agent Hickey/Lowy audit (16 candidates) → its “ship now” tier landed in #1089; a framework-scale re-test + adversarial skeptic surfaced the missed solid-xterm.
Built: @kolu/xterm-kit
Considered — not electricity
Looked framework-sized (the audit’s biggest braids), but fail the bar — recorded so they don’t get re-proposed.
| Candidate | Verdict | Why |
|---|---|---|
| defineMutation (optimisticOverlay) | helper | One module, one domain (canvas layouts); the write echo is already owned by surface’s metadata subscription. Pending is a UI cache, not a transport axis. |
| @kolu/fs-watch | helper | Owns a refcount/debounce lifecycle, not a streaming transport — no wire contract, Node-bound. A @kolu/shared helper, not a socket. |
| @kolu/geometry | domain-coupled | TileLayout binds it to a server-persisted domain type; a Rect package re-adapts at every boundary — moves the braid, doesn’t dissolve it. |
| @kolu/surface-publish | already-covered | Surface’s cellHandlers/collectionHandlers/pollOnEvent already own server snapshot+delta. Only terminal.attach is a raw escape hatch. |
| @kolu/commands (palette) | domain-coupled | Palette engine is already agnostic; all coupling is isolated in createCommands, which must stay in the app. Extracting deletes ~0 kolu lines. |
| God-procedure split | refactor | Its biggest dissolvable chunk is xterm-kit (above); the rest is kolu orchestration — a refactor, not a receptacle. |
Bar from Juval Löwy, Righting Software (receptacle/volatility) + Rich Hickey, Simple Made Easy (complecting); one-socket-not-a-topic-bundle is .agency/lowy.md §6.5. file:line verified against the codebase; ③ demonstrated by drishti — a second consumer (kolu is the first) that vendors @kolu/surface via npins.