Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Home

The client’s first page: the fleet - one row per project forge.toml declares, carrying the strongest state among its seats, its tree, its counts and its named misses. Nothing is mixed: a project’s own world opens as its board, and the home stays the glance. It is drawn from one snapshot of the home subject, and redrawn from the update stream after it; the page’s own 30-second tick moves the marks derived from the clock. The drawing it is held against is web-home.html, beside this page.

The app opens here whenever it has an address that answers - the one it last connected to - and on the connect screen when it has none, because its only input is the server URL. A deep link is the exception: it keeps the page it was addressed at and is handed the connection, since a session URL is how a seat stays reachable. Until a server answers there is nothing to draw, and the client never falls back to bundled data.

What it draws

RegionShowsRead from
Stoppedthe core’s last fatal, above everything else: forge stopped: and the core’s own wordsfatal_error
Upstreamthe core’s held service status, above the band and only when there is one: claude status and the status line’s own words, in the warn tone or the bad one an error report takesservice_status
Headerthe brand mark, forge, and the three builds as labelled pairs - server (the forge serving the socket), client (this app) and claude (the CLI both drive) - with socket and its protocol beside them; each update notice sits on the build it belongs to (in a browser, the latest published), and the fleet totals close the lineClientSettings.mark from the greeting; forge_version_short; CLIENT_VERSION; PROTOCOL_VERSION; cli_version; the shell’s update check, or the served latest.json; agents, fleet and projects counted
Bandsix cards: the gateway listener, the client’s own address, dictation, the account pool, the extensions, and the settingsaccounts.gateway, the connection the client made, dictate.snapshot, accounts.loading, the extensions subject
Fleet sectionsup to three sections, the rail’s own: needs you, working and asleep, each appearing when it holds a project and carrying its count; the asleep section starts expanded here but folds behind its count when closed, so the page can put the quiet half awaythe same rank the rail groups by, read from each project’s strongest seat - an ask first, then a run, then a sleep; a decision wait is a mark on the row and never a rank
Fleet rowone per project: the strongest seat mark, the project, its branch and changed-file count, when it last moved (the newest write among its seats and the sessions its catalog remembers, now/3h/2d, or never for a project with neither a seat nor a session behind it), its seats, live of cap slots · queue, the named misses, and the ways in - the row itself, each seat, and the boardfleet for the counts and the misses; projects for the tree; agents and projects[].sessions for the seats and the age
Decision-wait marka glyph and a count on a project row and on each seat row that holds a wait, never colour alone. Opening it lists the wait’s seat, its row’s subject, the ask’s own words - the row’s detail, the look a park is owed, the subject when the ask carries none, or the board’s sentence when even the subject is blank - and its age, and each item opens the project’s board, where the row waits and can be answered. Opening a seat does not clear a wait: it ends when the row leaves waiting, and the mark goes with the read that drops itfleet[].attention

The fleet reads in the same three sections the session rail draws, ranked from each project’s strongest seat: a worker holding an ask lifts its project into needs you, and a project with no live seat ranks asleep. A decision wait does not move a row (Ved, 2026-11-03): a task wait is often future work and its seat can be asleep, so the mark and its count stay on the project’s row and on the seat’s chip while the row keeps the group its own state names. The asleep section folds, like the rail’s, but starts expanded on this page - the rail keeps its own folded, because there the fleet is a column beside the conversation rather than the page itself. Its count rides the heading, so a closed section cannot read as an empty one.

Every cell of a fleet row is the server’s own read. The mark is the strongest of the project’s seat states, drawn with the same shapes a seat row uses - or, for a project holding no seat, the rail’s dormant mark: asleep once the catalog remembers a session, never only when nothing has ever run there; the counts are the server’s (slots is the project’s resolved worker cap); and a miss is named rather than implied - a queue stalling with a free slot, a worker holding no row, or a project that cannot start, which is the refusal the row drew before the fleet. The whole row opens the project’s lead in chat - the name’s link stretches over the row’s area, while a seat chip opens its own seat and open board opens the board. A project with no live seat is started by that same click, as a sleeping seat is from a session page’s rail.

