Skip to content

Hub: an Overview of every project, to see at a glance that each is moving - #223

Merged
hhff merged 10 commits into
mainfrom
hub-overview
Nov 14, 2026
Merged

hhff merged 10 commits into
mainfrom
hub-overview

Conversation

@hhff

@hhff hhff commented Nov 14, 2026 •

Copy link
Copy Markdown
Member

The hub gets an Overview tab, so you can see in one look that every project is still moving, even when you owe some of them answers. It sits after the box's Terminal and before the project tabs, and it's what the hub opens on unless the address names a tab (#terminal or a project).

A card per project

  • Status: Building, Moving, Fully blocked, Stalled, Idle, Parked, or Not answering, with an amber Partly blocked beside it when some tickets wait on you. Fully blocked, stalled and not-answering projects sort first.
  • Kit: the release it runs. Amber means it's behind the newest kit on the machine; + means local changes.
  • Inbox: the count. A click opens that project's dashboard.
  • Working: workers mid-ticket.
  • Last moved: when the fleet last wrote anything.
  • Progress: tickets merged out of tickets planned, as a bar, with the tickets waiting on you in amber.
  • Needs attention:
    • the box alert (a supervisor at a usage limit, say)
    • fully blocked (all N open tickets wait on your answers) or partly blocked (N of M do, the rest can move)
    • open work with nothing moving for 2 hours
    • workers silent for over an hour
    • open production incidents
    • a drain in progress
  • Activity: 12 weeks of the fleet's daily log activity, laid out like GitHub's contribution graph.

The headline reads "Everything is moving", "N fully blocked on you" or "N need a look", followed by how many projects are moving, how many are partly blocked, and how many answers you owe.

How the state is worked out (helloPulse, from the log in one pass):

  • Parked: every goal is held.
  • Building: a worker is mid-ticket and the fleet wrote something in the last hour.
  • Fully blocked: every open ticket waits on you. A ticket waits on you when it is named in the --blocks of an open question with no default, or depends on one that is, however far down.
  • Partly blocked (shown beside the state): some open tickets wait on you and the rest can move.
  • Stalled: there's open work and the fleet hasn't written anything for 2 hours.
  • Idle: no open work.
  • Moving: anything else, such as the supervisor planning, reviewing or merging.

Plumbing

  • The pulse rides in each project's hello, which the dashboard already serves to the hub. The hub and the cloud gateway pass it through unchanged, so there's no new endpoint and nothing new to authorize.
  • Everything renders as text through DOM nodes, the same as the existing tabs.
  • A dashboard on an older kit sends no pulse. Its card shows what it does send and says to update.

Checked

  • Rendered with five sample projects, one per state, in light, dark and on an iPhone 14, with no page errors.
  • New test/hub-overview.test.mjs walks a project from idle to moving, then fully blocked (including a dependent ticket), then partly blocked, then building while still partly blocked.
  • Dashboard, accounts and gateway tests fail only where main does here (tmux, restart, thread and preview tests). The Shift-and-point Chrome test is flaky on main as well.

https://claude.ai/code/session_01T5z3hdiDu7TwHcW5jpP6do


Generated by Claude Code

…ving

A tab after the box's Terminal and before the projects, and the hub's landing page when the
address names nothing else. A card per project:

- whether it is building, moving, waiting on the owner, stalled, idle, parked or not answering;
  what needs a look sorts first
- its kit release (amber when behind the newest kit on the machine), its Inbox (a click opens
  it), workers mid-ticket, and when the fleet last wrote anything
- tickets merged of planned, as a bar
- what needs attention: the box alert, questions with no default holding up work, open work
  with nothing moving for 2 h, workers silent over an hour, open incidents, a drain
- twelve weeks of the fleet's daily activity, GitHub-style

The data is a pulse in each project's hello, computed from the log in one pass; the hub and the
gateway pass it through as they did the rest. A dashboard on an older kit has no pulse, and its
card says to update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5z3hdiDu7TwHcW5jpP6do
A project is fully blocked when every open ticket waits on the owner: named in the --blocks of an
open question with no default, or depending on one that is. Its card says "Fully blocked" in red
and it sorts first, whatever else is running. Partly blocked is some of them: the card keeps what
the project is doing (Building, Moving) and adds an amber "Partly blocked" beside it. Either way
the progress bar shows the held tickets in amber after the merged ones, and the attention line
says how many of how many wait on how many answers. The headline leads with fully blocked
projects and counts partly blocked ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5z3hdiDu7TwHcW5jpP6do
…every project

- Each card names the approved goal first in priority, with its tickets merged of planned and how
  many other goals are open. A project with no approved goal says so.
- Each card has Run, Drain and Park (Park asks to confirm), and the page has Park all (confirmed)
  and Resume all while any project is held.
- The hub still posts no owner action. It asks the project's own dashboard, in its frame, by
  message; the dashboard posts the fleet action to its own origin, as a click there would. The
  dashboard takes the message only from its parent, and only when the parent is another origin,
  which frame-ancestors limits to the hub. Three ops, each once per nonce, and the answer goes back
  to that parent alone. The log's reason says it came from the hub's Overview.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5z3hdiDu7TwHcW5jpP6do
The pulse asks whether each ticket has merged. Without the read cache that was two
rev-parse spawns per unmerged ticket: 2.2 s for 200 open tickets, every 10 s, per
project. With it, 0.18 s (0.20 s at 1000).

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5z3hdiDu7TwHcW5jpP6do
…; a parked goal's tickets are not stalled

Two wrong reads, both reproduced:
- an approved goal with no tickets yet read "idle" (the card said "Waiting for a new
  goal"), however long nobody had planned it. It is open work: moving, then stalled.
- one goal parked by the owner (Upkeep still running, so the fleet is not parked) read
  "moving", then a red "stalled" after 2 h. Its tickets wait by choice: they no longer
  count as work that could move, nor as what a question blocks; the card says they are
  on hold.
Also: the goal named is a running one before a parked one; a question that holds no
ticket (an incident, unplanned work) is counted apart from the ones that do; the hub's
"Waiting on you" state, which nothing sent, is now sent for an unplanned goal held by
a question with no default.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5z3hdiDu7TwHcW5jpP6do
… under anything but the top page

index.html is also the page at /view/. On a box the gateway lets the owner's wrapper on
the public name frame a granted viewer, so there "a parent on another origin" is not
only the hub. The wrapper runs no script today; the bridge no longer rests on that.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5z3hdiDu7TwHcW5jpP6do
…ashboards left running unseen

- The page is rebuilt on every press and every 10 s, and focus went with it: Park then
  Confirm park could not be done from the keyboard. Whatever had focus keeps it.
- After a park that worked the card went on showing Run for up to 10 s (a hello's age),
  with Park enabled again. The card shows what its dashboard said it set until the
  hello agrees; the dashboard reads its hello again after every owner action.
- Park all loaded every project's dashboard in a hidden frame and left it there, each
  reading /api/data every 10 s. A frame made only to ask is dropped when it answers.
- A hello that isn't shaped like one (attention not a list) threw in the middle of the
  paint and took the whole Overview with it: now its own card says so.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5z3hdiDu7TwHcW5jpP6do
…an't

Park by keyboard, Park all, Resume all; then a dashboard's own page, the viewer under
the hub, another local page and a dashboard in its own tab each ask and are not heard.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5z3hdiDu7TwHcW5jpP6do
… fleet

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5z3hdiDu7TwHcW5jpP6do
…lo sends

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5z3hdiDu7TwHcW5jpP6do

@hhff hhff left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial review of the Overview. 7 commits pushed on top (9af633a..e23a928), no force-push.

Confirmed and fixed

sev where what fix
high devteam.mjs cmdHello / helloPulse two git rev-parse spawns per unmerged ticket on every hello. 200 open tickets: 2.2 s vs 0.14 s on main, every 10 s per project. the comment said "no git" 9af633a turns on the read cache in cmdHello: 0.18 s at 200, 0.20 s at 1000
high helloPulse state an approved goal with no tickets read idle ("Waiting for a new goal") forever, even with nobody planning it 0b99745: counts as open work, so moving then stalled
high helloPulse state one goal parked by the owner (Upkeep still running, so fleet mode is run) read moving, then red stalled after 2 h 0b99745: tickets under a parked or let-go goal are not work that can move or be blocked; card says they are on hold
med index.html bridge listener also installed at /view/. on a box the gateway adds the public-name wrapper as a frame ancestor of a granted viewer, so "a cross-origin parent is the hub" was false there. not exploitable today only because the wrapper has no script df58acc: not in the viewer, and only directly under the top page
med hub.html paintOverview every press and every 10 s rebuilt the page and dropped focus. Park then Confirm park was impossible by keyboard 53099b1: focus kept across repaints
med hub.html setFleet after a park that worked, the card showed Run (Park enabled again) for up to 10 s 53099b1: card shows what the dashboard said it set until hello agrees (30 s cap); dashboard re-reads hello after any owner action
med hub.html frameFor Park all loaded every dashboard in a hidden frame and left it polling /api/data every 10 s 53099b1: a frame made only to ask is dropped once it answers
low hub.html overviewCard a hello with attention not a list threw mid-paint and blanked the whole Overview 53099b1 per-card catch; e23a928 caps the mosaic
low hub.html STATES.waiting state the kit never sent 0b99745: sent for an unplanned goal held by a no-default question
low README still said the hub "takes no action" 7cffe97

Also in 0b99745: goal pick prefers a running goal over a parked one; questions that hold no ticket (incident, unplanned work) counted apart.

Bridge checks that held: frame-ancestors as served ('self' + hub; gateway maps the hub port to the box name); a dashboard cannot frame another dashboard; same-origin parent, null origin, opener and top-level self-messages all refused; nonce replay only reachable by the parent; reply targetOrigin is the sender's origin; hub checks source window and origin of the reply. If earlier script throws the listener is never installed, which fails closed (hub says no answer after 20 s). Review pages and artifact frames are not index.html.

Left for the owner

  • On a laptop, whatever serves 127.0.0.1:4320 can now run, drain or park every fleet with no click, if the owner opens it. Before this PR it could only frame dashboards. An agent that takes the port while the hub's dashboard restarts would qualify. Documented in the README; a real fix needs the dashboard to authenticate the hub.
  • Default landing on a box is now Overview, not Terminal.
  • Hub's 20 s timeout message blames an older kit even when the dashboard is just slow.
  • Blocking follows depends_on only, not contract providers or superseded deps.
  • A project named overview or terminal collides with those hashes.
  • "Working" counts tickets with a live phase, not tmux sessions.

Tests

  • test/hub-overview.test.mjs: 3 pass. New pulse test for both state bugs. New Chrome test: Park by keyboard, Park all, Resume all, then four askers that must not be heard (dashboard to itself, viewer under the hub, another local page, dashboard in its own tab) and the hub that must. Fails against the pre-fix index.html and hub.html.
  • dashboard the hub|pages parse: 2 pass. bounds 3 pass. speed 8 pass.
  • gateway: 66 pass, 1 fail ("a preview that is down, ambiguous or unknown..."), same on main.
  • fleet-park: 2 fail, same two on main (tmux in this sandbox).

Note: commits carry the session's own co-author trailer, not the one named in the task.


Generated by Claude Code

@hhff

hhff commented Nov 14, 2026

Copy link
Copy Markdown
Member Author

Re-reviewed at e23a928, which adds the Overview's Run, Drain and Park bridge:

  • The dashboard takes the message only from window.parent when that is the top page and on another origin, and never in the viewer. Opaque (null) origins are refused, and each nonce runs once.
  • frame-ancestors on the dashboard page is 'self' plus the hub, and the gateway adds the public name only on viewer routes (publicFrame), where the bridge is off. So only the hub can drive it. Artifact pages are sandboxed with frame-src 'none'.
  • The ops are fleet park, drain and resume, which the supervisor can do itself, posted to /api/fleet as a click on the dashboard would be.
  • Hello now uses one git call for all refs, not two per ticket.

On Node 24, merged with main, hub-overview and kit pass locally. CI passes 9 of 9. Merging.

@hhff
hhff merged commit 5972339 into main Nov 14, 2026
15 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants