Skip to content

Project Detail

scripts/dashboard/project-detail.js builds one bob-project-detail/1 document for one project on one machine (#2924). It holds what an expanded bob viz card shows beyond the compact tile. The fleet feed is pushed to every open page, so it carries only what a tile needs. This document is fetched when a card is opened.

Terminal window
node scripts/dashboard/project-detail.js <machine> <project> # the bob-project-detail/1 document; exit 3 + {"error"} when unknown
GET /bob/project/<machine>/<name>.json → 200 bob-project-detail/1
→ 404 {"error": "no machine …" | "no project … on …"}
→ 500 {"error": "project detail failed: …"}

The dashboard server serves it next to /fleet.json. A card fetches it when it is opened, and scripts/dashboard/bob-card.js renders it (Expanded card, #2925). While the card stays open, the page re-reads it every refresh_s. It is never pushed.

  • Cached per project. A document is rebuilt at most once per DASH_DETAIL_REFRESH_S (default 120 s), however many tabs open the card. Concurrent requests for a project that is building wait for that build and share its gh reads.
  • Bounded. One build makes up to seven gh reads: three labelled issue lists, PRs, the last run, and the run’s issue and flight plan when a run names them. Cost and reports come from the server’s cached /series.json and /reports.json builds, so they add no gh reads.
  • Degraded forge. When a build’s forge reads fail, the server serves the last good document instead. That document carries forge: {status: "degraded", reason, fix, failed, cached_age_s} and the #1968 gh evidence block, as /notices.json does. The next build that succeeds drops the label. When there is no earlier good document, the new build is served with the failed parts unavailable and cached_age_s: null.
  • Unknown targets are a 404 with a JSON error object: a machine the registry does not declare, a project the feed has no row for on that machine, or a malformed path. A failed build is a 500 with an error object. Neither ever returns a stack trace.
Section Project on this machine Project on another machine
session the warp state file, read live the snapshot’s warp run record
activity.commits git log -5 the snapshot’s last_commit only (commits_complete: false)
activity.prs gh pr list gh pr list
queue gh issue list per label, plus the flight plan issue the same
health .project-ready.json (with each failing check’s fix), the harness-defect and advisory state files, and the declared CI locus. gh run list supplies the last CI run. the snapshot’s project_ready (check names only), harness_defects, advisories and ci. gh run list supplies the last CI run.
cost this server’s bob-series/1 row for the project the same, when the project is registered here
reports this server’s bob-reports/1 and the latest stored briefing the same
links the repo URL; dev.json (url or server.port, then environments.prod.url); docs-site.json base_url; the /bob/links.json entry the same. There is no dev URL, because a localhost URL on another machine does not reach it.

A project on another machine is never contacted. Its facts are whatever its last snapshot published, and the document’s machine.age_s and machine.stale say how old that is. The forge-derived parts are read by the serving machine, because the repo is the same wherever it is checked out. The repo is the local registration’s origin, else the snapshot’s ci.slug.

The run context (issue, chunk, branch, host_session, flightplan) is read by scripts/fleet/project-live.js, so audit.js snapshot publishes it too. A snapshot published before #2924 lacks it. Each of those fields then reads not measured with that reason.

session.attach is set while a run is active (#3163). command is the one attach command, bob warp attach <project>, which follows the run to each new segment, so there is no warp-seg-<N> to look up. machine names the machine to run it on, and here is false when that is not the serving machine. The card shows it as the attach row of Now. It is null when no run is active.

{
"schema": "bob-project-detail/1",
"generated_at": "2026-09-23T03:00:00.000Z",
"refresh_s": 120,
"machine": { "id": "farm-01", "label": "The Farm (worker 01)", "current": true, "source": "live", "as_of": "…", "age_s": 0, "stale": false },
"project": { "name": "bigbrain", "repo": "paulirv/bigbrain" },
"forge": { "status": "ok", "reason": null, "fix": null, "failed": [], "cached_age_s": null },
"session": {
"status": "ok", "reason": null,
"session": "active", "phase": "coding",
"issue": { "number": 2924, "title": "bob-project-detail/1: …", "url": "https://github.com/paulirv/bigbrain/issues/2924" },
"chunk": { "index": 0, "total": 2 }, "elapsed_s": 7884, "tokens": 757127,
"branch": "feature/issue-2924-project-detail",
"host_session": { "kind": "tmux", "name": "warp-seg-bigbrain-6", "window": "warp-seg-6" },
"attach": { "command": "bob warp attach bigbrain", "machine": "farm-01", "here": true },
"flightplan": { "number": 2921, "url": "…/issues/2921", "done": 7, "total": 13 },
"not_measured": {}
},
"activity": {
"status": "ok", "reason": null,
"commits": [{ "sha": "…", "subject": "…", "at": "…", "age_s": 120, "url": "…/commit/…" }],
"commits_complete": true,
"prs": [{ "number": 2957, "title": "…", "draft": false, "ci": "success", "url": "…" }],
"not_measured": {}
},
"queue": {
"status": "ok", "reason": null,
"approved": 111, "next": { "number": 2924, "title": "…", "url": "…" }, "next_from": "flightplan",
"open_bugs": 16, "open_p1": 13, "not_measured": {}
},
"health": {
"status": "ok", "reason": null,
"project_ready": { "verdict": "READY", "ok": true, "at": "…", "failing": [] },
"preflight": null,
"ci": { "locus": "farm", "label": "self-hosted-bob", "verdict": "unmeasured", "drift": null, "fix": null, "queue": null,
"last_run": { "conclusion": null, "status": "in_progress", "workflow": "CI", "branch": "master", "at": "…", "url": "…" } },
"harness_defects": { "open": 0, "recurrences": 0, "unreported": 0, "fingerprints": [], "issues": [] },
"advisories": null,
"not_measured": { "advisories": "no dependency audit has run on farm-01" }
},
"cost": { "status": "ok", "reason": null, "window": { "days": 30 }, "as_of": "…", "machine": "farm-01", "series": { "usd_per_day": { "status": "ok" } }, "not_measured": {} },
"reports": { "status": "ok", "reason": null, "session_report": { "title": "…", "url": "…" }, "tick_digest": null, "brief": { "file": "…", "items": 2, "url": "/briefings" }, "not_measured": { "tick_digest": "…" } },
"links": { "status": "ok", "reason": null, "repo": "https://github.com/paulirv/bigbrain", "approved": "…", "prs": "…/pulls", "actions": "…/actions",
"warp_viz": { "path": "/warp", "url": null, "port": null }, "dev": null, "deploy": null, "docs": null,
"not_measured": { "dev": "no dev URL (dev.json) declared" } }
}
  • forge says whether the gh reads landed. It is ok, degraded (the first failed read’s typed reason, its fix, and which reads failed) or not-measured (no GitHub repo known). The gh reason names are the ones Notices uses. Each has one fix: gh-missing → install gh, rate-limited → wait for the window, unauthenticated → gh auth login, server-cannot-run-gh → bob warp viz (restart from a fresh shell), anything else → bob forge health.
  • Every section has a status. It is ok, not-measured or unavailable, with a reason when it is not ok. When a section is readable but one field is not, the field is null and not_measured[field] says why.
  • Nothing is defaulted. A queue that could not be read is unavailable, never approved: 0. An unaudited project has advisories: null, never zero open advisories. A PR with no checks has ci: null, never a pass. An in-progress run has conclusion: null.
  • Elapsed is as of the source. On a snapshot, session.elapsed_s is measured to the snapshot’s generated_at, as the feed measures it.
  • The queue counts are exact. Each count comes from its own labelled list (approved, bug, p1-critical), up to 200 each. A count that reaches the limit is a lower bound, and its not_measured entry says so.
  • The next item is the first unticked item of the run’s flight plan (next_from: "flightplan"). Without a plan, it is the approved queue’s head in the warp-drive order: priority, then bugs before reqs, then oldest first (next_from: "approved").
  • The warp viz link is the /bob/links.json entry for the project, which is monitorLinks in scripts/dashboard/fleet-links.js. No URL is built here.