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.” Functional decomposition splits a system by what it does; volatility-based decomposition isolates each area likely to change behind a stable interface — the way a body keeps blood-pressure, pulse, and salinity volatility “encapsulated behind the service called the heart.” An electricity, here, is one such volatility pulled out into its own package. — Juval Löwy, Volatility-Based Decomposition (excerpt from 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 (surface-connection). 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/solid-xterm (createSolidXterm) | xterm.js lifecycle — 8-addon orchestration, WebGL single-owner + context-loss recovery, FitAddon resize, owner-correct async dispose across await. | #1116 (plan) | to build |
| @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. 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.
Next: extract @kolu/solid-xterm
// one primitive (not a topic-bundle, per §6.5):
createSolidXterm({ container, theme, fontSize, canUseWebgl?, enabledAddons?,
onData?, onResize?, onLinkActivate?, onCustomKeyEvent?, plugins? })
-> { terminal, searchAddon, hasWebgl, renderer, dispose }
Owns the agnostic core; kolu bits (link provider, soft-keyboard, diagnostics probes, the PTY terminal.attach stream) stay consumer-injected via callbacks. Dissolves ~240–314 lines of Terminal.tsx. Survived the skeptic (“the domain braid runs too deep”) on the interface — those are orchestration concerns, not xterm-lifecycle concerns. Graduates the way surface did: any SolidJS app embedding xterm can plug in.
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 solid-xterm (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.