Commit graph

244 commits

Author SHA1 Message Date
Bryan Helmkamp
244b0eccce
feat(web): Run Files full UX polish + extraction (P2-3..P2-8, P3-3, P3-1)
Extracts inline components into apps/fabro-web/app/routes/run-files/:
  - placeholders.tsx — sensitive/binary/symlink/submodule/truncated +
    DegradedBanner + pickPlaceholder priority resolver
  - states.tsx — EmptyState, LoadingSkeleton, InlineErrorBanner, Toast,
    RunFilesErrorBoundary, emptyStateCopy, deriveEmptyKind
  - toolbar.tsx — Toolbar with freshness + Refresh + Split/Unified
    toggle, 44×44 touch targets
  - keyboard.ts — useFileKeyboardNav with j/k nav + Enter/Space click

Adds:
  - P2-3: consumes parent runStatus via useMatches to derive the 4-
    variant R4 empty-state taxonomy (starting / no_changes /
    failed_before_checkpoint / diff_lost) plus an "unknown" fallback
    when the loader returned null.
  - P2-4: RunFilesErrorBoundary handles 401/403 (access denied),
    429/503 (inline retry affordance), 500 (parses request_id out of
    the response body and surfaces it in the copy so users can cite
    it when contacting support).
  - P2-5: Refresh button now disables when the server reports the
    same to_sha as the last successful fetch — no new checkpoint, no
    point firing another request.
  - P2-6: InlineErrorBanner for mid-session revalidation failures so
    the user doesn't unmount to the route ErrorBoundary on a transient
    SSE-triggered revalidation blip.
  - P2-7: "No changes in this run" toast when a revalidation empties
    the previously-populated list (files reverted upstream).
  - P2-8: @pierre/diffs Virtualizer wraps file lists > 20 entries so
    large runs don't synchronously mount every diff.
  - P2-2: Split/Unified toggle with localStorage persistence
    (fabro.run-files.diff-style). Below md (<768px) the toggle shows
    the forced "unified" state but doesn't overwrite the persisted
    desktop preference.
  - P3-1: Enter/Space on a focused file row fires a click so
    @pierre/diffs expand handlers (if any) take over, and the deep-
    link handler now clicks the resolved row after scrolling to
    trigger the same expand.

Refs plan docs/plans/2026-04-19-002-feat-run-files-changed-tab-plan.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 17:43:34 -04:00
Bryan Helmkamp
6050b5fa4b
feat(web): Run Files tab empty/error/loading states, refresh, a11y
Completes Units 11 and 12 of the Run Files Changed plan:

- Empty-state taxonomy: distinct copy for total_changed==0 vs no
  recoverable diff
- LoadingSkeleton on initial loader navigation (shimmer respects
  prefers-reduced-motion via motion-safe:animate-pulse)
- ErrorBoundary export handling 401/403/503/429 and generic 5xx
- Refresh button + Toolbar with freshness indicator; relative
  timestamps tick every 10s
- SSE subscription to /runs/{id}/attach with a 500ms debounce that
  revalidates on checkpoint.completed, run.completed, run.failed
- After a revalidation completes, focus returns to the Refresh button
- j/k keyboard navigation over file rows, ignoring key presses while
  a text field is focused
- md (768 px) breakpoint collapses split to unified without writing
  any persisted preference
- #file=<encoded-path> deep link scrolls + focuses the matching row
  on mount; absent file surfaces a 5s toast; patch-only mode shows
  a toast explaining the limitation
- Touch targets on the Refresh button meet WCAG 2.5.5 AAA (44x44)

Refs plan docs/plans/2026-04-19-002-feat-run-files-changed-tab-plan.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 17:13:17 -04:00
Bryan Helmkamp
731a8cc6e0
feat(web): rewrite Run Files tab, surface it in navigation
Rewrites apps/fabro-web/app/routes/run-files.tsx to consume the real
PaginatedRunFileList response and removes the fallbackFiles fixture
and the Steer subsystem. The new component:

- Loads via apiJsonOrNull, so a 404/501 (dev without the route)
  renders the empty state instead of the root error boundary
- Branches on meta.degraded + meta.patch to render PatchDiff with a
  DegradedBanner whose copy reflects degraded_reason
