fix: PatternLab save UX — color-coded toasts + meaningful error feedback
- handleSave now shows a red toast "Lancez d'abord un backtest" instead of silently returning when activeRun is null (was completely invisible to user) - All toasts now color-coded: green (emerald) for success, red for errors - list_runs now includes context_snapshot so market data table shows when loading a historical run from the right panel Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -168,7 +168,7 @@ def list_runs():
|
||||
conn = get_conn()
|
||||
rows = conn.execute("""SELECT id, preset_id, theme, analysis_date, horizon_days,
|
||||
assets, status, created_at, evaluated_at,
|
||||
outcome, ai_result
|
||||
outcome, ai_result, context_snapshot
|
||||
FROM backtest_lab_runs ORDER BY created_at DESC LIMIT 100""").fetchall()
|
||||
conn.close()
|
||||
return [_row_to_dict(r) for r in rows]
|
||||
|
||||
@@ -296,7 +296,7 @@ export default function PatternLab() {
|
||||
// ── Current run state (events mode)
|
||||
const [activeRun, setActiveRun] = useState<any | null>(null)
|
||||
const [savedIdx, setSavedIdx] = useState<Set<number>>(new Set())
|
||||
const [toast, setToast] = useState<string | null>(null)
|
||||
const [toast, setToast] = useState<{ msg: string; type: 'ok' | 'err' } | null>(null)
|
||||
const [matchResults, setMatchResults] = useState<Record<string, MatchResult>>({})
|
||||
const [matchLoading, setMatchLoading] = useState<Record<string, boolean>>({})
|
||||
|
||||
@@ -307,9 +307,9 @@ export default function PatternLab() {
|
||||
return true
|
||||
}), [search, yearFilter, catFilter])
|
||||
|
||||
const showToast = (msg: string) => {
|
||||
setToast(msg)
|
||||
setTimeout(() => setToast(null), 3000)
|
||||
const showToast = (msg: string, type: 'ok' | 'err' = 'ok') => {
|
||||
setToast({ msg, type })
|
||||
setTimeout(() => setToast(null), 4000)
|
||||
}
|
||||
|
||||
const handleSelectPreset = (p: Preset) => {
|
||||
@@ -344,7 +344,7 @@ export default function PatternLab() {
|
||||
const res = await discoverEvents({ query: discoverQuery.trim(), n: 6 })
|
||||
setDiscoverResults(res.events)
|
||||
} catch (e: any) {
|
||||
showToast(`Discover failed: ${e?.response?.data?.detail ?? e?.message}`)
|
||||
showToast(`Discover failed: ${e?.response?.data?.detail ?? e?.message}`, 'err')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,7 +363,7 @@ export default function PatternLab() {
|
||||
setActiveRun(result)
|
||||
setSavedIdx(new Set())
|
||||
} catch (e: any) {
|
||||
showToast(`Error: ${e?.response?.data?.detail ?? e?.message ?? 'unknown'}`)
|
||||
showToast(`Error: ${e?.response?.data?.detail ?? e?.message ?? 'unknown'}`, 'err')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,7 +373,7 @@ export default function PatternLab() {
|
||||
const result = await evaluateRun(activeRun.run_id)
|
||||
setActiveRun((prev: any) => ({ ...prev, outcome: result.outcomes }))
|
||||
} catch (e: any) {
|
||||
showToast(`Error: ${e?.response?.data?.detail ?? e?.message ?? 'unknown'}`)
|
||||
showToast(`Error: ${e?.response?.data?.detail ?? e?.message ?? 'unknown'}`, 'err')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,7 +387,7 @@ export default function PatternLab() {
|
||||
const result = await findMatching({ run_id: activeRun.run_id, pattern_index: idx })
|
||||
setMatchResults(prev => ({ ...prev, [key]: result }))
|
||||
} catch (e: any) {
|
||||
showToast(`Find matching failed: ${e?.response?.data?.detail ?? e?.message}`)
|
||||
showToast(`Find matching failed: ${e?.response?.data?.detail ?? e?.message}`, 'err')
|
||||
} finally {
|
||||
setMatchLoading(prev => ({ ...prev, [key]: false }))
|
||||
}
|
||||
@@ -399,7 +399,10 @@ export default function PatternLab() {
|
||||
target_id?: string,
|
||||
regime_tag?: string,
|
||||
) => {
|
||||
if (!activeRun?.run_id) return
|
||||
if (!activeRun?.run_id) {
|
||||
showToast('Lancez d\'abord un backtest (bouton Run)', 'err')
|
||||
return
|
||||
}
|
||||
const pat = activeRun.ai_result?.patterns?.[idx]
|
||||
if (!pat) return
|
||||
try {
|
||||
@@ -415,9 +418,9 @@ export default function PatternLab() {
|
||||
})
|
||||
setSavedIdx(prev => new Set([...prev, idx]))
|
||||
const actionLabel = action === 'instance' ? 'merged as instance' : action === 'counter' ? 'saved as counter-scenario' : 'saved'
|
||||
showToast(`"${pat.name}" ${actionLabel}`)
|
||||
showToast(`"${pat.name}" ${actionLabel}`, 'ok')
|
||||
} catch (e: any) {
|
||||
showToast(`Save failed: ${e?.response?.data?.detail ?? e?.message}`)
|
||||
showToast(`Save failed: ${e?.response?.data?.detail ?? e?.message}`, 'err')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,7 +445,7 @@ export default function PatternLab() {
|
||||
const result = await evaluateInst(instRun.run_id)
|
||||
setInstRun((prev: any) => ({ ...prev, outcome: result.outcomes }))
|
||||
} catch (e: any) {
|
||||
showToast(`Error: ${e?.response?.data?.detail ?? e?.message ?? 'unknown'}`)
|
||||
showToast(`Error: ${e?.response?.data?.detail ?? e?.message ?? 'unknown'}`, 'err')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,9 +456,9 @@ export default function PatternLab() {
|
||||
try {
|
||||
await savePattern({ run_id: instRun.run_id, pattern_index: idx, name: pat.name, category: pat.category, signal_direction: pat.signal_direction })
|
||||
setInstSavedIdx(prev => new Set([...prev, idx]))
|
||||
showToast(`"${pat.name}" saved to Pattern Library`)
|
||||
showToast(`"${pat.name}" saved to Pattern Library`, 'ok')
|
||||
} catch (e: any) {
|
||||
showToast(`Save failed: ${e?.response?.data?.detail ?? e?.message}`)
|
||||
showToast(`Save failed: ${e?.response?.data?.detail ?? e?.message}`, 'err')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1089,10 +1092,10 @@ export default function PatternLab() {
|
||||
setEditAssets((Array.isArray(run.assets) ? run.assets : []).join(', '))
|
||||
setEditHint(run.theme_hint ?? '')
|
||||
setActiveRun({
|
||||
run_id: run.id,
|
||||
context: run.context_snapshot,
|
||||
run_id: run.id,
|
||||
context: run.context_snapshot,
|
||||
ai_result: run.ai_result,
|
||||
outcome: run.outcome,
|
||||
outcome: run.outcome,
|
||||
})
|
||||
setSavedIdx(new Set())
|
||||
setMatchResults({})
|
||||
@@ -1135,8 +1138,13 @@ export default function PatternLab() {
|
||||
|
||||
{/* ── Toast ── */}
|
||||
{toast && (
|
||||
<div className="fixed bottom-5 left-1/2 -translate-x-1/2 bg-slate-800 border border-slate-600 rounded-lg px-4 py-2 text-sm text-slate-200 shadow-xl z-50">
|
||||
{toast}
|
||||
<div className={clsx(
|
||||
'fixed bottom-5 left-1/2 -translate-x-1/2 rounded-lg px-4 py-2 text-sm shadow-xl z-50 border',
|
||||
toast.type === 'ok'
|
||||
? 'bg-emerald-900/90 border-emerald-600 text-emerald-100'
|
||||
: 'bg-red-900/90 border-red-600 text-red-100'
|
||||
)}>
|
||||
{toast.msg}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user