From 1e072346a91f279ac1440f8e2f0503dde27745fd Mon Sep 17 00:00:00 2001 From: Dennis Thiessen Date: Tue, 21 Jul 2026 13:50:00 +0200 Subject: [PATCH] ui: explicit Save button for shadow book settings The shadow book toggle saved on change and the numeric fields on blur, inconsistent with every other admin panel (which stages edits behind a Save button). Stage all four fields in local state and commit them together on Save, with an unsaved-changes hint. This also makes enabling the live-trade toggle a deliberate two-step action rather than an unguarded single click. Co-Authored-By: Claude Fable 5 --- .../components/admin/PerformanceSettings.tsx | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/admin/PerformanceSettings.tsx b/frontend/src/components/admin/PerformanceSettings.tsx index 015c131..563e23f 100644 --- a/frontend/src/components/admin/PerformanceSettings.tsx +++ b/frontend/src/components/admin/PerformanceSettings.tsx @@ -49,6 +49,15 @@ export function PerformanceSettings() { if (window.isLoading || shadow.isLoading || !book) return ; + // Staged edits: nothing about the shadow book persists until Save, matching + // the other admin panels — and making the live-trade toggle deliberate. + const bookDirty = + !!shadow.data && + (book.enabled !== shadow.data.enabled || + book.capacity !== shadow.data.capacity || + book.risk_pct !== shadow.data.risk_pct || + book.start_equity !== shadow.data.start_equity); + return (
@@ -106,14 +115,15 @@ export function PerformanceSettings() { saveBook.mutate({ enabled: e.target.checked })} + onChange={(e) => setBook({ ...book, enabled: e.target.checked })} className="mt-0.5" /> Shadow book enabled - Starts opening real paper positions automatically on the next near-close scan. Verify its - first selections match a backtest of that day's cross-section before trusting the curve. + Starts opening real paper positions automatically on the next near-close scan — takes + effect when you Save. Verify its first selections match the top-ranked qualified setups + before trusting the curve. @@ -127,7 +137,6 @@ export function PerformanceSettings() { max={100} value={book.capacity} onChange={(e) => setBook({ ...book, capacity: Number(e.target.value) })} - onBlur={() => saveBook.mutate({ capacity: book.capacity })} className="input-glass w-full px-3 py-2 text-sm" /> @@ -140,7 +149,6 @@ export function PerformanceSettings() { max={10} value={book.risk_pct} onChange={(e) => setBook({ ...book, risk_pct: Number(e.target.value) })} - onBlur={() => saveBook.mutate({ risk_pct: book.risk_pct })} className="input-glass w-full px-3 py-2 text-sm" /> @@ -152,7 +160,6 @@ export function PerformanceSettings() { step={1000} value={book.start_equity} onChange={(e) => setBook({ ...book, start_equity: Number(e.target.value) })} - onBlur={() => saveBook.mutate({ start_equity: book.start_equity })} className="input-glass w-full px-3 py-2 text-sm" /> @@ -161,6 +168,20 @@ export function PerformanceSettings() { Defaults match the validated configuration: 10 positions, 1% fixed-fractional risk. Start equity is only a sizing base — the books are compared in R-multiples, not currency.

+ +
+ + {bookDirty && !saveBook.isPending && ( + Unsaved changes + )} +
);