- Renders per-entry placeholders for sensitive, binary, symlink/
  submodule, and truncated files with the priority order
  sensitive > binary > symlink/submodule > truncated -- security
  flags never get hidden behind a lesser placeholder
- Renders one MultiFileDiff per regular entry
- Uses role="region" + aria-label on each file row

Also unhides the Files Changed tab in run-detail.tsx by flipping
broken: true -> false. Adds missing final_patch: None to the runner
RunFailed test fixtures to match the lifecycle change from Unit 2.

Refs plan docs/plans/2026-04-19-002-feat-run-files-changed-tab-plan.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 17:06:06 -04:00
Bryan Helmkamp
420e375134
build(web): upgrade @pierre/diffs to 1.1.15
Bumps @pierre/diffs from 1.0.11 to 1.1.15 to pick up the Virtualizer
component and renderHeaderPrefix/renderCustomHeader hooks the Run
Files tab relies on for large-diff performance. 1.0 -> 1.1 merged
MouseEventManager/LineSelectionManager into InteractionManager but
the public React components (MultiFileDiff, PatchDiff, FileDiff,
File) keep their existing shape, so no consumer changes are needed
yet -- Unit 10 exercises the new features.

Pins an exact version (1.1.15) rather than a caret range so bun
doesn't resolve up to 1.1.16, which was published today and would
trip the "no packages younger than 24 h" rule in the user-global
policy.

The redundant apps/fabro-web/bun.lock is removed; bun workspaces
resolve against the root bun.lock and the per-app lockfile was
drifting from it. Embedded SPA bundle (lib/crates/fabro-spa/assets/)
is refreshed to match the new build output.

