Imported from zeroaltitude/theseus (
cockpit/AGENTS.md). Install upstream withnpx skills add zeroaltitude/theseus --skill cockpit. Copyright stays with the author.
cockpit
The cockpit (spec §3.14; Part III Items 23 and 27): the daemon's web UI, a Vite app served by theseusd at /.
It took the place of the first one, the Observatory, on 2026-10-03 (theseus-vm3n.6), and its old address,
/cockpit/…, redirects to the same route. React, Tailwind, ECharts, React Flow, and TanStack. Its build,
crates/theseusd/cockpit/dist, is not committed: the gate builds it, and the install builds it before the release
build.
What's here
src/views/: the fourteen views. The Ship (Ship.tsx, the landing view at/ship), thenBridge,Fleet,SessionDeck,Actions,Boundaries(the boundaries board),Ledger,Money(the money river),Economics,Speed(the speed wall),Policy(policy.explain: each place's tools, layer by layer, and the tightenings with their undo; M7 42b),Judgment(Jev's judgments, M5 23b, overjudge.listandjudge.get, withsrc/lib/judgment.ts),Systems,Ontology(the kinds, the category tree, guidance, and topics; it shows the present only). A session's memberships are in its deck's Context tab (src/components/Memberships.tsx).- The time machine:
src/components/TimeMachine.tsx(the ship's log, at every page's foot),src/lib/history.ts(the whole ledger, read once withledger.tail'safterand followed),src/lib/timemachine.ts(the fold, its checkpoints, and the log's axis),src/lib/marks.ts(the marks: a start whose build differs from the one before it is an install), andsrc/lib/world.ts(useWorld(): the lists as of the moment). src/ship/: the Ship's parts.model.ts(the graph as a fleet, and its layout: pure, no three.js),engine.ts(three.js, drawn directly),loop.ts(when a frame is drawn: every display frame, a paced rate, or none; pure),motion.ts(the motion table: every motion, the one event that starts it, how long, its pace; pure),sea.ts(the living sea's height from the work; pure),shaders.ts,post.ts(the glow, and the swell's composite),labels.ts(nameplates and tags, HTML over the canvas),words.ts(every shape's words: the labels, cards, key, tour and the session deck read them; pure),keyset.tsandKey.tsx(the key),HoverCard.tsxandplacement.ts(cards beside their point, off the instruments; pure),Tour.tsx,tourText.tsandnews.ts(the tour, and what's new after an update; pure),sound.ts,audio.tsanduseShipSound.ts(the three cues: which push sounds which, pure; the sounds, made with Web Audio),instruments.tsx(the brass gauges),Minimap.tsx,useShipData.ts(the reads and pushes it composes),flares.ts(a failure flares its ship once, whichever of its push, its execution's change and its ledger row tells the page first; pure, theseus-1skt), andsynth.ts(a seeded 10,000-node fleet for measuring).- The watch, the Ship's column of six plates (theseus-hnof):
src/ship/Watch.tsxdraws them,src/ship/watch.tsworks out five (working, waiting, slow against each job's usual, spent with a day of 23 to 25 hours, went wrong) andsrc/ship/since.tsthe sixth, since you last looked, whose stretch this browser keeps (cockpit.watch.looked) and whose replay runs the time machine. Keys 1 to 6 toggle their overlays. Their tests aretest/watch.test.tsandtest/since.test.ts. src/components/PromptPicker.tsx(beside the composer; its pure parts aresrc/lib/prompts.ts): runs an MCP server's prompt as the next turn, a field per argument, throughturn.submit { prompt }.src/components/Extensions.tsx(in Systems; its pure parts aresrc/lib/extensions.ts): the loaded extensions (M7 43b) fromextend.list, each with its manifest, digest, files, tests, who acked it, calls, errors, and Revoke (extension.revoke, confirmed first), then the proposals not loaded.src/components/TaskGraph.tsx(in Actions; its pure parts aresrc/lib/taskgraph.ts, M7 39b): the task records as a tree (state, owner, claim, version, a waiting change with its card) and, at?taskgraph=1(&task=<id>), as a React Flow graph. It shows the present: under the time machine it says so and its acts are off.task.changedreadstask.listagain (bindPush).src/components/Budgets.tsx(Money's Budgets panel; its pure parts aresrc/lib/budgets.ts):budget.list's tree, the burn per hour from the history'sprovider.callrows, recent resets, the questions waiting (withConfirmCard), the judge's day and the AWS hands.src/components/Tightenings.tsxis the tightenings' list with their undo, shared by Boundaries and Policy. The Ledger's filter, saved filters, export and follow count aresrc/lib/ledgerview.ts; the Policy view's summaries aresrc/lib/policyview.ts.src/components/: the call and model-call inspectors, the transcript, the flame chart, the shell, andbrass.tsx(the plank strip and the coin).src/lib/:rpc.tsandhooks.ts(the connection and its queries),derive.ts,summary.ts,format.ts,money.ts(the catalog's rates and a call's split by token kind),verdict.ts(18a's verdicts in words), andcalm.ts(calm mode),scores.ts(a notified call'srisk N% (shadow), M5 24),fallback.ts(a refusal's fallback in theseus-protocol's words, theseus-7gir.18),sandboxwords.ts(L1 in the CLI's words),drafts.ts(what was sent and not yet written), andontology.ts(the category tree's order, and what a session's next compile would change).- The chart method (theseus-hnof.4):
src/lib/chart.tstakes its look fromsrc/lib/viz.ts;src/lib/palette.tsholds the method's palette checks, whichtest/palette.test.tsruns on every palette the charts draw with;src/lib/chartview.tskeeps which charts show their table in the address (?table=) and measures the widths a label needs;src/components/instrumentTables.tsgives the shared instruments' legends and table views;src/lib/calls.tsis a billed call as the cockpit reads it (pure;derive.tsre-exports it);src/lib/spans.tsflattens a turn's trace for the flame chart and the deck's timeline;src/lib/sessionrows.tsgives the session deck its session's rows from the one copy of the ledger (theseus-kuzw). - The frame (theseus-hnof.5):
src/components/Heartbeat.tsx, the heartbeat bar across every view (the profile chip and its menu, UP, the health lamps withsrc/lib/healthwords.ts's words, and the moment's "then" under the time machine);src/lib/flow.ts(rows a second, on the activity strip's bar);src/lib/activity.ts(the strip's lines folded, numbers aside, every line kept);src/lib/mode.tsandsrc/lib/daylight.ts(night or daylight, and each night colour's daylight step);public/mode.js(the mode's class before the first paint). src/protocol.ts: the protocol client, imported as@protocol. It re-exports the protocol's types,src/protocol.gen/, which theseus-protocol's test writes from the Rust ones: never edit them by hand.- The look is in
index.css's tokens and the shared components (.panel,.brass-card,.brass-button,.panel-title): brass, ivory, and night navy, with colour carrying state. Restyle there, not view by view.
Invariants
- Read only what is on screen. A panel reads the ledger only while it is open: both inspectors once polled 5,000 rows every 3 s with neither open (Item 27).
- Every control is confirmed first, and each is a protocol method, judged by the core as any surface's.
- Every view keeps its state in the address, so any view deep-links.
- Every chart has a table view, the same numbers as rows, its toggle in the address (
?table=). - A colour set outside an ECharts option follows the mode:
mode.tsswapstoneHexandTONE_MARK, and an option goes throughdaylight()asEchartdraws it. - Inside the Ship's night island a tone is its CSS token, never
toneHex, which the island re-points. - The Ship and the ship's log's track stay at night in either mode.
- The Ship shows only the daemon's data, and moves only when something happens (theseus-hnof.2). Every motion is
a row of the motion table (
src/ship/motion.ts): one event starts it, and every term of the shaders that moves with time names its row in amotion:comment, which a test reads (a new animation with no row fails it). A one-off (a flare, an oar growing out, a result flashing back) draws every display frame for its seconds; a state that moves while it lasts (oars rowing, a gear, a wake, a flow) a steadySTEADY_FPS; the swell aloneIDLE_FPS, one composite pass a frame. A state that only waits (a question for the operator, a failure's pennant) is lit, not moved. The sea is the work (the owner's C5,src/ship/sea.ts): its height is tokens a minute and the turns running, dead calm (exactly 0) when nothing happens, so an idle Ship draws no frame at all; a hidden tab draws nothing. Calm mode (?calm=1, the toggle, orprefers-reduced-motion) stills it all and drops the glow and every chart's transitions: a change draws one frame.?swell=0stills the sea for one page. - Sound is off until the operator turns it on (the Ship's Sound button, kept in the browser): three cues on the
daemon's own pushes and ledger rows (
src/ship/sound.ts), made in the browser with Web Audio (audio.ts), no recorded or third-party sound. A cue is a row ofCUES, with its events and its source. - What's new is one item an addition (
src/ship/news.ts): an update that adds something to the Ship adds a stop there with its date, which movesCOCKPIT_VERSION; a browser that has seen the tour flies to only the new stops, once. - The past is folded, never invented. A view that shows the time machine's moment reads
useWorld()(null while live), and its acts are off while it does. A new kind of row that changes what a view shows needs its step intimemachine.ts;window.__timeMachine.checkNow()(dev and bench builds) folds to the present and lists every difference from the daemon's own lists. - One copy of the ledger. Read it through
useHistoryRows(), never a secondledger.tailloop: a view that shows it at once reads it urgently; the ship's log reads it unhurried, so a page's own first reads come first. A hook that acts only on new rows (the Ship's horn and flare, for a session whose pushes the page does not hear) takes them withonNewRows, so it draws nothing for a read that brings none of its kinds. - A scrub redraws in under 100 ms. The fold keeps an unchanged entry the same object, so rows memoized on it do
not draw again; a heavy view takes
useDeferredValue(useWorld()), and a laid-out graph follows the needle only once it rests (useSettled).
Building and checking
-
npm ci, thennpm run lint(oxlint),npm test, andnpm run build(tsc -b && vite build). The gate runs all three, before the suite, and runsnpm cifirst whencockpit/node_modulesis missing (offline from npm's cache when it can). -
npm testis node's own runner overtest/*.test.ts, with node stripping the types: no dependency. A module it tests is pure, imports nothing but the protocol's types (import type, which node erases), and names its own imports with their.ts;src/lib/marks.ts(the ship's log's marks) is the first. -
A release built without the cockpit's build serves a page at
/that says how to build it. -
After a change, load every view from a scratch daemon of the build and check that each loads clean, with no console or page errors.
-
The synthetic fleet never ships.
synth.tsloads only in a dev or--mode benchbuild (?synthetic=1); check that a production dist has noses_synthin it.
Measuring the Ship
- Headless Chrome on this machine renders WebGL with SwiftShader, a CPU rasteriser (the GPU path through WSL's D3D12
runs below one frame a second), so its frame rates are a floor. Measure on a quiet CPU (
/proc/pressure/cpu): another agent's build halves them. ?bench=1renders every frame;?scale=1pins the internal resolution (otherwise it adapts to the frame time);?swell=0stills the sea, for numbers comparable with builds before the swell;window.__ship.statshas the frames, the frames of the swell alone (swellFrames), the first frame's time, and the scale.- Under a CPU rasteriser, half-float targets and full-resolution texture reads are what cost: keep the post's targets 8-bit and its composite at four reads a pixel (the sea, the fleet's layer, and the two blooms).
- An idle Ship costs nothing: the swell rolls only with work (the living sea), so a Ship with nothing running draws
no frame, Live mode included (it drew
IDLE_FPScomposites a second, idle included, until theseus-hnof.2: 2.4 to 3.7 cores of SwiftShader at 1920×1080, theseus-wp2d). While the sea rolls, each swell frame is the composite alone; a GPU's cost is unmeasured (theseus-n2hd). A headless page left open while work runs takes?swell=0or?calm=1; whatever moves with the swell goes inSEA_SWELL(shaders.ts), worked out per pixel from the camera's ray in the composite, never in the sea's cached pass, whose redraw was about half again a swell frame's cost. window.__shipEngine.motions(dev and bench builds) lists the motions of the last frame, andseaLevelthe sea's height;window.__shipCuesthe sound cues played.- Headless Chrome never hides a page (a tab behind another, or a minimized window, still runs requestAnimationFrame
at 60 a second), and it composites Live mode's CSS animations all the time. To check a hidden tab, set
document.hiddenand firevisibilitychange; to price the swell, compare Live against Live with?swell=0, not against Calm, which also stops those animations.
Traps
- The dev server connects straight to a scratch daemon (
THESEUS_DEV_DAEMON, default 127.0.0.1:7434, never the operator's 7433). Set that daemon's[web] dev_origin = "http://127.0.0.1:5174"while you work, and stop the dev server when you're done. Another port:npx vite --port 5175, and setdev_originto match. - A GLSL comment inside a template literal must not contain a backtick: it ends the string, and Vite's error names the shader file, not the cause.
- Never add a
/wsproxy (theseus-88im): it relays other pages and users to the daemon from your socket. - The first load after adding a dependency can fail while Vite re-optimizes ("Outdated Optimize Dep"). Load it again.
