fix(api): provider-chain GET preserves disabled entries (H2 code-review)

The GET response used to surface the resolveProviderChain()-filtered
list, which DROPS disabled entries upstream. The Settings UI then
seeded `enabled: true` for every returned row, so disabling an entry
made it disappear on next refetch and lose its toggle position —
the toggle was functionally a "remove from chain" button.

Changed GET to read the *persisted* chain via parseProviderChain() so
disabled rows still round-trip with their flag. The active-provider
summary still uses resolveProviderChain so the displayed "Active: …"
line continues to reflect the actual runner ordering (enabled-only).
The Settings UI seeds entries with the wire-supplied `enabled` value
instead of always-true, so re-enabling becomes possible without a
page reload.

4 unit tests pin the GET shape (persisted-chain ordering, disabled
entries surfaced, cached-active overlay, default-chain fallback).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-10 03:14:29 +02:00
1 parent 6863ecb87d
commit 5f7b9d8652
3 files changed
+126 -17

No files matched your search

+18 -5
View File
@@ -83,7 +83,11 @@ interface ChainEntry {
interface ProviderChainData {
activeProvider: ProviderType | null;
cachedActiveProvider: ProviderType | null;
configuredChain: { providerType: ProviderType; available: boolean }[];
configuredChain: {
providerType: ProviderType;
enabled: boolean;
available: boolean;
}[];
}
const DEFAULT_CHAIN: readonly ChainEntry[] = [
@@ -1257,7 +1261,11 @@ function FallbackChainCard({
selected,
onSelect,
}: {
chain: { providerType: ProviderType; available: boolean }[];
chain: {
providerType: ProviderType;
enabled: boolean;
available: boolean;
}[];
selected: ProviderType;
onSelect: (next: ProviderType) => void;
}) {
@@ -1267,17 +1275,22 @@ function FallbackChainCard({
// Local working copy. Server-confirmed values arrive via `chain`
// prop; we keep our own state so the user can shuffle multiple rows
// before clicking "Save chain order".
const seededKey = chain.map((c) => c.providerType).join(",");
// v1.4.16 phase D reconcile (code-review H2) — `enabled` from the
// wire is now the canonical state. The GET endpoint surfaces the
// raw persisted chain so a disabled entry survives the round-trip.
const seededKey = chain
.map((c) => `${c.providerType}:${c.enabled ? 1 : 0}`)
.join(",");
const [seeded, setSeeded] = useState<string | null>(null);
const [entries, setEntries] = useState<ChainEntry[]>(() =>
chain.map((c) => ({ providerType: c.providerType, enabled: true })),
chain.map((c) => ({ providerType: c.providerType, enabled: c.enabled })),
);
if (seededKey !== seeded) {
setSeeded(seededKey);
setEntries(
chain.map((c) => ({
providerType: c.providerType,
enabled: true,
enabled: c.enabled,
})),
);
}