A Python + FastAPI + HTMX dashboard for bd (beads) workspaces.
cd into any directory containing .beads/, run bdboard, and a browser tab
opens with a live view of swim lanes, activity, and bead detail with every
field bd exposes.
Who it's for: developers using bd who want an at-a-glance, always-fresh
web view of a workspace's beads without leaving their terminal-driven workflow.
| Tool | Why | Notes |
|---|---|---|
bd (beads) |
Required at runtime — bdboard shells out to the bd binary for every bead read (see src/bdboard/bd.py). Without it on PATH, the board comes up but every lane/detail/memory/history view fails. |
install from upstream; confirm with bd --version |
| Python ≥ 3.11 | runtime | pyproject.toml requires-python |
| uv | venv + installer | uv --version |
Node + npx |
only for make duplication (jscpd) |
bundled toolchains skip this |
lychee |
only for make links |
brew install lychee |
The full setup, in order. A fresh clone has no bead database, so
bd bootstrap comes first — skip it and bdboard renders an empty board
(see Getting the bead history).
git clone https://github.com/weegens-aaron/bdboard.git
cd bdboard
bd bootstrap --yes # hydrate the bead DB (one-time; fresh clones ship none)
make install # uv venv + editable install (resolves against public PyPI)
source .venv/bin/activate # activate the venv (run once per shell session)
make test # optional: confirm the suite is green
bdboard # binds 127.0.0.1:7332 and opens a browserThe sections below break these steps out individually.
cd bdboard
make install # uv venv + editable install (resolves against public PyPI)
source .venv/bin/activate # activate the venv (run once per shell session)Prefer the raw commands? They're equivalent:
cd bdboard
uv venv
uv pip install -e .
source .venv/bin/activateBehind a private package mirror?
make installreads two optional environment variables and passes them through touv— nothing is hard-coded, so the public default stays clean:export PY_INDEX_URL=https://mirror.example.com/simple export PY_TRUSTED_HOST=mirror.example.com # only if the mirror is self-signed make install
From within a bd workspace (any directory containing .beads/):
bdboard # binds 127.0.0.1:7332 and opens a browserOr use make run from the bdboard repo (runs against this repo's own
.beads/ workspace):
cd bdboard
make run # equivalent to .venv/bin/bdboardbdboard only works inside a bd workspace. From a non-
.beads/directory it reports "workspace not ready" —cdinto one or pass--dir /path/to/workspace.
Fresh clone? Hydrate first. A freshly-cloned bdboard workspace ships no bead database (
.beads/embeddeddolt/is gitignored), sobdboardstarts but renders an empty board until you runbd bootstrap --yesonce. See Getting the bead history (fresh clone) for the one-time hydration step.
make test # runs the full pytest suite against this repo's own .beads workspaceThe mechanical, deterministic code-health checks run on every pull request and
on pushes to main/working via .github/workflows/code-health.yml and gate
merges. Run the exact same set
locally with:
make code-health # lint + format-check + dead-code + tests + duplication + audit
make outdated # advisory only (dependency staleness)These checks live in CI rather than a bd formula by design — see
notes/decisions/0002-dashboard-architecture.md.
| Gate | Tool | Make target | Config |
|---|---|---|---|
| Lint + unused-import/dead-code (F401, F811, F841) | ruff | make lint |
[tool.ruff] in pyproject.toml |
| Format check (no rewrites) | ruff | make fmt-check (make fmt rewrites) |
[tool.ruff] in pyproject.toml |
| Dead-code sweep (≥80% confidence) | vulture | make dead-code |
inline flag |
| Tests | pytest | make test |
— |
| Copy-paste duplication | jscpd | make duplication |
.jscpd.json |
| Dependency CVE scan | pip-audit | make audit |
— |
| Outdated deps (advisory, never fails) | uv | make outdated |
— |
| Flag | Default | Notes |
|---|---|---|
--addr |
127.0.0.1:7332 |
HTTP listen address |
--no-browser |
off | Don't auto-launch a browser |
--bd |
bd |
Path to the bd binary |
--dir |
cwd | Workspace to run in |
--strict-port |
off | Fail if the requested port is taken instead of auto-incrementing (default scans 20 ports from the start port) |
- Lanes — Deferred / Blocked / Ready / In-Progress / Closed are derived in-process from
bd list --jsonsnapshots - Activity — derived from
updated_at/closed_at/created_atbecause bd doesn't expose a global audit feed; rendered as a swim lane alongside the bead lanes - JSONL freshness — bdboard reads bead state via the
bd list ... --jsonCLI (the dolt-native, always-fresh path), using a three-way split rather than a single--allfetch (see ADR 0004): (1) active issues viabd list --no-pager --limit 0(no--all, so closed are excluded) — the ~5 KB fast first-paint path; (2) the board Closed lane + header CLOSED KPI viabd list --status closed --closed-after <iso> --sort closed --no-pager --limit 0, date-window-bounded (BOARD_CLOSED_WINDOW_DAYS) — not count-capped — so the lane and KPI count the same set (bdboard-p8v); and (3) the History page viabd list --status closed --sort closed --no-pager --limit 0 [--closed-after <iso>], which is count-uncapped by design so older closures never silently vanish (bdboard-a194) but is window-bounded when a range filter is active (bdboard-gp06). The closed lane is lazy-loaded as a second phase after the active lanes paint (hx-trigger="load"). We do NOT read.beads/issues.jsonldirectly — per the upstream COMMUNITY_TOOLS.md, that path is deprecated and may be missing fields. Awatchfileswatcher observes each dolt database'snoms/directory (plus.beads/itself) non-recursively so any bd write triggers a refresh within ~250ms (debounced) + ~1s cooldown. We deliberately avoid recursively watching the whole.beads/tree: dolt's churningnoms/object store would open a kqueue fd per directory on macOS and exhaustRLIMIT_NOFILE. SSE pushes a singlebeads_changedevent only when the bead list actually changed (structural equality vs the previous cache). Nobd exportcalls; bdboard never writes to.beads/.
Issue history is replicated off-machine using Dolt's git-compatible wire
protocol — it rides under refs/dolt/data on the same GitHub origin as the
code, not as a committed issues.jsonl. That custom ref is not
auto-fetched by a normal git clone, and the local Dolt DB itself
(.beads/embeddeddolt/) is gitignored — so a bare clone has no bead
database at all. Until you hydrate once, bd list reports no beads database found and bdboard renders an empty board. (See
notes/decisions/0003-beads-sync-via-dolt-git-refs.md
for the full rationale.)
After cloning, hydrate the bead database with a one-time bd bootstrap:
git clone https://github.com/weegens-aaron/bdboard.git
cd bdboard
bd bootstrap --yes # auto-detects refs/dolt/data on the git origin and clones it
bd list # should now be non-empty — full issue history is localbd bootstrap auto-detects the dolt data ref on the git origin. If your clone
somehow lacks the dolt remote, point it at the code origin (the same repo —
per ADR 0003 we do not use a separate dolt repo) before bootstrapping:
bd dolt remote add origin git+https://github.com/weegens-aaron/bdboard.git
bd bootstrap --yesGotcha — don't use
bd init+bd dolt pullfor a fresh clone.bd initseeds an independent Dolt history, so a subsequentbd dolt pullfails withError 1105: no common ancestor. Usebd bootstrap, which clones the remote history directly. (Hydration of ~200 issues takes ~40s; a raw pull-into-init can churn for minutes before erroring.)
Issue data is replicated off-machine. The Dolt remote origin points at
the same GitHub origin as the code (weegens-aaron/bdboard.git), and the full
issue history rides under refs/dolt/data there via Dolt's git-compatible wire
protocol — not as a committed issues.jsonl. See
notes/decisions/0003-beads-sync-via-dolt-git-refs.md
for the full rationale, and Getting the bead history
for how a fresh clone hydrates.
To push local bead writes off-machine:
bd dolt push # replicates issue history to refs/dolt/data on originThe bead database lives in .beads/embeddeddolt/ (gitignored) and is the
single source of truth, read at runtime through the bd CLI. bd dolt pull
brings remote changes back down on another machine.
bdboard never writes to
.beads/and never reads.beads/issues.jsonl; the canonical, runtime source of truth is the Dolt DB as read through thebdCLI.
If any
bdcommand hangs, you may have hit the staledolt sql-serveraccumulation issue — runpkill -9 -f 'dolt sql-server'(see Troubleshooting) and retry.
bd embeds dolt; each bd invocation can leave a dolt sql-server subprocess
running. Over weeks of use these accumulate and start fighting each other for
locks. Symptom: bd commands hang, bdboard's bead-detail panel times out, etc.
Fix:
pkill -9 -f 'dolt sql-server'Then bd — and bdboard — will be snappy again. If you see this happen often, file an issue upstream against bd.
You're not in a directory with .beads/.
cd to a bd workspace or pass --dir /path/to/workspace.
Contributions are welcome — see CONTRIBUTING.md for the dev setup and the checks CI enforces.
Released under the MIT License.