Four cards are ways in. The dictation card opens the models page, where the pins, the catalogue check and the feed are drawn; the extensions card opens the extensions page, where the plugin inventory, its components and the marketplaces are; the settings card opens the settings page, which a desktop shell cannot otherwise reach because it has no address bar; and the server card opens the connect screen (below). All are links at rest - a chevron on the card’s title line, drawn dim rather than uncovered by the pointer, because an affordance only the hover uncovers is one a touch reader cannot find - and all take Enter and Space. The other two cards, the gateway listener and the account pool, open nothing.

The extensions card reads its own subject. What a reader wants on it - how many plugins are installed, and whether any is behind its marketplace - is in the extensions read and nowhere on the home’s own wire (the home snapshot carries only the update records), so the card subscribes that subject from the home and takes it with the page. Its states are its own: reading before the read lands, unread for a refused subscription or an inventory with no plugins root to scan (with the core’s own reason on the page behind it), a warning when something is stale, naming how many updates wait, and quiet when nothing is, naming the marketplaces the card draws nothing of.

The header names each build it draws. server is the forge serving the socket (the build in forge_version_short), client is this app’s own build, socket is the protocol they speak, and claude is the CLI both drive. The labels keep one build’s version from being read as another’s, and each update notice sits beside the build it belongs to.

The band’s server card is the way back to the connect screen (Ved, 2026-10-12): the card names the forge this client is on - host:port, as a reader recognises it - and the whole card is a link to /connect, the other door cards’ own pattern. It is how the client is pointed at another forge - a scratch stack, a second machine - without a relaunch.

The row’s tree cell is the project’s own read. It carries the branch the tree is on and how much has changed in it; a count of zero draws nothing, because an unchanged tree is what the cell already means when it is empty. A tree that could not be read says so in the cell’s place - not a git repository, its working directory is not there - because a blank branch beside a real project reads as a tree with nothing to say rather than as one that could not be asked.

The state a row carries

The state is decided by the server and arrives decided. The client promotes five cases of its own before drawing it - background work, a turn nobody watched, an unanswered ask, a failure the seat has not been shown since, and a session that ended and has not been shown since - and draws it as a shape as well as a colour, so the row reads without colour:

StateClassMeans
Runningrunninga turn is in flight, or background work is running
Spawningspawningthe subprocess is coming up
Restartingrestartingthe subprocess died while it was up, and forge is between restarts
Idleidlealive, nothing in flight
Finishedunseena turn completed on a seat no client was showing
Needs youneedsa question, a permission prompt, a Slack draft, a browser, terminal or document hand-off, a held configuration write or a held vault call is waiting
Sign-in neededauththe bridge is waiting on /login
Failedfailedthe session is not running: its spawn never came up, or it died and nothing brought it back
Session endedfaileda session on the seat ended, and the seat has not been opened since
Turn failedfailedthe newest turn ended in failure, and the seat has not been opened since
Asleepasleepthe seat is not running, and no failure is recorded for it
Never startednevernothing has ever run in this project

A project that cannot start does not draw a state of its own: the refusal is one of the named misses instead, which is the same fact placed where a miss already lives. A row that failed carries its reason as a line under it, and so does a row a session ENDED on - the lifecycle beside the mark is the truth about now, so a seat that came back is counted live by the fleet and the header and is somewhere the close walk can land while the mark and the line say what happened; the mark goes the moment the seat is opened. A failed TURN has no reason text of its own (the failure’s words are in the seat’s conversation), so its line is the words a turn failed. A seat between heal attempts reads restarting (N of M) - the attempt forge is on and the cap from [sessions] heal_attempts, or restarting (attempt N) when the server names no cap - under the project’s name, and beside the seat on the rail: the mark says the seat is working, the line says what is happening to it, and nothing about it is the reader’s to act on.

