fabro/lib/crates/fabro-server/tests/it/api/settings.rs
Bryan Helmkamp 4190e13a20
Collapsible run stage sidebar (#352)
## What

Makes the run-detail stage sidebar (shown on the Overview and Stages
tabs) collapsible with a slide animation.

- A toggle button slides the panel between full width (`w-56`) and an
icon-only rail (`w-12`), animating `width` over 300ms with the same
easing as the Ask Fabro panel.
- When collapsed, **stage status icons stay visible** — green check /
red X / spinning teal for running — so run progress is still scannable
at a glance. Workflow links (Graph Source, Run Logs, etc.) collapse to
icons too so they remain reachable.
- Labels and durations become `sr-only` with `title` tooltips for hover.
- The open/closed choice persists to `localStorage`
(`fabro:stage-sidebar-collapsed`), carrying across the Overview and
Stages tabs and reloads.

## Layout

- The collapse toggle is inline with the `STAGES` heading row (or
`WORKFLOW` when a run has no stages yet), so it doesn't push the stage
list down.
- The stage sidebar's top padding on the Stages tab was reduced (`pt-6`
→ `pt-3`) so the heading aligns with the adjacent content column and
sits closer to the tab nav.

## Notes

Self-contained in `StageSidebar` — `run-overview.tsx` and
`run-stages.tsx` render it inside flex layouts that already track its
width, so the slide works in both with no parent changes (aside from the
padding tweak).

Verified: `tsc` typecheck passes; `stage-sidebar` lib tests pass
(10/10).

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: fabro-sh-0530[bot] <281434857+fabro-sh-0530[bot]@users.noreply.github.com>
Co-authored-by: Fabro <noreply@fabro.sh>
2026-05-22 12:20:29 -04:00

72 lines
2.1 KiB
Rust

use axum::body::Body;
use axum::http::{Request, StatusCode};
use fabro_server::test_support::test_app_state_with_runtime_settings_and_options;
use tower::ServiceExt;
use crate::helpers::{response_json, settings_from_toml};
#[tokio::test]
async fn retrieve_server_settings_returns_dense_server_settings_from_app_state() {
let settings = settings_from_toml(
r#"
_version = 1
[server.listen]
type = "tcp"
address = "127.0.0.1:32276"
[server.storage]
root = "/srv/fabro"
[server.scheduler]
max_concurrent_runs = 9
[server.auth]
methods = ["dev-token", "github"]
[server.auth.github]
allowed_usernames = ["alice"]
[server.integrations.github]
client_id = "Iv1.abcdef"
"#,
);
let app = fabro_server::test_support::build_test_router(
test_app_state_with_runtime_settings_and_options(
settings.server_settings,
settings.manifest_run_defaults,
5,
),
);
let request = Request::builder()
.method("GET")
.uri("/api/v1/settings")
.body(Body::empty())
.unwrap();
let response = app.oneshot(request).await.unwrap();
let body = response_json(response, StatusCode::OK, "GET /api/v1/settings").await;
let top_level = body
.as_object()
.expect("server settings response should be an object");
assert!(top_level.contains_key("server"));
assert_eq!(body["server"]["listen"]["type"], "tcp");
assert_eq!(body["server"]["listen"]["address"], "127.0.0.1:32276");
assert_eq!(body["server"]["storage"]["root"], "/srv/fabro");
assert_eq!(body["server"]["scheduler"]["max_concurrent_runs"], 9);
assert_eq!(body["server"]["auth"]["methods"][0], "dev-token");
assert_eq!(body["server"]["auth"]["methods"][1], "github");
assert_eq!(
body["server"]["auth"]["github"]["allowed_usernames"][0],
"alice"
);
assert_eq!(
body["server"]["integrations"]["github"]["client_id"],
"Iv1.abcdef"
);
assert!(body.get("features").is_none());
assert!(body.get("cli").is_none());
assert!(body.get("run").is_none());
}