fabro/lib/crates/fabro-api/tests/server_settings_round_trip.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

79 lines
2.1 KiB
Rust

use std::any::{TypeId, type_name};
use fabro_api::types::{
LogDestination as ApiLogDestination, ObjectStoreSettings as ApiObjectStoreSettings,
ServerNamespace as ApiServerNamespace, ServerSettings as ApiServerSettings,
};
use fabro_config::ServerSettingsBuilder;
use fabro_types::ServerSettings;
use fabro_types::settings::ServerNamespace;
use fabro_types::settings::server::{LogDestination, ObjectStoreSettings};
#[test]
fn server_settings_family_reuses_domain_types() {
assert_same_type::<ApiServerSettings, ServerSettings>();
assert_same_type::<ApiServerNamespace, ServerNamespace>();
assert_same_type::<ApiObjectStoreSettings, ObjectStoreSettings>();
assert_same_type::<ApiLogDestination, LogDestination>();
}
#[test]
fn server_settings_json_matches_openapi_shape() {
let settings = ServerSettingsBuilder::from_toml(
r#"
_version = 1
[server.listen]
type = "tcp"
address = "127.0.0.1:32276"
[server.api]
url = "https://api.fabro.example.com"
[server.web]
enabled = true
url = "https://fabro.example.com"
[server.auth]
methods = ["dev-token", "github"]
[server.auth.github]
allowed_usernames = ["alice"]
[server.storage]
root = "/srv/fabro"
[server.logging]
destination = "stdout"
[server.integrations.github]
enabled = true
strategy = "app"
app_id = "12345"
client_id = "Iv1.abcdef"
slug = "fabro-dev"
"#,
)
.expect("settings should resolve");
let json = serde_json::to_value(&settings).expect("server settings should serialize");
assert_eq!(json["server"]["listen"]["type"], "tcp");
assert_eq!(json["server"]["listen"]["address"], "127.0.0.1:32276");
assert_eq!(json["server"]["storage"]["root"], "/srv/fabro");
assert_eq!(json["server"]["logging"]["destination"], "stdout");
assert!(json.get("features").is_none());
let round_trip: ApiServerSettings =
serde_json::from_value(json).expect("server settings should deserialize");
assert_eq!(round_trip, settings);
}
fn assert_same_type<T: 'static, U: 'static>() {
assert_eq!(
TypeId::of::<T>(),
TypeId::of::<U>(),
"{} should be the same type as {}",
type_name::<T>(),
type_name::<U>()
);
}