mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-08 22:21:45 +00:00
6 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c945fb404b
|
feat: add MCP servers settings UI at /settings/mcps (#540)
Some checks are pending
Rust / Format (push) Waiting to run
Rust / Clippy (push) Waiting to run
Rust / Generated Docs (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
## Summary
Adds a full CRUD management UI for server-managed MCP servers at
`/settings/mcps`, consuming the already-shipped `MCPServersApi` backend.
The implementation mirrors the existing `/settings/environments` pages
exactly in structure, naming, and component conventions.
## What changed
### Step 1 — Shared `KeyValueEditor` extracted
`KeyValueEditor`, `KeyValueEntry`, `entriesFromMap`, and
`mapFromEntries` are moved from `environment-form.tsx` into a new
`components/key-value-editor.tsx`. The component gains an optional
`renderEntryHint` prop so per-row warnings can be injected without
coupling the editor to credential logic. `Label` is promoted from
`environment-form.tsx` to `settings-panel.tsx` so both forms can use it.
### Step 2–4 — Query plumbing
- `query-keys.ts`: `mcpServers.{list, detail}` keys.
- `api-client.ts`: `mcpServersApi` instance (same pattern as
`environmentsApi`).
- `queries.ts`: `useMcpServers()` and `useMcpServer(id)` SWR hooks.
### Step 5 — Credential heuristics (`lib/credential-heuristics.ts`)
Pure functions `looksLikeCredential`, `secretNameForKey`,
`secretReference`. Key-name matching covers `authorization`, `password`,
`token`, `api[-_]?key`, `_key`/`_token`/`_secret` suffixes.
Value-entropy fallback fires for strings ≥ 20 chars, no spaces, mixed
case/digit classes. Template references (`{{ secrets.* }}`) are never
flagged.
### Step 6–7 — Form model + component (`components/mcp-server-form.tsx`)
- Flat `McpServerFormValues` discriminated on `McpTransportKind`.
- `defaultMcpServerFormValues`, `mcpServerToFormValues` (populates
`env`/`headers` from `env_keys`/`header_keys` with **empty values** —
the §5 write-only design), `createRequestFromForm`,
`replaceRequestFromForm`, `isMcpServerFormValid`, `credentialWarnings`.
- `McpServerFormFields` renders stdio / http / sandbox panels switching
on `values.transport`. Per-row credential nudge opens the secrets-new
page in a new tab and substitutes a `{{ secrets.NAME }}` reference; save
is never blocked by the heuristic.
- On edit, a row with a non-empty key and empty value blocks save with
an inline error (the intentional overwrite guard).
### Step 8–10 — Route pages
| File | Mirrors |
|---|---|
| `routes/settings-mcps.tsx` | `settings-environments.tsx` |
| `routes/settings-mcps-new.tsx` | `settings-environments-new.tsx` |
| `routes/settings-mcps-edit.tsx` | `settings-environments-edit.tsx` |
The edit page shows a write-only-values banner whenever the transport
has any `env_keys`/`header_keys`, uses `key={server.revision}` to
remount the form on external change, and translates 409 responses into
the `staleAwareMessage` pattern.
### Steps 11–12 — Router + nav
Three routes registered under `settings` children. `PuzzlePieceIcon` nav
entry added to the same section as Environments.
### Plan Summary
- Extract `KeyValueEditor` to shared component with hint-injection slot
- Credential heuristics library (pure, fully unit-tested)
- MCP form model: flat values ↔ discriminated API types, write-only-key
guard
- List / new / edit pages following environments pattern exactly
- Route registration and settings nav link
### Fabro Details
<details>
<summary>Ran 9 stages in 65m 58s for $20.54</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 2m 47s | – | 0 |
| preflight_lint | 4m 15s | – | 0 |
| implement | 26m 29s | $12.27 | 0 |
| simplify_opus | 7m 41s | $4.95 | 0 |
| simplify_gpt | 6m 51s | $2.69 | 0 |
| verify | 15m 44s | – | 0 |
| fixup | 1m 41s | $0.63 | 0 |
| **Total** | **65m 58s** | **$20.54** | **0** |
</details>
<details>
<summary>Ran <code>ImplementPlan.fabro</code> (11 nodes and 14
edges)</summary>
```dot
digraph ImplementPlan {
graph [
goal="Implement and simplify",
model_stylesheet="
* { model: claude-opus-4-8; }
"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
toolchain [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0]
preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check -q --workspace 2>&1", max_retries=0]
preflight_lint [label="Preflight Lint", shape=parallelogram, script="cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", max_retries=0]
fix_lints [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
implement [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD.", model="gpt-55", reasoning_effort="xhigh"]
simplify_opus [label="Simplify (Opus)", prompt="@prompts/simplify.md"]
simplify_gpt [label="Simplify (GPT-55)", prompt="@prompts/simplify.md", model="gpt-55"]
verify [label="Verify", shape=parallelogram, script="git fetch origin main 2>&1 && git merge --no-edit --no-stat origin/main 2>&1 && cargo +nightly-2026-04-14 fmt --all 2>&1 && cargo dev docs refresh 2>&1 && cargo +nightly-2026-04-14 fmt --check --all 2>&1 && { command -v rg >/dev/null 2>&1 || { echo 'rg is required for verify'; exit 127; }; } && ! rg -n 'AuthMode::Disabled|RunAuthMethod|RunSubjectProvenance|\bActorRef\b|\bActorKind\b|AuthenticatedSubject|AuthenticatedService|AuthorizeRunScoped|AuthorizeRunBlob|AuthorizeStageArtifact|AuthorizeCommandLog|auth_method\s*==\s*\"disabled\"' lib/crates apps lib/packages docs/public/api-reference/fabro-api.yaml 2>&1 && cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --workspace --status-level slow --profile ci 2>&1 && cargo dev docs check 2>&1 && bun install --frozen-lockfile 2>&1 && (cd apps/fabro-web && bun run typecheck) 2>&1 && (cd apps/fabro-web && bun run test) 2>&1 && (cd lib/packages/fabro-api-client && bun run typecheck) 2>&1 && cargo dev build -- -p fabro-cli --release 2>&1", goal_gate=true, retry_target="fixup"]
fixup [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all format, clippy, Rust test, docs, TypeScript typecheck/test, and build failures.", max_visits=3]
start -> toolchain
toolchain -> preflight_compile [condition="outcome=succeeded"]
toolchain -> exit
preflight_compile -> preflight_lint [condition="outcome=succeeded"]
preflight_compile -> exit
preflight_lint -> implement [condition="outcome=succeeded"]
preflight_lint -> fix_lints
fix_lints -> preflight_lint
implement -> simplify_opus -> simplify_gpt -> verify
verify -> exit [condition="outcome=succeeded"]
verify -> fixup
fixup -> verify
}
```
</details>
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: Fabro <noreply@fabro.sh>
|
||
|
|
62f0b3e7d1
|
refactor(web): improve React Doctor score (#405)
## Summary Improves the web UI's React Doctor audit score by separating reusable helpers from React component modules, tightening effect/state ownership, and extracting real component boundaries in the install wizard, stage activity view, run-files diff browser, RunDetail route, and Runs workspace. The branch removes the previously deferred RunDetail and Runs giant-component diagnostics without changing RunDetail UX, route contracts, action ordering, or Runs workspace behavior. | Metric | Main baseline | Initial PR | Current PR | |--------|---------------|------------|------------| | React Doctor score | 63 | 71 | 99 | | React Doctor errors | 123 | 0 | 0 | | React Doctor warnings | 241 | 163 | 3 | | React Doctor diagnostics | 364 | 163 | 3 | ## Changes - Moves exported helper logic out of component files so Fast Refresh/component-export rules no longer dominate the audit. - Adds a targeted React Doctor config exception for React Router route modules, where non-component exports like route metadata are intentional. - Refactors low-risk state/effect patterns: keyed interview question state, reducer-backed editable run title state, event-owned preview opening, route-keyed insights editor initialization, refresh timer ownership, and selection/derived list cleanup. - Reworks `InstallApp` around an install reducer, a controller hook for install lifecycle state, and focused wizard step components for LLM, server, object-store, sandbox, and GitHub setup. - Moves `RunStages` selected-stage activity into a keyed boundary for panel/debug detail state while preserving stage activity filters across navigation. - Extracts the `RunFiles` loaded diff-browser view from route/query coordination so the route owns data/URL state and the loaded view owns rendering. - Splits `RunDetail` into route-local header, actions, tab shell, docked controls, model, and lifecycle-toast modules; the actions menu now uses grouped descriptors instead of a large boolean/callback prop matrix. - Extracts Runs workspace preference ownership into `useRunsWorkspacePreferences` and moves toolbar rendering into `RunsToolbar`, leaving the route focused on data, DnD state, filtering, and view selection. - Guards `InsightsEditor` query execution with a latest-run id and timeout cleanup so stale or unmounted mock query runs cannot overwrite newer results. - Adds regression coverage for archived-run deletion from RunDetail and stale-result handling in InsightsEditor. - Improves semantic/accessibility coverage with labeled controls, native meter/section semantics, decorative status dots, and clearer unavailable copy. - Removes dead UI code and applies local suppressions only where the rule is a documented false positive or an intentional imperative integration boundary. ## Remaining React Doctor warnings Current score is 99 with 0 errors and 3 warnings. The remaining warnings are intentionally left for separate judgment rather than mechanical churn: - `prefer-useReducer` (3): `AutomationsNew`, `InsightsEditor`, and `CreateSecretForm` need reducers only if they encode real coupled transitions, not simple field setters. ## Verification - `cd apps/fabro-web && bun test app/routes/run-detail.test.ts` -> `22 pass`, `0 fail` - `cd apps/fabro-web && bun test app/routes/insights-editor.test.tsx app/routes/runs.preferences.test.tsx` -> `7 pass`, `0 fail` - `cd apps/fabro-web && bun run typecheck` - `cd apps/fabro-web && bun test --isolate` -> `490 pass`, `0 fail` - `cd apps/fabro-web && bunx react-doctor@latest --full --json > /tmp/fabro-react-doctor-runs-insights.json` -> score `99`, `0` errors, `3` warnings - Earlier branch verification also included `cd apps/fabro-web && bun run build` - `git diff --check` --- [](https://github.com/EveryInc/compound-engineering-plugin) 🤖 Generated with GPT-5 (context not reported, default reasoning) via [Codex](https://openai.com/codex) |
||
|
|
412f57f5ae
|
feat(web): add secrets management settings page (#327)
## 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> |
||
|
|
fbe8b50a16
|
feat(server): add GET /api/v1/providers and /settings/models page (#321)
## 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> |
||
|
|
a772c0feb5
|
feat(web): split settings into General/Integrations/Security/Storage tabs
Some checks failed
Rust / Format (push) Waiting to run
Rust / Clippy (push) Waiting to run
Rust / Generated Docs (push) Waiting to run
Rust / Test (Linux) (push) Waiting to run
Rust / Test (macOS) (push) Waiting to run
TypeScript / Typecheck (push) Has been cancelled
TypeScript / Test (push) Has been cancelled
TypeScript / Build (push) Has been cancelled
Replaces the single Settings overview with four focused tabs, plus the existing Live Events tab below a sidebar divider. JSON view is kept only on General and shows the full server settings document. The Storage tab splits Storage Root, SlateDB, and Artifacts into separate panels, with object store fields broken into one row per field via a shared ObjectStoreRows helper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
70cf37f26c
|
refactor(web): share settings panel UI between server and run pages
Extract Panel, Row, ViewToggle, and value renderers into a shared module so the run settings page can adopt the same paneled layout and Settings/ JSON toggle as the server settings page. The run page groups its frozen snapshot into Workflow, Sandbox, Git, and Artifacts panels and falls back to raw JSON for everything else. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |