Repository navigation
Spec: the Console, a web page for each machine's Commands #776
Description
From the #711 grilling. The decision record is
.scratch/grill-with-docs/711-live-runs.md; see its "Settled" list, Rounds 6 and 7 included. This Spec builds on #711, the CLI half, which has shipped: the Command record andstatus,show,logsandstop [--requeue]. The look (Q27) was decided from the prototype on the branchprototype/711-console; see Further Notes.
Problem Statement
#711 gives a terminal everything it needs to see and Stop a machine's Commands. Away from a terminal, especially on a phone, you still can't:
- see what needs you and what's fine;
- tell a Run that's slow from one that's stuck;
- read a Session log as it grows;
- stop a Run that started too early.
thirdshift status lists Commands, but it doesn't say which of them need you.
Two machines run thirdshift, thirdshift and stephens. Setting up a page on a new one shouldn't take more than a command and a paste, and someone else's machine shouldn't need anything of yours. A page you look at every day should also look the way you like.
Solution
Each machine serves its own Console: a web page at an address of its own, here console.thirdshift.app and thirdshift.stephens.page. It opens on the Inbox, which groups the machine's Commands by what each asks of you:
- Needs you: Silent and Slow Runs first, then Commands that died or failed, Stops left for triage, and Security findings waiting on you;
- Ready for review;
- Landing;
- Working;
- Done;
- Resolved.
A stall is silence, not a long stage. A Run is Silent when its logs stop growing, and Slow when it has gone past its stage's Usual range while still logging.
Seven pages sit a level above the Inbox's sections: 1 Inbox, 2 Lanes, 3 Stages, 4 Waterfall, 5 History, 6 Logs, 7 Security. A live Command can be stopped from any of them, with a confirm step.
The page has one layout and many Themes. Focus, the default, follows the system's light or dark setting, and Plain, Press, Signal, Garden and Factory ship beside it. You can make your own Themes, with your own fonts, and the chosen Theme is saved in the User config, so it's the same in every browser.
The page sits behind a sign-in by email and password. Only addresses allowed in the User config may sign in, and an address proves itself with a code sent by email. A Console log records sign-ins and account changes, and the Activity log records who stopped what.
The page does nothing the CLI can't: every view and action is also a command. thirdshift status shows the Inbox's groups and order too. thirdshift console serves the page from 127.0.0.1, with Caddy or Apache in front. thirdshift console install prints one script for sudo that sets everything up, and Setup offers it.
User Stories
The Inbox:
- As the Day shift, I want to open my machine's Console on my phone, so that I can see what the factory is doing without a terminal.
- As the Day shift, I want the Console to open on an Inbox that groups Commands by what they ask of me (Needs you, Ready for review, Landing, Working, Done, Resolved), so that what needs me is never below what's fine.
- As the Day shift, I want Needs you open on load and every other section foldable, so that the first screen is what needs me.
- As the Day shift, I want live problems first in Needs you (Silent, then Slow), then Commands that died, failed, were Stopped to triage, or have a Security finding waiting, each newest first, so that a Run I can still save comes before one that has already ended.
- As the Day shift, I want an ended problem to leave Needs you when a later Command on the same issue starts, so that I don't see what someone has already acted on.
- As the Day shift, I want an ended problem to leave Needs you after
inbox.days, 7 by default, so that a long weekend doesn't bury the Inbox. - As the Day shift, I want Done to show the last day and Resolved to reach back
inbox.days, so that I can find what a Run notification was about. - As the Day shift, I want each row to show a state word, never colour alone, plus the issue and title, a one-line now-line and the age, so that I can scan it on a phone and in any Theme.
- As the Day shift, I want Commands failing with the same cause shown as one item with a count, so that one broken base doesn't fill the Inbox.
- As the Day shift, I want each Run's Harness, Model and Effort, and its stage with its age, such as "CI watch · 52 min", so that I see what's running at a glance.
- As the Day shift, I want child Runs nested under their Command (a Pickup run, its Spec run, then its Tickets), so that I see the whole tree.
- As the Day shift, I want a Spec run's Tickets shown as a plan checklist: running, merged, failed, waiting on named blockers or not started, so that I see why it's slow.
- As the Day shift, I want a Command whose process is gone with no outcome shown as "died at ", so that a crash or reboot is visible.
- As the Day shift, I want each repository's idle Passes shown with their latest skip reason, so that I see why Weeding or Fencing waits.
- As the Day shift, I want the Inbox to update itself as Commands start, change stage and end, without a reload, so that I can leave it open.
- As the Day shift, I want the page to keep working if the live connection drops, by checking again at intervals, so that a flaky network on my phone doesn't freeze it.
- As the Day shift, I want the tab title to say "‹n› need you", with a dot on the favicon when something is urgent, so that I notice from another tab.
- As the Day shift, I want rows that are new since I last looked marked as such, so that I see what changed overnight.
Silent and Slow:
- As the Day shift, I want a Run whose Session log and Command log have both stopped growing for
inbox.silent_minutes(10 by default) marked Silent, so that a stuck Run stands out. - As the Day shift, I want a Silent Run to escalate in words at three times the silent time ("silent 45 min · past 30"), so that I can tell how long it has been stuck.
- As the Day shift, I want a Run past the top of its stage's Usual range, but still logging, marked Slow ("slower than usual (12–17 min), still working"), so that I don't mistake slow for stuck.
- As the Day shift, I want a Slow Run to escalate at twice its usual time ("3× usual"), so that a badly slow one rises.
- As the Day shift, I want the Usual range worked out per repository, from the middle half of that stage's last 30 times, and shown only once there are 3, so that chart35's long checks don't make thirdshift's look fast.
- As the Day shift, I want a stage that waits, such as CI watch or Self-merge, to write a line every
logs.waiting_line_minutes(5 by default), so that waiting on CI never reads as Silent. - As the machine's owner, I want
status, the page and Setup to warn when the waiting line isn't more often than the silent time, with thirdshift using half the silent time instead, so that a typo doesn't turn every CI watch red.
The pages:
- As the Day shift, I want seven pages (Inbox, Lanes, Stages, Waterfall, History, Logs, Security) switched with keys
1to7and kept in the URL, so that I can link to one and get back to it. - As the Day shift, I want a Lanes page showing each Command as a lane of its stages over the last 3.5, 12 or 24 hours, so that I see how long each step took.
- As the Day shift, I want a Stages page that sets each stage's time beside its Usual range, Run by Run, so that I can tell whether today is normal.
- As the Day shift, I want a Waterfall page showing one Command in depth, from Run down to tool call, so that I see where its time went.
- As the Day shift, I want a History page of every ended Command, newest first, filtered by outcome and repository, back to the oldest logs, so that I can look back as far as the logs go.
- As the Day shift, I want a Logs page listing Session logs and Command logs, live ones marked, so that I can open any log from one place.
- As the Day shift, I want a Security page of Security runs, their findings and their sessions, behind the sign-in, so that I can follow Security runs too.
- As the Day shift, I want a peek with a row's details beside the list, or as a bottom sheet on a phone, closed on load, so that I can look without leaving the Inbox.
- As the Day shift, I want one Command's details to show everything
thirdshift showdoes, so that I can see it all in one place.
Session logs:
- As the Day shift, I want to read a Command log on the page, so that I can see what it printed.
- As the Day shift, I want to read a Session log rendered as a conversation, with the agent's messages, tool calls and results, so that I can follow what the agent did.
- As the Day shift, I want consecutive tool calls folded into one group I can open, so that a long session stays readable.
- As the Day shift, I want thinking collapsed by default, so that the reasoning doesn't drown out the actions.
- As the Day shift, I want Bash commands, file edits (shown as diffs), file reads and searches each rendered in their own way, so that each tool is easy to read.
- As the Day shift, I want anything the page doesn't know how to render shown plainly, never hidden, so that nothing in a log is lost.
- As the Day shift, I want a live Session log to add new lines at the bottom as they arrive, without redrawing what's there, so that a long log stays fast.
- As the Day shift, I want to choose whether the view follows the newest line, so that I can read back while it grows.
- As the Day shift, I want both Claude and Codex Session logs rendered, so that it doesn't matter which Harness ran.
- As the Day shift, I want a Ticket's Run and its Session logs reachable from its Spec run, so that I can follow one Ticket.
Stop:
- As the Day shift, I want a Stop button on each live Command, and only while it's alive, so that I can stop a Run that started too early.
- As the Day shift, I want Stop to ask me to confirm, saying the issue will be labelled
needs-triage, with a "requeue instead" choice, so that a mis-tap on my phone doesn't cost an hour of work. - As the Day shift, I want the page to show the Command still ending, then its outcome and anything left on
origin, so that I know the Stop worked. - As the Day shift, I want the Activity log to say a Stop came from the Console and from which account, so that the repository's history says who stopped it.
Moving around:
- As the Day shift, I want
jandkto walk rows and section headings and wrap,spaceto fold a heading or open the peek,enterto open the full log, andescto close one layer at a time, so that I can work the Inbox from the keyboard. - As the Day shift, I want
gplus a letter to jump to a section or page,zto fold the current section,?to list every key andShift+Tto open Themes, so that nothing needs the mouse. - As the Day shift, I want to hide the sections I don't use, so that the Inbox shows only what I care about.
- As the Day shift, I want the header to name the machine and link to my other machines' Consoles, so that I can check the other one in one tap.
- As the Day shift, I want each page to show the CLI command that does the same thing, such as
thirdshift stop chart35#239, so that I can do it over SSH or hand it to an agent.
Themes:
- As the Day shift, I want the Focus Theme by default, following my system's light or dark setting, so that what needs me carries the most weight.
- As the Day shift, I want to choose among Focus, Plain, Press, Signal, Garden and Factory, each as auto, light or dark, so that the page suits me and the time of day.
- As the Day shift, I want my chosen Theme and hidden sections saved in the User config, so that every browser I use on this machine looks the same.
- As the Day shift, I want to make a Theme in an editor on the page and have it saved to my themes folder, so that I can make the Console my own.
- As the Day shift, I want to export a Theme as JSON and import one, on the page and with
thirdshift console theme importandexport, so that I can move it to my other machine. - As the machine's owner, I want to copy font files into
~/.thirdshift/console/themes/fonts/and use them in a Theme, served from the Console's own address, so that I'm not limited to the built-in fonts. - As the Day shift, I want motion only where thirdshift is doing something (the Working spinner, a heartbeat dot when a log moves, the live log cursor, the stopping bar, the running stage), so that anything moving means activity.
- As the Day shift, I want a Pause motion control, so that I can stop every animation.
- As the Day shift, I want every built-in Theme to pass AA text contrast, so that every Theme is readable.
- As the machine's owner, I want
thirdshift console theme <name>andthirdshift console sectionsto set what the page sets, so that the page does nothing the CLI can't.
Accounts and sign-in:
- As the Day shift, I want to sign in with my email and a password, so that nobody else can see my private repositories or unfixed vulnerabilities.
- As the machine's owner, I want only the addresses listed in the User config able to sign in, so that I decide who may.
- As someone on the allowlist, I want to create my account by entering my email, getting a code and setting a password, so that I don't need the machine's owner to set it for me.
- As someone on the allowlist, I want to reset a forgotten password with the same flow, so that I'm never locked out.
- As the machine's owner, I want the page's answer to an email to be the same whether or not the address is allowed, so that it never reveals who may sign in.
- As the machine's owner, I want codes to last 15 minutes, allow 5 tries, and be sent at most once a minute and 10 times a day per address, so that the flow can't be abused.
- As the machine's owner, I want sign-in to slow to one try a minute after five wrong passwords, so that guessing is too slow to work.
- As the Day shift, I want to stay signed in for 30 days from my last visit, so that my phone doesn't ask every time.
- As the Day shift, I want to sign out, and setting a new password to end my other sessions, so that a lost phone can be cut off.
- As the machine's owner, I want taking an address off the allowlist to end its sessions and block it at once, while its account is kept, so that the list is the one switch and a mistaken edit costs nothing.
- As the machine's owner, I want
thirdshift console password --email <address>to set any allowed account's password, so that accounts work without email. - As the machine's owner, I want
thirdshift console sign-out --email <address>to end that account's sessions, so that I can act from a terminal. - As the owner of a machine with no Resend key, I want the email flows turned off, with the page saying to ask the owner to run the password command, so that sign-in still works.
- As the machine's owner, I want a Console log of sign-ins, failed sign-ins, codes sent, passwords set, sign-outs and look changes, shown on the page and by
thirdshift console log, so that I can see who has been in. - As the machine's owner, I want page views not recorded, so that the log guards the machine without watching its readers.
- As the machine's owner, I want
thirdshift consoleto refuse to start with nobody on the allowlist, unless I pass--no-sign-in, so that it can't be served open by mistake. - As someone on a laptop or using an SSH tunnel, I want
--no-sign-into serve the page to localhost only, with no accounts, so that I can use it without setting up sign-in.
Setting up:
- As the machine's owner, I want
thirdshift console installto see whether Caddy or Apache is running and print one script forsudo, so that setting up a new server is one command and one paste. - As the machine's owner, I want that script to write a system service running as me, the proxy's site file and the HTTPS step, then start everything, so that the Console survives reboots and log-outs.
- As the owner of an Apache machine, I want the site file to stream live updates through Apache and get its certificate from
certbot --apache, as my other sites do, so that it fits how that machine already works. - As the owner of a machine with neither Caddy nor Apache, I want instructions for an SSH tunnel, and a README section for nginx, so that I can still reach it.
- As someone setting up a machine, I want Setup to ask whether to run the Console, its whole address and the allowed emails (defaulting to my Run notification address), then offer
console install, so that the Console is part of setting up thirdshift. - As the machine's owner, I want the Console to keep running across
thirdshift update, restarting itself on the new binary, so that it never serves from a stale one.
The CLI and agents:
- As the Day shift in a terminal, I want
thirdshift statusto show the Inbox's groups in the Inbox's order, with the Silent and Slow words, so that the terminal and the page never disagree about what needs me. - As an agent, I want
status --jsonto give each Command its group and health, andshow --jsoneach stage's Usual range, so that I can read what the page shows. - As an agent, I want nothing on the page that the CLI can't do, enforced by a test, so that I never need the page.
- As the machine's owner, I want the page to load nothing from other sites, fonts included, so that it works offline behind the proxy and leaks nothing.
Implementation Decisions
-
The process.
thirdshift consoleserves the page on 127.0.0.1, at a port from the User config, behind the machine's proxy (ADR 0016).- It runs on axum, and its async runtime stays inside the Console; the rest of thirdshift stays blocking.
- It re-executes itself when
thirdshift updatereplaces the binary. - It reads the Command records, Activity logs, Command logs and Session logs through the same code
status,showandlogsuse. - It runs as the machine's owner, so it reads the logs where they are and nothing is opened to the proxy's user.
-
The User config.
- A
[console]section holds:- the Console's address and port;
- the allowlist of emails that may sign in;
- the addresses of the owner's other Consoles, which the header links to (nothing is fetched from them);
- the chosen Theme;
- the hidden sections.
inbox.days = 7sets how long an ended problem stays in Needs you and how far back Resolved reaches. Done is one day.inbox.silent_minutes = 10sets when a Run is Silent. It escalates at three times that.logs.waiting_line_minutes = 5sets how often a waiting stage writes a line. When it isn't less than the silent time,status, the page and Setup warn, and half the silent time is used.
The
inbox.*andlogs.*settings are read by the CLI as well as the page, so they sit outside[console]. - A
-
The Inbox's model lives in the shared blocking code that
statususes, so the page and the CLI compute the same thing. It is a grouping of Commands:-
Needs you:
- live problems first: Silent, then Slow;
- then, newest first:
- Died: the process is gone with no outcome;
- Failed;
- Triage: a Stop that left the issue
needs-triage; - Finding: a Security finding waiting on a decision.
An ended item leaves when a later Command on the same issue starts on this machine, or once it's older than
inbox.days. Nothing is read from GitHub, and there's no acknowledge, shelve or steer. Failures with the same cause become one item with a count. -
Ready for review: ended
ready, with its pull request; a Pass's dispatched ready Run shows as its own row. -
Landing: live Runs at CI watch or Self-merge. One already in Needs you points there instead of appearing twice.
-
Working: every other live Command, with a Spec run's Tickets as a plan beneath it.
-
Done: Commands that ended in the last day and aren't in Needs you.
-
Resolved: Commands that ended from one day to
inbox.daysago, with nothing waiting.
-
-
Health.
- Silent: a Run in a stage whose Session log and Command log have both stopped growing for the silent time.
- Slow: a Run whose stage has gone past the top of its Usual range.
- Usual range: the 25th to 75th percentile of that stage's times over the last 30 Command records in the same repository on this machine, and none with fewer than 3.
Escalation is in words: "silent 45 min · past 30", "past usual 12–17 min", "3× usual".
-
Waiting lines. CI watch and Self-merge write a Command log line at least every
logs.waiting_line_minuteswhile they wait, such as "CI on f7c3940: 3 still running out of 17 checks", even when the count hasn't changed. Today CI watch writes only when the count changes. -
The CLI's side.
thirdshift status:- shows the Inbox's groups in the Inbox's order, with state words and the Silent and Slow wording;
--jsongives each Command its group and its health;--allstill adds the history.
show --jsongains each stage's Usual range.- New commands:
console theme <name>,console theme list,console theme import <file>andconsole theme export <name>;console sections [--hide <section>…] [--show <section>…].
- Unchanged from earlier decisions:
thirdshift console, with--no-sign-in;console password [--email <address>], which prompts twice and doesn't echo;console sign-out [--email <address>];console log [--json];console install.
-
Parity with the CLI. Each route is paired with the CLI command whose output holds its data:
- Inbox:
status. - Lanes, Stages and History:
status --all --json. - Waterfall:
show --jsonandlogs. - Logs and the log views:
logs. - Security:
status --all --jsonandshow --json. - Stop:
stop. - The Console log:
console log. - Setting the Theme:
console theme. - Hiding sections:
console sections. - Theme import and export:
console theme importandexport.
A test walks the routes and fails on any route without a pair. Each page shows its CLI equivalent. The HTTP routes are for the page only and promised to nobody.
- Inbox:
-
What the page writes. It writes only three things:
- a Stop;
- its own look under
[console]: the chosen Theme and the hidden sections; - Theme files in the themes folder.
It never writes the allowlist, the addresses, the
inbox.*orlogs.*settings, or any other section. Config writes keep the file's comments and layout, as Setup's do. Each write goes in the Console log, naming the account. The look is per machine, the same for every account. Per browser, in local storage, are "new since you last looked", Pause motion, what's folded and the peek. All of those are conveniences, and losing them hides nothing. ADR 0016 is amended to match. -
Pages. There are seven, a level above sections:
- Inbox: the groups above.
- Lanes: each Command a lane of its stages, over a 3.5, 12 or 24 hour window. Older Commands are on History.
- Stages: the stage grid. Rows are stages and columns are Runs, live ones pinned first. Each cell gives a state and its time beside the row's Usual range.
- Waterfall: one Command from Run to stage to tool call. Session log lines carry no time, so each tool call's time comes from matching it to its
HH:MM:SSline in the Command log. - History: every ended Command, newest first, filtered by outcome and repository.
- Logs: every Session log and Command log, live ones marked, with a reader.
- Security: Security runs, their findings and their sessions.
Keys
1to7switch pages, and the page is in the URL as?page=. The peek is the details beside the list on a wide screen, or a bottom sheet on a phone, and it's closed on load. -
The keyboard model comes from the prototype:
jandkwalk rows and section headings, and wrap.spacefolds a heading, or opens or closes the peek on a row.enteropens the full log.esccloses one layer at a time, then hides the selection mark.gplus a letter jumps to a section or page; a tooltip lists them.zfolds the current section.?lists every key.Shift+Topens Themes.- Moving the selection never re-renders the list.
-
Themes (ADR 0018). There is one layout, and a Theme is only tokens, applied as CSS custom properties. It can't change what the page shows, its order, or where things are. This shape comes from the prototype, trimmed:
{ id: "press", name: "Press", colors: { bg, panel, panel2, selBg, text, head, dim, rule, link, urgent, urgentText, look, lookText, working, workingText, landing, ready, readyText, done, proof, ramp, primary, term, termText, termDim, sheet, sheetText, glowWorking, glowUrgent, glowLook, ink1, ink2, ink3, ink4 }, fonts: { head: "Big Shoulders Display", body: "JetBrains Mono", mono: "JetBrains Mono" }, type: { headStyle: "display", headCase: "none", labelCase: "uppercase", bodySize: 13 }, shape: { radius: 0, density: "compact" }, tags: "filled", effects: { scanlines: true, misreg: true, halftone: true, glow: true, colorbar: true, crops: true, tilt: true }, motion: "full", }
- Text on a solid fill is black or white, whichever reads better, worked out from the fill.
- Built-ins are compiled in: Focus (the default), Plain, Press, Signal, Garden and Factory, each as auto, light and dark.
- Focus is "weight follows need". Plain is the quiet inbox look. Press is thirdshift.app's press look in plain words. Signal uses colour only for state, in Okabe–Ito colours. Garden and Factory are themed; Factory takes after Factorio.
- Your own Themes are
<name>.jsonfiles in~/.thirdshift/console/themes/. The page's editor saves them there. - Importing checks every token and gives defaults for any added later, so older exports keep importing.
-
Fonts.
- Built-in Themes' fonts are embedded as Latin-subset woff2 files at the weights they use, all under the SIL Open Font License: Big Shoulders Display, JetBrains Mono, Inter, Caprasimo, Nunito, Barlow Condensed, Titillium Web, Atkinson Hyperlegible, IBM Plex Sans and IBM Plex Mono.
- Your own fonts are
woff2,woff,otforttffiles in the themes folder'sfonts/. The Console serves them from its own address. It serves only those file types from that folder, and never follows a link out of it. - A Theme names a font as a built-in family, a system stack or a file in
fonts/. Any other name falls back to its stack. - Fonts are copied in by hand. The page never uploads files.
-
Motion belongs to the layout, not the Theme, and is only for thirdshift's activity.
-
Kept:
- the Working spinner;
- a heartbeat dot that beats when a log moves;
- the latest line flashing as it arrives;
- the live log cursor;
- the stopping bar;
- stripes on the running stage;
- the Silent alarm, which pulses three times and then holds.
Factory's conveyor and Garden's bed move only while a Command is live.
-
Pause motion stops all of it, to meet WCAG 2.2.2, and
prefers-reduced-motionis respected.
-
-
Visual calm. There are no coloured bars between sections; the title's bar is the only one. With Needs you open, every section heading fits above the fold on a 14" laptop. It works at 390 px with no sideways scroll.
-
HTML is rendered on the server, with templates checked at compile time.
- Libraries: htmx 2.0.7, its server-push extension 2.2.3 and idiomorph 0.7.4 (all 0BSD), plus a small hand-written ES module. The module does the keyboard, the peek, following the tail, the Theme editor, Pause motion and copying.
- Packaging: these and the fonts are vendored in the source and compiled in with
include_dir, because the crate publishes only its source, Factory skills, README and licence. - Nothing from outside: no npm in the release build, no CDN, and nothing loaded from another site.
-
Live updates.
- One server-push stream tells the page the Commands have changed, and the page re-renders with a morph rather than replacing anything.
- Each open Session log has its own stream, which sends rendered HTML for lines appended since a byte offset.
- When a stream drops, the page falls back to polling.
- The heartbeat comes from log growth that the server sees.
-
Rendering Session logs.
- The page reads Claude's
stream-jsonoutput and thecodex exec --jsonoutput that thirdshift keeps, and turns Codex items into the same shape. - Renderers are registered per tool name over a plain fallback. Bash, Edit with a unified diff, Read and Grep come first.
- Consecutive tool calls fold into one group, and thinking is collapsed.
- Unknown event types are shown plainly.
- Only appended lines are rendered; a whole log is never redrawn.
- The Claude types may be based on Vibe Kanban's (Apache-2.0, with attribution) or written from the shapes in real logs. Either way, they're tested against real logs.
- The page reads Claude's
-
What's shown.
- Everything
status,showandlogsshow, including Security sessions and findings, behind the sign-in. - Only the current log layout is read. Session logs in the old
sessions/folder from before Move session logs from .thirdshift/logs/<user>/<repo>/sessions to .thirdshift/logs/<user>/<repo>/commands/<command> #599 aren't linked, though their Commands still show from their Command logs.
- Everything
-
Console accounts live in a file of their own beside the Credentials, readable only by the owner, written by atomic replace. Each account holds:
- an email;
- an argon2id password hash;
- whether the address is verified;
- hashed session tokens with their last use.
-
Sign-in.
- Email and password. The session cookie is HttpOnly and Secure, and lasts 30 days from last use.
- CSRF: every action needs a token.
- Slowdown: after five wrong passwords for an address, sign-in for that address slows to one try a minute.
- Ending sessions: a new password, or
console sign-out, ends that account's sessions. - The allowlist is the switch: taking an address off it ends its sessions at once and blocks it, and the account is kept.
- An empty allowlist:
thirdshift consolerefuses to start, unless--no-sign-inis given. Without sign-in, it serves only to localhost, with no accounts.
-
"Set your password". One flow both creates an account and resets a password:
- You enter an email.
- The page answers the same way whatever the address.
- An allowed address is sent a 6-digit code: valid for 15 minutes, with 5 tries. Codes go out at most once a minute and 10 times a day per address, and are stored only as hashes.
- You enter the code and a new password. That sets the password, marks the address verified, and signs you in.
The email comes from
email.from, using the Resend key from the environment or the Credentials. With no key, the flow is off and the page says how to get a password from the owner. -
The Console log is one file per machine beside the accounts, one line per event, each naming the account. The events are:
- sign-in and failed sign-in;
- code sent;
- password set;
- sign-out;
- an address blocked by the allowlist;
- a Theme or sections change.
Page views are never recorded.
-
Stop from the page.
- The button shows only while the Command is live.
- A confirm step offers Stop or "requeue instead", which is
stop --requeue. - The action calls the same Stop as
thirdshift stop, with "from the Console" and the account's email as who asked. The Activity log reads<subject> stopped from the Console by <email>. - The page then shows "still ending" until the outcome arrives.
-
console installsees which proxy is running and prints one script to run withsudo. The script:- writes a system service that runs
thirdshift consoleas the owner, restarting it if it stops; - writes the proxy's site file for the configured address:
- Caddy: a file in its sites folder that reverse-proxies with flushing turned off;
- Apache: a port-80 virtual host that proxies with
flushpackets=onand gzip off on the stream paths, preserving the host and setting the forwarded protocol;
- for Apache, runs
certbot --apache -d <address>; - enables the service, then restarts the proxy. For Caddy that's a restart, not a reload, which would break HTTP/3 on Android.
With neither proxy, it prints the service and how to reach the Console over an SSH tunnel. The README covers nginx. thirdshift itself never runs as root.
- writes a system service that runs
-
Setup asks whether to run the Console. If yes, it asks for the Console's whole address, showing
thirdshift.example.comas an example with nothing detected, and the allowed emails, defaulting toemail.to. Then it offersconsole install. It also warns about the waiting line and silent time, as above. -
Portability. Nothing outside
console installassumes Linux or systemd, sothirdshift console --no-sign-inruns on a Mac. -
The glossary defines Console, Console account, Console log, Command record, Stop, Inbox, Needs you, Silent, Slow, Usual range and Theme.
Testing Decisions
-
A good test checks what a user or an agent sees:
- the HTML and its server-push events;
statusoutput and its JSON;- responses to sign-in steps;
- labels on the fake GitHub;
- Activity log and Console log lines;
- User config contents;
- emails at the Resend stand-in.
It never checks templates' internals, the accounts file's layout or CSS.
-
The CLI binary, as now, covers:
status's groups and order: Needs you's order, an ended item leaving when a later Command on the same issue starts or afterinbox.days, Silent from fixture logs with old modification times, Slow and its Usual range from fixture Command records, and the escalation words;- CI watch's waiting line, against the fake GitHub with checks that stay running;
- the settings' clamping and warnings;
- the Console subcommands:
console theme,console sections,console theme importandexport, which change the User config and keep its comments;console password,console sign-outandconsole log;
console install, with a fakecaddyorapache2ctlonPATHdeciding which script is printed, and none for the tunnel case;- Setup's new questions, using the Setup tests' harness.
Prior art: the tests Spec: see, follow and Stop a machine's Commands from the CLI #711 added for
statusandstop, and the Setup and User config tests. -
The main seam for the page is
thirdshift consolerunning on a free localhost port, driven over HTTP, with fixture Command records and logs and the existing fakes. It covers:- every page and its HTML:
- the Inbox's groups, matching
status --jsonfor the same fixtures; - Spec run plans, history and idle Passes;
- Lanes, Stages and Waterfall data;
- the Inbox's groups, matching
- the Session log view: folding, the renderer for each tool, the fallback, and appended lines from a byte offset;
- sign-in: the slowdown, session lifetime and sign-out;
- CSRF on every action;
- allowlist removal;
- the "set your password" flow through the Resend stand-in, including the identical answers, code expiry, tries and send limits;
- no Resend key;
- refusing to start with an empty allowlist, and
--no-sign-in; - Stop from the page against a real Command with a waiting fake Harness, checking labels and the Activity log line;
- writing the Theme and sections: the User config changes, nothing outside
[console]can be written, and a Console log line is added; - saving a Theme into the folder;
- serving a user font, while refusing other file types and links out of the folder;
- the parity test over every route.
Prior art: the Run notification tests and the
email-testtests for the Resend stand-in. - every page and its HTML:
-
A contrast test over every built-in Theme checks AA contrast for each text-on-background pair the tokens define.
-
A few browser tests with Playwright, as
site/already runs them, for what HTTP alone can't see:- a live Session log gaining lines without a redraw;
- the Inbox morphing after a push, without moving the selection;
- the keyboard model;
- Stop's confirm step;
- Pause motion;
- switching Themes;
- every page at 390 px with no sideways scroll.
They run in CI beside the site's browser tests. The release build never needs Node.
Out of Scope
- Starting Commands from the page. It's wanted later and will follow the same CLI-first rule.
- Charts of totals over time: Charts on the Console: what to chart over time #813.
- Things the CLI can't do: acknowledging, shelving or steering a Run.
- Reading other machines' Commands, or GitHub for the Inbox, or any HTTP API for agents or other machines. The CLI's
--jsonis the API. - A look per Console account. The look is per machine until a machine has more than one person.
- Uploading files through the page, fonts included.
- Separate issues: launchd on macOS, and Stopping a single Ticket's Run.
- Accounts: recording page views, roles or permissions beyond the allowlist, and passkeys.
- Old data: logs in the flat layout from before ADR 0011, Session logs in the
sessions/folder from before Move session logs from .thirdshift/logs/<user>/<repo>/sessions to .thirdshift/logs/<user>/<repo>/commands/<command> #599, and an SQLite index.
Further Notes
- Spec: see, follow and Stop a machine's Commands from the CLI #711 has shipped, so this Spec's Tickets build on
status,show,logsandstopas they are onmain. The native "blocked by Spec: see, follow and Stop a machine's Commands from the CLI #711" edge is satisfied. - The look (Q27) is the
consolevariant of the prototype, on the throwaway branchprototype/711-console(commit05b1b0a). Runpython3 .scratch/prototypes/711-console/serve.py, then openhttp://localhost:8711/?variant=console&page=inbox. Its data is made up and its sign-in is a stub. It's the reference for layout, wording and Themes, not code to copy: the real page is rendered on the server. - Tickets, each a sub-issue of this Spec, with native blocking edges:
- The CLI first: Waiting lines in CI watch and Self-merge #814 waiting lines, then Silent, Slow and the Usual range in status and show #815 Silent and Slow, then The Inbox's groups in thirdshift status #816 the Inbox in
status. - thirdshift console --no-sign-in serves the Inbox #817, the first Console slice serving the Inbox, waits on The Inbox's groups in thirdshift status #816.
- After thirdshift console --no-sign-in serves the Inbox #817:
- A live Inbox: server push, heartbeat and tab title #818, the live Inbox;
- Session log and Command log views, and the Logs page #819, log views and the Logs page;
- History, Security, the peek and the keyboard model #820, History, Security, the peek and keys;
- Console accounts and sign-in #821, accounts and sign-in;
- The Lanes and Stages pages #826, Lanes and Stages.
- After Console accounts and sign-in #821:
- Set your password by email code #822, the email code;
- Stop from the Console #823, Stop from the Console, which also waits on A live Inbox: server push, heartbeat and tab title #818;
- Built-in Themes, the picker and the page's own look #824, built-in Themes, then Your own Themes and fonts #825, your own Themes;
- thirdshift console install #828,
console install.
- Last:
- The Waterfall page #827, the Waterfall, after Session log and Command log views, and the Logs page #819 and The Lanes and Stages pages #826, since matching tool calls to Command log lines is a parser of its own;
- Setup's Console questions #829, Setup, after Waiting lines in CI watch and Self-merge #814 and thirdshift console install #828.
- The CLI first: Waiting lines in CI watch and Self-merge #814 waiting lines, then Silent, Slow and the Usual range in status and show #815 Silent and Slow, then The Inbox's groups in thirdshift status #816 the Inbox in
- Research:
console-ui-inspiration.mdis the source of parity with the CLI, folded tool calls, and Stop only while live.console-run-monitoring-ui.mdis the source of silence as the stall rule, "usually" ranges, the Stop wording and the stage grid.software-factory-console-trends.mdis the source of the Inbox's grouping and the tab title's count.focus-theme-attention.mdis the source of the Focus Theme.
- ADRs:
- Addresses:
console.thirdshift.app, on this host:*.thirdshift.appalready resolves here.thirdshift.stephens.page:*.stephens.pageresolves there, andconsole.stephens.pageis taken.
Activity
JacobStephens2 commentedon Oct 19, 2026
Q27, the Console's look, is decided. The choice is the
consoleprototype, opening on its Inbox page.prototype/711-console, commit05b1b0a. Runpython3 .scratch/prototypes/711-console/serve.py, then openhttp://localhost:8711/?variant=console&page=inbox. All of its data is made up.web-ui.CONTEXT.mdgains Inbox, Needs you, Silent, Slow, Usual range and Theme. ADR 0018 records one layout with Themes made of tokens.docs/research/console-run-monitoring-ui.md,software-factory-console-trends.mdandfocus-theme-attention.md.This Spec's body will be rewritten to match once the grilling's Round 7 is answered: the settings, what the page may write, user Themes and fonts, and how the Specs are cut. Its Tickets come after that.
Grill #711 Round 7: settings, the page's own look, user Themes