Hovering the run header items now reveals a popover with extra
context:
- Run status: failure reason and error message for failed runs;
archived timestamp for archived runs (no popover otherwise)
- Repository: full owner/repo name and the cloned branch
- Workflow: node and edge counts plus run labels
- PR: live GitHub details fetched lazily on hover — title, an
open/draft/merged/closed badge, and the head -> base branch arrow
Workflow node/edge counts are new: WorkflowRef now carries
node_count/edge_count, computed in build_summary from the parsed
graph that is already in hand there.
Adds a HoverCard primitive alongside Tooltip (shared useHoverAnchor
hook) for rich, viewport-aware popovers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## What
Adds a **Settings → Secrets** page so secrets can be managed from the
browser, backed by the existing secrets HTTP API and generated TS
client.
- **`/settings/secrets`** — lists stored secrets (name, type badge,
description, last-updated) and deletes them through the shared confirm
dialog.
- **`/settings/secrets/new`** — the create form for **token** and
**file** secrets. OAuth secrets still list and delete here, but are
created by provider sign-in flows, not typed by hand (matching the CLI's
`secret set`).
- The **Secrets** entry is added to the settings sidebar nav.
## How
- `secretsApi` wired into `api-client.ts`; `useSecrets()` SWR hook +
`secrets` query key.
- New sibling routes `secrets` and `secrets/new` under `settings` (same
pattern as `runs` / `runs/:id`).
- The settings layout gains optional **handle-driven** `description` and
`headerAction`. When a page declares them, the layout renders title +
subheading + a vertically-centered header action button as one unified
header. Other settings pages are unaffected — they fall back to the
existing title-only header.
## Notes
- Values are write-only: the API never returns secret values, and the UI
never displays them.
- Reuses existing primitives throughout (`Panel`, `Badge`,
`ConfirmDialog`, `useToast`, button/input classes) — no new shared
components.
- Verified: `bun run typecheck` and `bun run build` pass; routes serve
200.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Adds server-visible resource reporting and a compact Resources settings
tab for CPU, memory, and the filesystem that contains Fabro storage.
## Changes
- Adds `GET /api/v1/system/resources` backed by `sysinfo`, including CPU
sampling, cgroup-aware memory reporting, storage filesystem matching,
and Fabro-managed disk byte totals.
- Extends the OpenAPI contract and regenerates the Rust and TypeScript
API clients.
- Adds a deterministic demo-mode resources route.
- Adds `/settings/resources` with 5 second polling and panels for
overview, CPU, memory, disk, and notes.
- Adds server integration/unit coverage and web route/render coverage.
## Screenshot