The states the page can be in

  • A fleet, which is the ordinary case.
  • A stopped forge: the core’s last fatal draws above everything, in the core’s own words. It reaches this page from the snapshot’s recorded fatal, so the attach that sees it is one landing inside the wind-down window before the process goes; the live announcement is the fatal’s own frame, drawn in the chat.
  • An upstream issue: the core’s held service status draws above the band, in the status line’s own words. It reaches the page from the snapshot the same way - the probe fires once, before a client attaches, so a page that only took the report off the update stream would draw nothing for an outage that was already up. The live announcement is likewise a chat row, one per report. With nothing held, no banner draws.
  • A fleet mid-change: a row can move between any of the states above, and the page redraws from the stream without the reader losing their place.
  • Nothing configured: no projects at all draws the empty state, naming forge.toml and the [[orgs.projects]] entry that would add one. A page that drew an empty list here would read as broken rather than as unset.
  • An update available: the header names the published version when npm has one strictly newer than the installed CLI. Both sides have to resolve for it to appear at all, so a probe that answered one of them draws nothing rather than claiming an update it cannot see.
  • This app’s own update: the header draws ↑ vX available beside the client pair when a newer release is published, checked once at launch. The line starts as checking for updates... while that check is in flight
    • on the phone it is a network fetch - and settles to the offer, to nothing on a build that is current, or to the failure line below. It is a control, where the CLI notice beside it is not: clicking installs the update and the line becomes ready - restart to finish, which is the click that swaps into the new build. On the phone the same click opens the release page in the browser, where the APK is installed by hand; the line goes back to the offer, so a second tap reopens the page. A failed install draws - update failed, retry. A check that could not run draws update check failed - <reason>, in the shell’s own words and as text rather than a control: a build that is current and a check that failed are different facts, and the reason is what tells them apart.
  • A browser tab: outside the shell there is nothing to install, so the header draws the two facts instead - client vX, the build it is serving, and latest vY when the app it was served with carries a manifest naming a newer release. It is text, not a control: the next load carries the new build. A page nothing serves a manifest to - the dev server - draws the build alone.
  • Connected, waiting for the first read: the door has gone and the fleet has not arrived. The server builds a home snapshot by reading each project’s working tree, so the window is not instant, and it is its own state rather than the connect form handed back - a form here would re-render with the address reset and a live button, and a second Enter would open a second socket. A read the server does not answer is asked again once its bound runs out, so the state ends on its own rather than only when something else moves.
  • The home was refused: the server turned the subscription down and its own words are drawn. The address is not the thing that is wrong, so the door is not what is shown.
  • A home this client could not read: the snapshot arrived malformed, or the store reports it ready and holds nothing. The page names the reason in the fleet’s place rather than drawing an empty fleet, which would read as a forge with no projects. Nothing periodic retries it: the next update that asks for a read, entering the page again, or a reconnect, reads again.
  • A connection that dropped: the page keeps what it last read and says so above it, and the notice stays until a fresh read lands rather than clearing the moment the socket reopens - the window between those two is the one place a reader cannot tell stale rows from current ones.
  • A forge on another protocol: the shell draws one line above the page naming the command that updates the stale half and this client’s build - and the answering build where the greeting carried it - and the page behind it is live. Nothing is refused over a version: the notice is the whole answer, drawn wherever a surface meets the skew. It is the one the shell stands down on at the door’s route, where the door draws its own copy in its own column.

The unseen mark is drawn from a read of its own. unseen is the list of seats whose last turn finished while no client was showing them, and nothing in the records reconstructs it: a turn that ended before any client opened the seat leaves nothing in the transcript to say it went unwatched, so a page reading only the messages would draw every settled seat as idle. The server keeps that fact and sends it; the client draws it and computes none.

Watched is not the same as subscribed. This client keeps every seat it has visited subscribed, so the seat’s frames keep arriving after its page closes - but a subscription is not a showing: the client tells the server which seat is on screen, and that is what spends the seat’s diamond and failure mark. The death trace is spent by the seat being shown, or by the reader’s OWN prompt - a cron’s, a peer’s or forge’s own does not count as looking. A turn that finishes on a visited seat nobody is drawing therefore arms its diamond, and opening the seat spends it; the subscription is what keeps the return instant, not what clears the mark.

Two things this page does not draw are the schedules a project holds and the account a row chips. Each is drawn elsewhere: the schedules on the session page’s strip, whose schedules row keeps the seat’s own set (a cron names the seat that created it) - read from the board subject since #2517, which is also why the home’s own read is a few kilobytes rather than the board’s hundreds - and the account on the session page’s rail footer, beside what it is costing. Neither is a gap in this page.

The two widths

The page is checked at 1600 and at 430, against the drawing beside it. At 430 the band becomes a two-column grid, the header wraps to two lines, and a fleet row’s cells flow onto second lines under the project name; nothing overflows horizontally at either width.

The sheet’s own breakpoints are at 900, 760, 700 and 560. This page’s own collapse is the fleet row’s at 900; its chrome’s is at 760, where the header wraps and the band drops to two columns. Nothing scoped to this page sits below that, so the 430 check covers what the sheet changes here.