Refs plan docs/plans/2026-04-19-002-feat-run-files-changed-tab-plan.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 17:00:50 -04:00
Bryan Helmkamp
2505cb6d46
Merge remote-tracking branch 'origin/main' into feat/web-install-wizard
# Conflicts:
#	lib/crates/fabro-spa/assets/assets/entry-ez8gc920.js
#	lib/crates/fabro-spa/assets/index.html
#	lib/packages/fabro-api-client/src/.openapi-generator/FILES
#	lib/packages/fabro-api-client/src/models/index.ts
2026-04-19 15:06:12 -04:00
Bryan Helmkamp
b8af65a9c6
refactor(runs): blocked status canonicalization cleanup (#165)
## Summary

Stacked cleanup of the `canonicalize blocked run status` work (local
commit `d13cdf374`) plus reconciliation with origin's `canonicalize
paginated run list responses` (origin commit `8ab689da7`). Both efforts
ran in parallel and diverged on the column name (`blocked` vs `waiting`)
and on how the board response is shaped — this PR converges them,
keeping `blocked` as the canonical column id while adopting origin's
`column` field on `RunListItem` and `StoreRunSummary` shape.

Also fixes a production-worker regression introduced by the
canonicalization: the worker's start-precondition only accepted
`Submitted | Starting`, so once runs started transitioning through
`Queued` on the way to `Starting`, every subprocess-worker run failed
with `Precondition failed: cannot start run: status is Queued`. That
cascaded into ~90 failing CLI/server integration tests locally.

## Commits

1. `f65843168` refactor(runs): simplify blocked status follow-ups
2. `1492d956c` chore: resolve clippy warnings
3. `676fd9f44` first merge of origin/main
4. `23fc92a2f` **fix(runs): allow Queued status in start precondition**
← the cascade-fix
5. `36b507a83` refactor: simplify pause/unpause + dedupe web status
tables
6. `8d8d27748` refactor(workflow): encapsulate BlockedStateTracker
inside HumanHandler
7. `1c17fda35` second merge of origin/main — resolves waiting vs blocked
8. `4cd3ef7b1` refactor(workflow): Mutex<usize> → AtomicUsize
9. `2e5a58e8a` fix(demo): align run-4 lifecycle status with Blocked
board column

## Test plan

- [x] fmt, clippy, build, doctests all clean
- [x] `cargo nextest run --workspace` — **4092/4092 pass**
- [x] `bun test` — **26/26 pass**, typecheck + production build clean
- [x] Manual CLI repro of the Queued-precondition fix
- [x] Browser smoke test: all 5 columns render with correct
labels/colors, demo run-4 appears in Blocked lane with question text
intact

## Known follow-up (not blocking)

A "paused-while-blocked" run (status `Paused` + `blocked_reason: Some`)
lands in the `running` column because the visible status chooses
`Paused` over `Blocked`. The pending question is not prominent on the
board. Addressing it would require `board_column()` to branch on
`(status, blocked_reason)` rather than just `status` — worth a separate
ticket.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 14:53:46 -04:00
Bryan Helmkamp
20e4ef32ae
refactor(install): collapse overlapping React state into discriminated unions
Two wins here. First, the `session`/`loadingSession`/`sessionError`
triple is replaced with a single `SessionState` discriminated union, so
the component can switch on `.status` instead of juggling three
correlated flags. Second, the seven flat `useState` calls for the
GitHub step are grouped into `githubStrategy` + `tokenForm` + `appForm`,
with `appForm.owner` typed as the generated `InstallGithubAppOwner`
tagged object. Invalid states like "token flow but org slug set" simply
stop existing.

\`buildInstallGithubAppOwner\` is deleted (unused) — form handlers build
the tagged object in place, which is small enough to stay readable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:59:09 -04:00
Bryan Helmkamp
2e7a6c95ea
refactor(install): use generated @qltysh/fabro-api-client types
Run \`bun run generate\` inside lib/packages/fabro-api-client to pick up
the new install schemas. Swap install-api.ts from hand-written
interfaces to re-exports from @qltysh/fabro-api-client and drop the
last duplicated type surface for the install wizard.

Keeps the \`installFetch\` wrapper and \`readInstallError\` helper so the
session-storage token handling and our custom error parser stay local
to the wizard. The generated Axios client is available as a future
migration if we decide to drop the wrapper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:55:58 -04:00
Bryan Helmkamp
acb6b3f9d6
refactor(install): tag GithubAppOwner with discriminated object shape
The install GitHub App manifest shape encoded owner as `"personal"` or
`"org:<slug>"` - a magic string parsed in install-app.tsx, built by
install-api.ts, and reparsed server-side. Replace with a tagged object
`{ kind: "personal" } | { kind: "org", slug }` in the OpenAPI spec, the
progenitor-generated Rust types, and the frontend.

Server-side, the internal `GitHubAppOwner` enum keeps its semantic
shape but gains a `TryFrom<GithubAppOwnerInput>` conversion and emits
the tagged JSON via `as_session_value`.

Frontend drops `buildGithubOwnerValue` in favor of
`buildInstallGithubAppOwner`, and the ready-screen renders the owner
through a small helper instead of string concatenation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:48:31 -04:00
Bryan Helmkamp
ad7fdc8d13
refactor(install): return spec-conformant ApiError shape
Install handlers returned `{"error": "..."}` while the OpenAPI paths
referenced the repo-wide `ErrorResponse` schema
(`{"errors":[{status,title,detail}]}`). Funnel the install helper through
`ApiError::into_response`, switch the invalid-token 401 and the
persistence-failure INTERNAL_SERVER_ERROR to the same shape, and update
the TS `readInstallError` helper + test fixtures to read
`body.errors[0].detail`.

The install-finish failure path still carries `leftover_env_keys`
alongside the error envelope so the rollback integration tests retain
their diagnostic field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:43:57 -04:00
Bryan Helmkamp
dd4e467bfc
Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lib/crates/fabro-server/tests/it/api/mod.rs
2026-04-19 13:36:54 -04:00
Bryan Helmkamp
3f21644d80
fix(install): cover follow-up edge cases
Harden the remaining install flow regressions and add the missing
coverage for startup dispatch, finish-time shutdown behavior, and
partial-state persistence after vault failures.
2026-04-19 13:32:46 -04:00
Bryan Helmkamp
75f8ed845b
fix(install): harden web wizard against review findings
Tighten the browser-based install flow after correctness and adversarial
review, without changing the external wizard shape.

- Persist the actual bind in server.listen, not the canonical URL
- Reject concurrent /install/finish and rapid GitHub App retries
- Keep the prior GitHub Token strategy until App callback succeeds
- Recover from poisoned install locks instead of propagating panics
- Rollback both settings and vault on failed persistence
- Redirect GitHub callback errors back into the wizard UI
- Validate LLM keys via /models probe instead of a billed generate()
- Reject canonical URLs with trailing slash, path, query, or fragment
- Accept any valid install-token source, not just the first present one
- Redact the install token in structured logs
- Assert install-mode SPA marker injection at startup
- Warn on suspected concurrent operators via UA + X-Forwarded-For
- Add component-level test for the GitHub callback error banner

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 12:43:00 -04:00
Bryan Helmkamp
9dd792c8b8
fix(install): exclude openai-compatible from v1 setup
Restrict the browser install flow to Anthropic, OpenAI, and Gemini,
remove the unused install-time base URL surface, and reject
openai_compatible with a stable 422 response.

Also fix the finishing health poller so it only redirects after the
server comes back healthy outside install mode instead of jumping early
on transient restart failures.
2026-04-19 11:43:38 -04:00
Bryan Helmkamp
b5bb134890
fix(runs): bound board enrichment and demo normalization
Paginate board-eligible summaries before enriching them from run state,
add safety caps to paginated web fetches, and make demo run summaries
follow the production title and status-reason normalization rules.
2026-04-19 11:35:30 -04:00
Bryan Helmkamp
ecdfdd82d8
feat(install): add browser-based setup flow
Implement the web-first install experience across the server, CLI, API spec,
web app, and packaged SPA assets.

This also removes test-side process env mutation by pushing env-dependent
decision points behind explicit helpers and test wiring.
2026-04-19 11:20:58 -04:00
Bryan Helmkamp
6226858648
fix(runs): finish canonical run summary rollout
Complete the /runs and /boards/runs canonicalization work by fixing the
run-detail response shape, preserving lifecycle status separately from board
columns, loading all board pages in the web client, and aligning the shared
status_reason typing.
2026-04-19 11:12:58 -04:00
Bryan Helmkamp
8ab689da78
feat(runs): canonicalize paginated run list responses
Unify /api/v1/runs and /api/v1/boards/runs around a shared
paginated summary contract with additive convenience fields.

Update the server, demo data, generated clients, CLI pagination,
and web consumers so board views become a thin projection over the
canonical run summary surface.
2026-04-19 10:37:31 -04:00
Bryan Helmkamp
6c57e17bb3
docs: add Homebrew as a first-class install method
Add Homebrew tab to the Quick Start install tabs and simplify the
agent-driven install.md to detect Homebrew and fall back to the install
script, dropping the gh/tar manual path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 12:37:15 -04:00
Bryan Helmkamp
828d686a6f
feat(release): add x86_64 and aarch64 musl Linux targets
Extend the release matrix to two statically-linked musl variants so
Alpine and other musl-based Linux hosts can install without glibc.
Homebrew and the Docker image remain glibc-only.

- release.yml: add x86_64-unknown-linux-musl (ubuntu-24.04) and
  aarch64-unknown-linux-musl (ubuntu-24.04-arm) matrix rows with
  musl-tools, CC_*_musl, CARGO_TARGET_*_LINKER, and LIBZ_SYS_STATIC
- Cargo.toml: enable git2 vendored-libgit2 so libgit2 compiles from
  source for every target (needed because musl cannot link against
  Ubuntu's glibc-built libgit2-dev)
- install.sh: check `ldd --version` for "musl" and rewrite the target
  from -gnu to -musl so Alpine users get the right tarball
- upgrade.rs: add detect_linux_libc() / parse_ldd_libc() helper and
  route detect_target() Linux arms through it, with unit tests
  covering glibc, musl, empty, and unknown output
- tests/it: extend target regex in the dry-run snapshot filter

Ubuntu 24.04 is required for the musl runner: 22.04 ships musl 1.2.2
which SIGSEGVs statically-linked x86_64 test binaries at startup.
Confirmed against graphviz-sys CI before landing here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 01:00:51 -04:00
Bryan Helmkamp
1c9ebba945
chore: delete stale apps/fabro-web/Dockerfile
References npm/package-lock.json and an `npm run start` script that no
longer exists. Nothing in CI or compose configs references it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 19:15:21 -04:00
Bryan Helmkamp
f3bf40ac83
deps: delete stale apps/fabro-web/package-lock.json
The project uses bun (bun.lock); the npm lockfile was vestigial and the
source of 7 Dependabot alerts (vite, lodash, path-to-regexp, picomatch).
2026-04-16 19:14:14 -04:00
Bryan Helmkamp
74578c22bc
feat(web): add empty state to runs page and fix logo link
Show a getting-started empty state with quick start commands and
resource links (docs, Discord) when there are zero runs. Link the
logo to /runs in non-demo mode instead of /start.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 08:02:18 -04:00
Bryan Helmkamp
c8be2068f6
fix(web): show both GitHub and dev-token login when both auth methods enabled
Previously the login page used an either/or conditional, hiding the
GitHub button whenever dev-token was in the methods list. Now GitHub
is the primary action and dev-token collapses behind a "Use a dev
token instead" toggle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 07:20:45 -04:00
Bryan Helmkamp
1b7c449262
fix(web): strip leading markdown heading markers from run goal
Some checks are pending
Rust / Format (push) Waiting to run
Rust / Clippy (push) Waiting to run
Rust / Test (Linux) (push) Waiting to run
Rust / Test (macOS) (push) Waiting to run
TypeScript / Typecheck (push) Waiting to run
TypeScript / Test (push) Waiting to run
TypeScript / Build (push) Waiting to run
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 12:04:50 -04:00
Bryan Helmkamp
5c03fb2e41
fix(web): truncate run goal to first line, max 100 chars with ellipsis
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 12:04:14 -04:00
Bryan Helmkamp
d98422595f
feat(web): render markdown in stage system prompt and assistant blocks
Add marked and @tailwindcss/typography to render markdown content as
HTML in the stage detail view instead of displaying raw text in a <pre>.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 11:21:25 -04:00
Bryan Helmkamp
e9101fb4c9
feat(web): graph direction control, failed node colors, breadcrumb and stage fixes
- Add direction toggle (LR/TB) to overview and graph pages, re-fetching
  SVG from server with ?direction= param on change
- Add failed node colors (red) to graph theme for both dark and light modes
- Color failed stages red and exit node green/red based on run outcome
- Skip pointer capture on graph node clicks so navigation works
- Hide workflow breadcrumb segment in non-demo mode
- Show empty state on stages page when no stages exist instead of 500
- Use apiJsonOrNull for stages endpoint to handle missing data gracefully

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:52:12 -04:00
Bryan Helmkamp
8353968f1f
feat(web): render command/script stage output on stage detail page
Command stages were emitting events but the frontend only handled agent
turns, leaving the page empty. Parse command.started/completed events
and display script, stdout, stderr, exit code, and duration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:23:55 -04:00
Bryan Helmkamp
789cbf9896
feat(web): enhance run detail UI with stages tab, graph controls, and live annotations
- Always show Stages tab (remove demoOnly) with /stages route
- Overview graph: live-annotated with running (pulsing teal) and
  completed (green) node states, clickable nodes link to stage pages,
  zoom/pan/fit controls
- Graph tab: clean neutral rendering without stage coloring

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:12:10 -04:00
Bryan Helmkamp
130d76729c
fix(server): show running stage immediately via checkpoint next_node_id
The stages API used checkpoint.current_node to identify the running
stage, but current_node is the last *completed* node — always already
in completed_nodes, so the running-stage check was always false.
Switch to checkpoint.next_node_id which correctly identifies the
currently-executing stage.

Also move SSE subscription from run-detail parent layout into the
StageSidebar component with since_seq=1 to replay all events and
close the race between loader fetch and SSE connection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:03:26 -04:00
Bryan Helmkamp
95e7165034
fix(web): hide empty assistant boxes for tool-call-only responses
Skip rendering assistant turn when agent.message has empty text
(LLM responded with only tool calls, no message content).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 09:56:36 -04:00
Bryan Helmkamp
3aa5a0c32d
feat(web): live stage sidebar with SSE updates and ticking timer
Extract shared StageSidebar component from 4 duplicated implementations
across run-overview, run-graph, run-settings, and run-stages routes.
Add SSE subscription in run-detail parent layout so all child routes
get live stage updates — stages appear immediately when they start,
show a spinning icon while running, and display a ticking elapsed timer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 09:46:01 -04:00
Bryan Helmkamp
75ab9965dd
feat(web): wire up non-demo run detail pages to real data
- Fix run detail status: display actual API status (submitted, running,
  succeeded, failed, etc.) instead of always showing "Working"
- Implement /runs/{id}/stages endpoint in non-demo mode, reading from
  checkpoint + events to build stage list with statuses and durations
- Fix /runs/{id}/graph to fall through to durable store when run is not
  in the live map
- Render real workflow graph SVG on overview and graph pages instead of
  hardcoded demo graph; remove unused DotDiagram component from overview
- Add dark mode CSS overrides for server-rendered SVG graphs
- Wire stage detail page to real event data: fetch from /events, filter
  by node_id, and render as system/assistant/tool blocks
- Fix stage page 500: use apiJsonOrNull for unimplemented /turns endpoint
- Filter start/exit graph control nodes from stage lists in the UI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 09:42:49 -04:00
Bryan Helmkamp
a90038f7a7
refactor(web): rename board column pending → initializing
Clarifies that Submitted/Starting runs are initializing, not just
pending. Also refactors run-detail to display the actual run status
via runStatusDisplay instead of mapping to board columns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 09:22:17 -04:00
Bryan Helmkamp
3e8a1f7cdc
feat(web): live-update runs board via SSE
Subscribe to the global event stream (GET /api/v1/attach) on the runs
board page. When a status-changing event arrives (run.submitted,
run.starting, run.running, run.paused, run.completed, run.failed),
debounce 500ms then revalidate the loader to refresh the board.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 08:54:08 -04:00
Bryan Helmkamp
84f3c80566
refactor(api): move features flags from /auth/me to /system/info
Features like session_sandboxes and retros are server-level capability
flags, not user settings. Expose them on GET /system/info where they
belong alongside other server metadata.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 08:51:22 -04:00
Bryan Helmkamp
a12ceb0ad0
fix(web): board runs endpoint reads from store, not in-memory state
The /boards/runs endpoint was driven by the in-memory state.runs map,
which is empty after server restart. Now reads from SlateDB store so
runs persist across restarts.

Also makes board columns dynamic from the API response instead of
hardcoded in the frontend. Real mode returns: pending, running, waiting,
succeeded, failed. Demo mode returns: working, pending, review, merge.

Board layout changed from fixed 3-column grid to horizontal scroll.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 08:36:55 -04:00
Bryan Helmkamp
cfd0005319
fix(web): handle 404 and 501 gracefully in API loaders
Demo mode returns 404 for unimplemented endpoints instead of 501.
Rename isNotImplemented to isNotAvailable covering both status codes,
and use apiJsonOrNull in workflow-runs loader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 07:54:11 -04:00
Bryan Helmkamp
549d85aaa7
fix(web): board layout, column config, and 501 error handling
- Forward route handle to React Router so wide:true works on /runs
- Switch board view to CSS grid for full-width columns
- Remove Verify column, rename Merge to Complete
- Use apiJsonOrNull in workflows/workflow-detail loaders to handle 501

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 07:48:50 -04:00
Bryan Helmkamp
bb63182dba
fix(web): use plain HTML form for logout to avoid React Router 500
React Router's <Form> intercepts submissions and tries to match the
action URL against client-side routes. Since /auth/logout is a
server-only route, this caused a 500 error on sign out.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 07:01:56 -04:00
Bryan Helmkamp
95ad4b1cfe
fix(web): redirect to /runs instead of /start after login
All three login paths (root redirect, dev token, GitHub OAuth callback)
now send users to /runs on first visit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 06:57:07 -04:00
Bryan Helmkamp
ce26f66846 feat(release): support prerelease builds
Add prerelease-aware release automation and keep default install and upgrade
paths pinned to the latest stable tag unless an explicit prerelease version is
requested.
2026-04-14 15:43:00 -04:00
Bryan Helmkamp
f4bae6e9bc refactor(setup): remove browser-based GitHub app bootstrap
Make fabro install the only supported GitHub App setup path. This removes
HTTP endpoints and browser routes that mutated local server config, rewrites
/setup as an operator instructions page, and aligns the installer manifest
with the live GitHub OAuth callback and setup URLs.
2026-04-13 18:25:29 -04:00
Bryan Helmkamp
a6775a051c feat(auth): add dev-token local server auth
Replace local no-auth startup with a shared dev-token flow for CLI-managed
servers. This provisions and validates dev tokens, preserves dev-token
provenance through browser sessions, and teaches local CLI and web clients how
to authenticate against local Unix and TCP servers.
2026-04-13 07:04:47 -04:00
Bryan Helmkamp
3fa7b65182 Split server runtime secrets from vault secrets 2026-04-12 14:03:54 -04:00
Bryan Helmkamp
dc93404e38 refactor(config): move project state under .fabro
Keep project config and checked-in workflows under .fabro so they stay out of
normal repo listings. Update config discovery, CLI project commands, fixtures,
docs, and checked-in workflow paths to use .fabro/project.toml and
.fabro/workflows/*.
2026-04-11 12:55:46 -04:00
Bryan Helmkamp
999f2a11c3 refactor(fabro-web): stage 6.6 rewrite workflowData literal to v2 shape
The hardcoded sample workflow entries in `workflow-detail.tsx` still
embedded the legacy flat `RunSettings` shape (top-level `llm`, `vars`,
`sandbox`, `setup`) — a visible mismatch with what the server now
returns on `/api/v1/runs/:id/settings`.

Rewrites the four static literals (fix_build, implement, sync_drift,
expand) to mirror the v2 `SettingsFile` tree: `_version`, `run.goal`,
`run.inputs`, `run.model`, `run.sandbox`, `run.prepare.steps`,
`run.prepare.timeout`, etc. Duration and size fields now use the
human-readable forms (`"120s"`, `"8GB"`, `"10GB"`) per R83 / R84.

Adds a module-level doc comment pointing readers at the
`fabro_types::settings::SettingsFile` Rust type as the source of truth
for the shape. `RunSettings` stays as `Record<string, unknown>`, so
the literal typechecks without needing a formal type assertion on
each entry.

fabro-web `typecheck` / `test` / `build` stay green.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:18:34 -04:00
Bryan Helmkamp
78c57d585c refactor(api): stage 6.6 collapse settings DTOs to freeform v2 shape
Replaces the legacy flat `ServerSettings` / `RunSettings` schemas in
`docs/api-reference/fabro-api.yaml` and 20+ supporting nested type
schemas (LlmSettings, SandboxSettings, HookDefinition, WebSettings,
ApiSettings, GitSettings, McpServerEntry, etc.) with two simple
`type: object, additionalProperties: true` schemas that declare the
wire shape as the v2 `SettingsFile` tree with secret-bearing subtrees
dropped before serialization.

Regenerates the Rust progenitor and TypeScript Axios clients against
the new spec. The progenitor generates `RunSettings` / `ServerSettings`
as `#[serde(transparent)]` newtypes over `serde_json::Map<String,
Value>`; the openapi-generator emits `{ [key: string]: any; }` inlined
into the API method signatures and no longer exports named model
types.

Updates fabro-web to define local `type ServerSettings =
Record<string, unknown>` / `type RunSettings = Record<string,
unknown>` aliases since the generated client no longer exports them.
The UI only `JSON.stringify`s these payloads into a CollapsibleFile,
so the opaque shape is fine.

All 3,756 workspace tests remain green. The OpenAPI conformance test
`server_settings_keys_match_openapi_spec` still passes because
`compare_schema` short-circuits on pure-map schemas (no `properties`);
it becomes a no-op that will be removed entirely when Stage 6.3b
deletes the legacy flat `Settings` struct it still builds.

Unblocks the server handler + CLI migration in the next commits of
Stage 6.6.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:04:33 -04:00
Bryan Helmkamp
5003fb5c2e fix(fabro-web): restore local watch rebuilds
Replace the unsupported Bun.watch call in the SPA build script with
node:fs.watch so `bun run dev` keeps running in local development.
Add a regression test that verifies watch mode stays alive until
interrupted.
2026-04-08 15:27:27 -04:00