What's actually in the UI, where to find it, and what shipped recently. This
is a map of the product, not the code — see docs/beads-coverage.md and
docs/molecules-design.md for the design rationale behind the relationship
model and molecules, and the main README.md for install/CLI/config.
Every claim below was checked against the source (file:function references
given) or the live server (curl against a running hub). If something isn't
verifiable in the code, it isn't in this document.
One hub server per machine. You run bd-console start once per box, then
bd-console add <path> for every repo you want on the dashboard — each repo
needs its own .beads/ (a bd init'd beads database). The hub (#/) is a
landing page listing every registered project as a card; clicking a card
opens that project.
Console 2.0 is the only project view. Hub cards route straight to it
(#/p2/<id>) — a full-viewport "mission control" screen (omnibar, pulse
stats, a three-mode canvas, a relationship-rich Detail slide-over). The older
classic view (#/p/<id>, a three-pane issues layout plus a read-only docs
tab) was retired in bd-console-0nd: its components are deleted, and the two
hashes it owned (#/p/<id> and #/p/<id>/docs) now redirect to #/p2/<id>
so old bookmarks still land on the project.
Routing is hash-based (public/ui/routing.js's parseRoute(), wrapped by
public/ui/store.js's parseHash()), so every one of these is a
shareable/bookmarkable URL as well as a click path. Every route except
#/p2/<id> renders under the hub-level top bar
(public/ui/components/TopBar.js); Console 2.0 is full-viewport with its own
header.
| Route | How you get there | What it's for |
|---|---|---|
#/ |
Default landing page; the ⌂ icon in Console 2.0's header; the top bar's brand link | Global Hub: project cards, live tmux/scheduler summary, bd-version status, usage gauges |
#/p2/<id> |
Click a project card on the hub (the only click-through the cards offer) | Console 2.0 — the per-project view (§4) |
#/p/<id>, #/p/<id>/docs |
An old bookmark or link only — nothing in the UI emits these any more | Retired (bd-console-0nd). Redirected in place to #/p2/<id> via history.replaceState, so no history entry is left behind and Back doesn't bounce off it (legacyProjectHash() in public/ui/routing.js) |
#/tmux |
Hub's "Overview" ops-strip chip; hub's "Terminal sessions" section "View all →"; the top bar's Terminal nav icon; a tmux session card's own click | Grid of every tmux session/pane on the hub's host, with a live scrollback preview drawer |
#/schedule |
Hub's "Overview" ops-strip chip; the top bar's Schedule nav icon; a tmux session card's "Schedule a prompt here" button (preloads that session) | Create/list/cancel scheduled prompts (queued keystrokes into a named tmux session at a future time) |
#/settings |
The top bar's gear icon; Console 2.0's own ⚙ header link; an automatic redirect whenever a write is rejected for a missing/wrong token (requireToken() in store.js) |
Server settings (read-only), Appearance, Beads CLI version/update, browser + server write tokens, Default epics |
#/learn |
Console 2.0's "? Guide" header link; "Read more" in any concept tip | The concepts reference — every bead/link-type concept the UI links to |
| anything unrecognized | — | Falls back to the hub (#/) |
Top to bottom, public/ui/components/HubView.js:
- Header — "Global Hub" + the Overview strip: two chips ("N tmux
sessions" / "unavailable", "N scheduled prompts"), each a shortcut to
#/tmux/#/schedule. Collapsible on mobile only. - bd version row — a compact chip showing the installed
bd(beads CLI) version; if a newer release exists, an "update available" chip that copies the update command to your clipboard (or, if the command can't be determined, sends you to Settings); a warning chip if more than onebdbinary is onPATH(the exact trap where an update can silently target the wrong binary). Fetched once per hub visit (GET /api/bd-version, server-cached for hours) — Settings has the fuller version card with a manual recheck. - Usage section — two bands:
- Live quota: Claude Code and Codex quota gauges (session/weekly
windows, percent + "resets in Xh Ym", color-coded ok/warn/crit), plus
per-model "scoped limits" with a loud "⛔ throttled" badge when a model is
actively rate-limited. Polls every 5 minutes (paused while the tab is
hidden, caught up on return) and the server caches the Claude call for
the same 5 minutes, so an open hub can't sustain the provider's rate
limit; the ↻ button asks for a genuinely fresh read
(
GET /api/usage?fresh=1), which the server still refuses during a 429 backoff (15 min) or within ~20s of the previous manual refresh — those answers come back marked as cached and say so in the UI. Degrades to "not detected" per provider rather than erroring the whole hub. Kimi Code joins this band as a third row when~/.kimi-codeexists (and renders nothing at all when it doesn't): stack info, not quota — server state from thekimi webheartbeat (running/stale/stopped, stale after 90s of silence), host version, host:port, session/workspace counts, and the newest session's model + token total. Kimi publishes no rate-limit data anywhere, so it deliberately gets no gauge. Gemini (Google's Antigravity CLI — the binary isagy, notgemini) joins as a fourth row on the same terms when~/.gemini/antigravity-cliexists: language-server state and version read from the CLI's own log header, sign-in state, conversation and workspace counts, and the newest conversation. Antigravity publishes no limit, utilization or reset instant anywhere either — its internal quota manager logs only control flow — so it gets no gauge; the one real quota fact is whether the API answered429 RESOURCE_EXHAUSTED, which surfaces as a dated "⚠️ quota hit" chip and only when it actually happened. The adapter never opensantigravity-oauth-tokenand never surfaces the account email the CLI writes into its own log. - Usage attribution: a separate, explicitly-labeled "estimated from local session logs, not quota" band — a 7/30/90-day range picker and a manual refresh. This is parsed from local session logs on the hub host, not the providers' own usage APIs, and is presented that way in the UI so it's never confused with the authoritative gauges above it.
- Live quota: Claude Code and Codex quota gauges (session/weekly
windows, percent + "resets in Xh Ym", color-coded ok/warn/crit), plus
per-model "scoped limits" with a loud "⛔ throttled" badge when a model is
actively rate-limited. Polls every 5 minutes (paused while the tab is
hidden, caught up on return) and the server caches the Claude call for
the same 5 minutes, so an open hub can't sustain the provider's rate
limit; the ↻ button asks for a genuinely fresh read
(
- Terminal sessions — a compact table (session name/attached badge, repo
chip if a pane's cwd matches a registered project, command, age, last
activity, copy-attach button) for up to 6 sessions, "View all →" to
#/tmux. Hidden entirely if no projects are registered yet. - Project grid — one card per registered project: issue count, git
insights if available (branch, ahead/behind, dirty-file count, commits/week,
a labeled repo chip linking to GitHub/GitLab/etc.), status stat pills
(Ready/Active/Blocked), a velocity pill (closed in the last 7 days) and an
open-bugs pill when non-zero. The entire card is one click-through to
Console 2.0 (
#/p2/<id>). - Empty state: "No projects registered. Run
bd-console addinside a project to register it."
public/ui/console2/Console2.js. A full-viewport view with no hub-level top
bar — its own header, a persistent Pulse stats bar, a segmented Canvas
(Flow / Map / Docs), and a right-hand Detail slide-over for the selected
issue.
- ⌂ — back to the hub.
- Brand — project name/id, "CONSOLE 2.0 · MISSION CONTROL".
- Omnibar — the centerpiece command input (§5).
- + New — opens the full-featured
CreateIssueDialog(type/priority/labels/description/acceptance/epic/assignee) — the omnibar's own plain-text mode only does quick triage capture, not this. - ⚗ Templates — the molecule/formula pour dialog.
- ? Guide —
#/learn, the concepts reference. - Theme switch — preset + light/dark/auto popover.
- ⚙ —
#/settings. - Sync chip —
synced/stale/error/unknown, reflecting the freshness of the.beads/issues.jsonlexport this view is reading from. - CLI-flash strip (directly under the header) — every write you make
through Console 2.0 echoes the literal
bd/tmuxcommand it just ran ("✓ ran$ bd update … --claim"), with copy/dismiss — this is Console 2.0's explicit "teach you the CLI" mechanism, not a suggestion to run something yourself.
A row of clickable stat tiles under the header, always visible (collapsible on mobile only — desktop always shows it):
- Ready / Active / Blocked / Triage / Stale tiles — each click focuses the matching Flow lane (or highlights stale issues). Active shows an "N aging" sub-label once an in-progress issue passes 24h.
- A velocity sparkline — issues closed per week, 8-week trailing window.
- A "details ▾" expand revealing:
- Active · ages — every in-progress issue with an age chip (amber past 24h, red past 72h), click to open its Detail.
- Unblock hint — "Closing X frees N issues" for the single open issue that, if closed, would unblock the most currently-blocked work. A thing you'd likely never think to compute yourself.
- Priority mix — a P0–P4 bar chart of open issues.
- Sessions · this repo — tmux sessions whose active pane's cwd is inside this project, each with a "delegate here" button (only enabled once an issue is open in Detail — see §6.5).
- A duplicate theme switch (for narrow layouts where the header's isn't reachable).
- Flow (default) — five swim lanes: Triage · Ready · In progress · Blocked · Done·7d. A "Group by epic" toggle (persisted per project) replaces the lanes with one row per container (epics and poured molecule roots — both group identically) showing a closed/total progress track. Cards carry inline quick actions (claim/start/defer/close/open).
- Map — a client-drawn SVG dependency graph of open issues: blocking
edges lay out the DAG (roots left, dependents right), the longest blocking
chain is always highlighted as the "critical chain," and hovering a node
lights its full up/downstream blocking chain. Non-blocking overlay
edges (related / tracks / discovered-from / caused-by / validates /
supersedes / duplicates) draw as bowed curves on top, each independently
toggleable via checkboxes in a toolbar (only types actually present in the
current graph get a checkbox), default related only — all ~7 at once
is unreadable, per
docs/beads-coverage.md's own rationale. Pan by drag, zoom by scroll/pinch/±buttons, click a node to open its Detail. A thing you would never discover on your own: overlay-edge toggles are per-project and persist inlocalStorage, so a choice you made last week is still applied. - Docs — file tree (filterable), a markdown reader with select-text-
to-promote (select any text in the rendered doc → a floating "✦ Promote
to issue" button appears, prefilling a new
task/triageissue with the quoted excerpt and adoc:<path>provenance label — §6.9), and an inline editor (Ctrl/Cmd+S to save, a dirty dot, a live preview toggle). On mobile, where text-selection handles are unreliable, a manual "promote…" form lets you paste/type the excerpt instead of selecting it.
Opens when any issue is selected (selectIssue()), in this order — every
section only renders if it has content, so a plain task shows far less than
a molecule root does:
- Badges (type/priority/status/id) + close button.
- Title, labels.
- Molecule badge — "⚗ molecule · N steps" if this issue IS a poured molecule's root, or "⚗ step of " (click to jump to the root) if it's one of that molecule's steps.
- Retired-state banner — "Superseded by / Duplicate of
<other>" — this outranks the blocked/ready banner below it (an issue bd already closed as a duplicate has no meaningful "ready to work" story). - Blocked banner ("⛔ Blocked by N open issues") or ready banner ("✓ Ready — no open blockers") — whichever applies, never both.
- Description / Close reason / Design / Notes / Acceptance (all rendered as markdown).
- Steps (molecule roots only) — a progress bar, then a parallel-group-
aware step list: each step is a relationship chip plus (when the live
bd ready --molcall succeeds) its parallel-group label and a "ready" pip. Falls back to a locally-derived (non-parallel-aware) step list if the live call fails — it never blocks rendering on that call. - Parent · Blocked by · Blocks · Children (Children is suppressed for molecule roots — already covered by Steps above).
- Related (bidirectional — renders once regardless of which issue created the edge) and one section per other link type actually present on this issue, both directions (tracks/discovered-from/caused-by/validates/ supersedes/duplicates) — only non-empty sections show, so the panel never grows ten empty headings.
- Undo (molecule roots only) — "Burn this molecule…": a dry-run
preview (verbatim
bd mol burn --dry-runtext) then a confirm button labeled with the real bead count. Caveat, stated plainly in the UI itself: burn deletes everything parented under the root, including beads you added by hand after pouring — not just what the pour created — and it silently drops (does not archive) dependency links from issues outside the molecule. This is a real, git-synced deletion, not a soft-delete. - Edit — claim/start/close/reopen; priority P0–P4; labels
add/remove; parent set/clear; blocked-by add/remove; a generic Link
row (pick any of the 10
bd dep add --typevalues + an issue id — see §5's link-type table); a Retire row — Supersede (closes this issue, needs a replacement id) and Duplicate (closes this issue, needs a canonical id), explicitly labeled as state transitions, not links; Defer set/clear. - Delegate — compose a prefilled tmux prompt ("Work on
<id>:<title>… Runbd show <id>for full context."), pick a live tmux session (no default preselected — you must choose, a deliberate anti-footgun after an earlier stray-prompt incident), then "Send now" or pick a datetime and "Schedule…". - Comments — live via
bd comments --json(never stale), ⌘/Ctrl+Enter to submit. - Meta — assignee, created/updated/closed timestamps.
public/ui/console2/Omnibar.js — the single input in Console 2.0's header.
Focus it with / (when not already typing) or Ctrl/Cmd+K from anywhere
in Console 2.0; Escape or a click outside closes it.
Three auto-detected modes, based on what you type:
- Capture (plain text, no prefix) — Enter creates a triage bead:
task, priority 3, labeltriage, title = what you typed (bd create --type task -p 3 --labels triage --title "…"). While typing, up to 6 fuzzy-matched existing issues (by id or title) also appear below the capture row — clicking one jumps to its Detail instead of capturing a new issue. - Jump — a fuzzy match selected from the capture-mode dropdown; opens that issue's Detail directly.
- Command (
>or/prefix, or focusing the omnibar empty) — the full command palette:
| Command | Arguments | What it does |
|---|---|---|
ready |
— | Switch to Flow, focus the Ready lane |
blocked |
— | Switch to Flow, focus the Blocked lane |
triage |
— | Switch to Flow, focus the Triage lane |
stale |
— | Switch to Flow, highlight stale issues (21d+ no update) |
progress |
— | Switch to Flow, focus the In-progress lane |
flow |
— | Switch to Flow, clear any focus |
map |
— | Switch to Map |
docs |
— | Switch to Docs |
stats |
— | Scroll to / focus the Pulse rail |
claim |
<id> |
Claim an issue |
start |
<id> |
Mark in progress |
close |
<id> [reason] |
Close an issue |
defer |
<id> <when> |
Defer until (e.g. +2d, a date) |
prio |
<id> <0-4> |
Set priority |
open |
<id> |
Open an issue's Detail |
mol |
[formula] |
Pour a molecule — see below |
mol is the one exception to "commands act immediately." Every other
action command runs its write on Enter. mol instead opens the Molecule
dialog (browse → fill variables → preview → confirm → pour) — pouring
creates many beads at once, so it always routes through a dry-run preview
first rather than firing on the spot. An optional argument (> mol mol-feature) preselects that formula in the browser.
Link types, for reference (the 10 values bd dep add --type accepts,
used by Detail's Link row and by everything above that reads/creates
relationships — public/ui/relationships.js):
| Type | Meaning (outbound / inbound) |
|---|---|
blocks |
Blocked by / Blocks — the only type that affects readiness and the Map's layout |
parent-child |
Parent / Children — epic/molecule containment |
related / relates-to |
Related / Related — a bidirectional "see also," rendered once regardless of which side created it |
tracks |
Tracks / Tracked by — convoy tracking |
discovered-from |
Discovered from / Discovered — provenance ("this idea came from investigating that") |
until |
Blocked until / Gates |
caused-by |
Caused by / Caused |
validates |
Validates / Validated by |
supersedes |
Superseded by / Supersedes — a state transition (auto-closes the subject), not a plain link |
duplicates (from bd duplicate --of) behaves the same way as supersedes
but isn't a dep add --type value — it only appears because bd duplicate
wrote it.
1. Capture a thought.
Console 2.0: type into the omnibar, Enter. Anywhere else (or via the i
shortcut): the full "+ New issue" dialog, intent chip "Idea / triage".
2. File a full issue with an epic.
Press i (or click "+ New" in Console 2.0) →
CreateIssueDialog: pick an intent chip (Log a bug / New feature / Task /
Idea·triage / Epic / Chore — each maps to a bd create type plus default
labels), enter a title (the only required field), optionally add a
description, priority, labels, acceptance criteria, epic parent, assignee.
The epic picker has an inline "+ new epic" so you never have to leave the
dialog to create one first. If the project's Settings → Default epics has a
mapping for the chosen intent, the epic field preselects automatically until
you touch it by hand.
3. Link two issues. Open either issue's Detail → Edit → Link: pick a type from the table in §5, enter the other issue's id, click "link". Any non-blocking/non-parent link you created can be removed from the same row.
4. See what's blocking what. Detail's "Blocked by" / "Blocks" chip rows and the blocked/ready banner on any single issue; Console 2.0's Map mode for the whole project at once (critical-chain highlight, hover a node to light its blocking chain).
5. Delegate an issue to a tmux session. Console 2.0 only: open the issue's Detail → Delegate → the compose box is prefilled, pick a live session from the dropdown (no default), "Send now" — or set a datetime and "Schedule…" instead (equivalent to workflow 6 below, scoped to this issue). Also reachable from the Pulse rail's Sessions block ("delegate here") once an issue is already open in Detail.
6. Schedule a prompt for later.
#/schedule → fill in the prompt text, pick a target session (a live
combobox over currently-running tmux sessions, or type a name), pick a run
time (manual datetime, or presets: +1h…+5h, next 2am/4am, and — when usage
data is available — "next Claude reset" / "next Codex reset"). Saved
prompts (optional feature, degrades to hidden on an older server) let you
name and reuse a prompt. A job whose target session isn't running when it
fires ends up failed, not silently dropped.
7. Pour a molecule from a formula.
Console 2.0 omnibar: > mol (optionally > mol <formula-name>) → pick a
formula from the browse list → fill in its variables (a live preview of the
resolved step titles updates as you type) → "Preview spawn →" runs the real
bd mol pour --dry-run and shows its output verbatim (see caveat below) →
"Pour N issues" performs the write and jumps straight to the new molecule's
Detail. Formulas themselves have no authoring UI — they're hand-written
.formula.json files under a project's .beads/formulas/; the dialog only
browses and pours what's already there.
8. Edit a doc and promote a selection to an issue.
Console 2.0 → Docs mode → open a file → either click "edit" (inline
textarea, ⌘/Ctrl+S to save, "preview" toggles a live rendered pane) or, in
read mode, select any text in the rendered markdown to get a floating
"✦ Promote to issue" button — click it to create a task/triage issue
whose description quotes the selection and carries a doc:<path> label back
to the source. On mobile, use the "promote…" button to paste/type the
excerpt instead of selecting it.
9. Check your usage/limits. Hub → Usage section: the "Live quota" band is the authoritative Claude Code/Codex gauges (percent used, reset countdown, per-model throttled badges). The "Usage attribution" band below it is a separate, explicitly- labeled estimate derived from parsing local session logs — useful for "how much of my usage went to which project/model," not for "am I about to hit a limit" (that's the Live quota band's job).
10. Update beads (the bd CLI).
Hub's bd-version row, or Settings → "Beads CLI" card for the fuller view:
shows installed vs. latest (checked against GitHub, cached for hours — "↻
Recheck" forces a fresh check), a copyable update command when the install
method is known, or all three candidate commands (Homebrew/install-
script/npm) when it isn't so you can pick the one that matches how bd got
onto this machine. Warns if more than one bd binary shadows another on
PATH — the scenario that motivated this feature in the first place.
Global (ignored while typing in a field — public/app.js's onKeyDown).
j/k (list cursor) and c (focus the comment box) were retired with the
classic view in bd-console-0nd: both targeted DOM only its list/detail panes
rendered.
| Shortcut | Action | Where |
|---|---|---|
i |
Open the full "New issue" dialog | Console 2.0 |
/ |
Focus the search field | Anywhere; the omnibar carries the .issue-search class this targets, so in Console 2.0 this and the omnibar's own / handler both hit it |
Esc |
Close the open dialog | Anywhere |
Console 2.0's omnibar (public/ui/console2/Omnibar.js), independent of the
above:
| Shortcut | Action |
|---|---|
/ (not already typing) or Ctrl/Cmd+K |
Focus the omnibar from anywhere |
↑ / ↓ |
Move through suggestions/commands |
Enter |
Run the selected (or first) item |
Esc |
Close the omnibar dropdown |
Elsewhere:
| Shortcut | Action | Where |
|---|---|---|
⌘/Ctrl+Enter |
Submit a comment | Comment composer, both views |
⌘/Ctrl+S |
Save the current doc | Console 2.0's Docs editor |
bd-console's own command line (serve.mjs) — see README.md for full
detail on each:
bd-console run the hub in the foreground
bd-console start (re)start as a supervised background process
bd-console stop stop it, however it's currently supervised
bd-console status pid + port-reachability + supervision report
bd-console add <path> register a project with the hub
bd-console remove <id> unregister a project
bd-console list list registered projects
bd-console settings view/edit host, port, token, persist
bd-console update [--dry-run] self-upgrade in place
For anyone who last looked at bd-console before its relationship model, graph, and molecule support existed:
- Link types — all 10 of
bd'sdep add --typevalues are now creatable, visible, and removable (Detail's Link row), not justblocksandparent-child. Supersede/Duplicate are wired as dedicated state transitions (they close the source issue), not generic links. - Graph-links in Map — Console 2.0's Map mode now overlays non-blocking
link types (related/tracks/discovered-from/caused-by/validates/
supersedes/duplicates) on top of the blocking DAG, with per-type toggles
and a legend; default is
relatedonly. - Molecules v1 — pour a molecule from a formula via the omnibar's
> molcommand (browse → variables with live preview → dry-run confirm → pour), a molecule-aware Detail (progress + parallel-group-aware steps), epic-style grouping in Flow, and a "Burn this molecule" undo path.bd mol bond,bd mol distill, wisps, andbd mol squashare deliberately not built — see the caveats below. - Usage analytics — the hub's Usage section: live Claude Code/Codex quota gauges plus a separate "estimated, not quota" attribution band.
- bd version surfacing — the hub's compact bd-version row and Settings' fuller Beads CLI card (installed/latest/update command/multiple-binaries warning).
- Default epics — Settings lets you map each create-dialog intent to a default epic per project, plus a one-click "create the 5 standard epics" action.
- Global Hub + Console 2.0 architecture — the umbrella change behind all of the above: one hub server registering many projects, with Console 2.0 as the per-project view.
- Classic view retired (bd-console-0nd) —
ProjectView/IssueList/IssueDetail/FiltersPane/DocsVieware deleted;#/p/<id>and#/p/<id>/docsredirect to#/p2/<id>. Every feature had been shipping twice; there is now one place for each.
- Molecule dry-run previews are opaque text, not structured data.
bd mol pour/distill/burn --dry-runsilently ignores--jsoneven when it's passed — bd-console renders the raw stdout verbatim rather than pretending it's parseable. (Confirmed indocs/molecules-design.md§1/§4.) - Burning a molecule deletes more than the pour created — everything parented under the root, including beads added by hand afterward — and it is a real deletion (syncs to remotes), not an archive.
- Wisps,
bd mol bond,bd mol distill, andbd mol squashare out of scope, not just unbuilt. Wisps are excluded frombd exportentirely, so they're invisible to bd-console's normal read path without a second, always-live polling path that doesn't exist yet;bond's formula-name resolution was found to be unreliable on the installed CLI version during design research. Seedocs/molecules-design.md§2/§8 for the full reasoning. - Formula authoring has no UI anywhere — formulas are files on disk; the Molecule dialog only ever reads them.