## Verification
- `cargo build -p fabro-api`
- `cd lib/packages/fabro-api-client && bun run generate`
- `cargo nextest run -p fabro-server --features test-support --test it
api::system`
- `cargo test -p fabro-server resource_sampler::tests`
- `cd apps/fabro-web && bun test`
- `cd apps/fabro-web && bun run typecheck`
- `cd apps/fabro-web && bun run build`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
---
[](https://github.com/compound-engineering)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Operators had no UI surface to see which LLM providers their Fabro
server has configured — provider state was only inferable indirectly via
the per-model `configured` flag on `GET /api/v1/models`. This adds a
dedicated **Models** settings tab backed by a new providers endpoint.
- **`fabro_model::Provider`** — a public projection of the internal
`CatalogProvider` that *structurally* excludes credential-bearing fields
(`auth`, `extra_headers`, `billing_policy`, `agent_profile`). Reused by
the generated API client via progenitor `with_replacement`, mirroring
the existing `Model` pattern — no parallel API DTO.
- **`GET /api/v1/providers`** — lists catalog providers with effective
config and a `configured` status stamped per request from
`ready_llm_provider_ids()`. Sorted by the catalog's existing
`provider_order`. No write endpoints.
- **`/settings/models` web page** — new route + nav entry
(`CpuChipIcon`, between Integrations and Security) rendering each
provider with model count, default model, configured status, and a "Get
API key" link for unconfigured providers.
## Key decisions
- Provider sort: reuse catalog `provider_order` (priority desc, id asc)
— zero extra code.
- `adapter` is hidden in the UI row (noisy for first-party providers);
the OpenAPI `adapter` field is pinned to an enum matching the closed
`AdapterKind` type.
- `configured` reflects credential resolution **at the time of the
response**, not a frozen startup snapshot — doc/spec wording corrected
to match.
## Testing
- `fabro-model`: `From<&CatalogProvider>` + serde `skip_serializing_if`
unit tests.
- `fabro-api`: `Provider` type-identity + JSON-parity tests, including
the required/optional field split.
- `fabro-server`: handler tests for configured vs unconfigured
providers, exact `model_count`/`default_model` against catalog truth,
and credential-omission (asserts internal field names *and* the injected
credential value never reach the wire).
- OpenAPI route conformance test covers `GET /api/v1/providers`.
- `cargo build --workspace`, `fmt --check`, `clippy -D warnings` clean;
935 Rust tests pass; web `tsc` typecheck passes.
- Reviewed via a 10-persona `ce:review` (autofix) — no P0/P1 in shipped
code; 8 safe fixes applied.
Not done: manual UI screenshots — the `apps/fabro-web` build is blocked
in this environment by an unrelated missing `@assistant-ui/react`
dependency. Run `bun install` in `apps/fabro-web` to verify
`/settings/models` manually.
## Post-Deploy Monitoring & Validation
- **What to watch:** request logs for `GET /api/v1/providers` — expect
`200`s for authenticated users, `401` for unauthenticated. The handler
resolves LLM credentials per request via `ready_llm_provider_ids()` (the
same path the existing `list_models` handler already uses).
- **Healthy signals:** `/settings/models` renders the provider list;
`configured` matches each provider's actual credential state; no
credential strings appear in any response body or log line.
- **Failure signals / rollback trigger:** any provider object in the
response containing `auth`, `extra_headers`, or a raw key/token value →
roll back immediately (the projection type makes this structurally
impossible, but treat any occurrence as P0). 5xx spikes on the new
route.
- **Validation window / owner:** first 24h after deploy, owned by the
deploying engineer. Pre-existing note (not introduced here): credential
resolution can refresh OAuth tokens and write the vault as a side effect
of this read — shared with `list_models`; flagged for a future caching
pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
- Surfaces parent/child run relationships in the web UI as a new
**Children** tab between Files Changed and Sandbox on `/runs/:id`.
- Backend exposes a new `children_count` field on the `Run` summary,
computed on read from the existing
`RunProjectionCacheState.children_by_parent` index — accurate without an
extra query.
- Frontend reuses the compact-table `RunRow` from `/runs` (now exported)
so the children list matches the existing list-view at a glance.
- Tab always shows, with a zero state when there are no children.
Refresh button (icon-only, matching the Files Changed pattern)
re-fetches both the list and the parent detail so the count badge
updates with the list.
## Screenshots
Captured live from the running fabro server.
**Populated — `Children · 2` tab active, two succeeded child rows:**

**Zero state — visiting a run that has no children:**

## API verification
```sh
# parent
$ curl -s -H "Authorization: Bearer $TOKEN" \
http://127.0.0.1:32276/api/v1/runs/01KRTKP5DJJ4EV6T7QSB081Z1N \
| jq '{id, parent_id, children_count}'
{
"id": "01KRTKP5DJJ4EV6T7QSB081Z1N",
"parent_id": null,
"children_count": 2
}
# child
$ curl -s -H "Authorization: Bearer $TOKEN" \
http://127.0.0.1:32276/api/v1/runs/01KRTKP7VAS2J2AG73GQSAKF4G \
| jq '{id, parent_id, children_count}'
{
"id": "01KRTKP7VAS2J2AG73GQSAKF4G",
"parent_id": "01KRTKP5DJJ4EV6T7QSB081Z1N",
"children_count": 0
}
# list-by-parent
$ curl -s -H "Authorization: Bearer $TOKEN" \
"http://127.0.0.1:32276/api/v1/runs?parent_id=01KRTKP5DJJ4EV6T7QSB081Z1N" \
| jq '{count: (.data | length), has_more: .meta.has_more}'
{ "count": 2, "has_more": false }
```
## What's in each commit
| Commit | What |
| --- | --- |
| `2f5f4296` | `chore(api-client)`: regenerate TS client from current
OpenAPI spec — catches up drift from #292's source-aware diagnostics and
the session/turn shape updates that hadn't been re-run yet. Pure
generator output. |
| `ba16d2b3` | `feat(web)`: the actual Children tab feature. Backend
`children_count` field + cache wiring, new `useChildRuns` SWR hook,
exported `RunRow`/`RUNS_LIST_GRID_TEMPLATE` from `runs.tsx`, new
`run-children.tsx` route, `Run.children_count` on the generated TS type.
|
| `de0c32c9` | `docs`: live UI screenshots for this PR. Safe to revert
before merge if reviewers prefer a screenshot-free repo. |
## Reproducing the screenshots
1. `cargo build -p fabro-cli && ./target/debug/fabro server start`
2. `cd apps/fabro-web && bun run build`
3. ```sh
PARENT=$(./target/debug/fabro run hello --dry-run --detach --sandbox
local --json | jq -r .run_id)
./target/debug/fabro run hello --dry-run --detach --sandbox local
--parent "$PARENT"
./target/debug/fabro run hello --dry-run --detach --sandbox local
--parent "$PARENT"
```
4. Open `http://127.0.0.1:<port>/runs/$PARENT/children` (populated) and
a child's children tab (zero state).
## Test plan
- [x] `cargo nextest run -p fabro-store -p fabro-types -p fabro-api -p
fabro-server -p fabro-mcp-server` — 900+ tests pass, including new
`run_summary_includes_children_count` in `fabro-store`
- [x] `cd apps/fabro-web && bun run typecheck` — clean
- [x] `cd apps/fabro-web && bun test` — 383/383 pass
- [x] OpenAPI ↔ Rust parity (the `fabro-api` `run_summary_round_trip`
test covers the new field both directions)
- [x] Manual API verification via curl (above)
- [x] Live UI verification (screenshots above)
## Out of scope (v1)
- Real-time SSE updates of the children list (refresh button covers
this).
- Multi-page pagination UI (shows first page with a "more exist" footer
when `has_more`).
- Parent breadcrumb on the child run page (separate small change).
- Tree/nesting view (flat list only).
- Empty-state CTA.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Render /profile/sessions from the new GET /api/v1/auth/sessions API.
The page shows the current browser session and active CLI sessions in one
list, with revoke buttons gated by the backend-supplied revocable field.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lists backend-discovered TCP services for a run's sandbox between the
Terminal and Filesystem tabs. Previewable ports open a signed Daytona
URL in a new browser tab; the rest render as Unavailable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a third right-column mode (Terminal | Filesystem | VNC) gated to
Daytona sandboxes. The panel POSTs /api/v1/runs/:id/sandbox/vnc, embeds
the signed noVNC preview URL in an iframe with clipboard + fullscreen
allowed, and renders distinct states for unsupported provider (Docker
hides the tab entirely), 409 startup failure (recoverable, "Try again"),
and 404/501 (non-recoverable). A reconnect button refetches the signed
URL when it expires.
Also fixes a Filesystem regression: the previous "skip first effect"
ref guard meant @pierre/trees never received its first resetPaths call
when the listing transitioned from empty to populated, leaving the tree
stuck on the initial empty model. Now the model is always synced via
resetPaths whenever the listing changes; verified live against a
Daytona /workspace + / listing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a Filesystem right-column mode alongside Terminal in /runs/:id/sandbox,
selectable via ?mode=filesystem. The persistent SandboxDetails left column
stays visible in both modes. Browses the run sandbox via the existing
list/get file endpoints, previews text files with @pierre/diffs, and falls
back to download-only for binary, oversized, and unreadable files.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds /runs/:id/sandbox between Files Changed and Terminal, gated by
the same sandbox-presence check as Terminal. The route fetches
SandboxDetails through a new useRunSandboxDetails SWR hook and renders
provider-neutral panels for Overview, Resources, Labels, and
Timestamps. Null fields render as muted em dashes.
Add committed, uncommitted, and all scope handling for run files with source reporting for sandbox and final patch responses.
Wire the run files page to persist scope in the URL and cache each scope independently.
Click the title (or its hover-revealed pencil) to swap it for an input;
Enter or blur saves via PATCH /runs/{id}, Esc reverts. Trims whitespace,
no-ops blank or unchanged values, and surfaces server validation errors
through the existing toast system. The board and breadcrumb refresh from
the same SWR cache after a successful save.
Expand the OpenAPI contract for frontend auth and workflow routes, regenerate the TypeScript Axios client, and route web API calls through generated client classes while preserving SSE and install exceptions.
Lists captured artifacts grouped by stage and retry, with per-file
download links that stream from the existing artifact download endpoint.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Route command stderr into stdout at execution time and expose a single output log across events, projections, API clients, and the web UI. Keep replay compatibility for older command.completed events that still contain split stdout/stderr fields.
Extend GET /api/v1/boards/runs with include_archived=true (matching the
existing flag on listRuns), add an Archived BoardColumn that the server
appends only when the flag is set, and surface a "Show archived" toggle
on /runs that flips between request shapes. Default behavior is unchanged
— archived runs stay hidden.
Server: list_board_runs now takes ListRunsParams; board_column maps
RunStatus::Archived to BoardColumn::Archived; board_columns(include_archived)
appends the column conditionally. Two new handler tests cover the default
and flag-on paths.
Web: useBoardsRuns(includeArchived) keys requests so SWR refetches on
toggle; columnStatuses + columnStatusDisplay + columnStyles get an
"archived" entry; buildSkeletonColumns filters by the flag so the loading
state matches the eventual response. Two new buildBoardColumns tests cover
both column shapes.
Touched generated TS client files include unrelated whitespace drift from
openapi-generator-cli; including them keeps the working tree consistent
with what `bun run generate` produces.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Stage detail now loads activity from a canonical stage-scoped events
endpoint instead of falling back to the first 1000 run-wide events. This
fixes empty panes for late stages in long runs and removes the
presentation-shaped `StageTurn` API from the wire.
### Plan Summary
- Add `GET /runs/{id}/stages/{stageId}/events` with cursor pagination
and server-side `node_id` filtering.
- Replace frontend stage-turn/fallback loading with paginated
stage-events loading and local event-to-activity projection.
- Broaden SSE/SWR invalidation so every activity event consumed by the
reducer refreshes the per-stage cache.
- Remove `StageTurn` schemas/client models and update demo fixtures plus
pagination/handler/reducer tests.
## What changed and why
The store now scans the run event prefix and filters by `node_id` before
applying the `limit + 1` cutoff. That preserves sparse late-stage
matches that would otherwise be dropped if we reused the run-wide
limited scan and filtered afterward. The real-mode handler returns an
empty page for an unknown stage id in an existing run, while preserving
404 for missing runs.
On the frontend, `run-stages` fetches all pages for the selected stage
and feeds them through `eventsToActivity`, keeping `TurnType` as a local
presentation model. Invalidation now targets `runs.stageEvents(runId,
stageId)` for lifecycle and reducer-consumed activity events
(`stage.prompt`, agent messages/tools, and command events), so active
panes refresh from the existing run event subscription.
The OpenAPI document and generated TS client now expose
`listStageEvents` and drop stale `StageTurn` models. Demo mode serves a
`detect-drift` stage-events fixture using the same cursor semantics as
the real endpoint.
## API notes
`/runs/{id}/stages/{stageId}/turns` is removed; clients should use
`/runs/{id}/stages/{stageId}/events?since_seq=&limit=` and project
events locally. The `stageId` path segment for this endpoint is the
workflow node id, not the visit-qualified `node_id@visit` form used by
command logs/artifacts.
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
Persist command stdout/stderr through scratch logs and finalized CAS refs, expose byte-offset tailing through the API, and render separate streaming panels in the web run view.
Resolve command output blob refs for execution-time consumers such as edge routing and retros, and make Docker streaming timeout/cancel drain output before returning.
Ensure local runs use the worktree checkpoint path by default, expose source and sandbox paths in API/web surfaces, and remove dead fork/rewind push controls. Update docs for clone-based sandboxes and durable checkpoint timelines.
Add GET /api/v1/runs/{id}/graph/source returning text/vnd.graphviz so
the run graph can be inspected as the original Graphviz DOT in addition
to the rendered SVG. Refactor get_graph to share DOT loading with the
new handler. The web run-graph view gains a Graph | Source toggle that
lazy-loads and displays the DOT with a copy button.
Add a "Run Logs" entry to the run detail sidebar that fetches the
worker tracing log via GET /api/v1/runs/{id}/logs and renders it with
auto-refresh while the run is live. Refreshes the embedded SPA bundle.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace React Router loader/action state paths with SWR query and mutation hooks.
Add targeted run and board EventSource managers that invalidate SWR keys, and refresh embedded SPA assets.