Skip to content

Commit 04ae041

Browse files
Kasper Jungeclaude
authored andcommitted
fix: persist run history across server restarts via SQLite store
The History tab previously only showed in-memory runs from the current server session. Now it fetches persisted runs from the SQLite store via a new GET /api/history/runs endpoint, so completed runs survive restarts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0a114e3 commit 04ae041

2 files changed

Lines changed: 36 additions & 2 deletions

File tree

src/ralphify/ui/api/runs.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,12 @@ async def update_settings(run_id: str, body: RunSettingsUpdate, mgr: RunManager
167167
return _run_response(managed)
168168

169169

170+
@router.get("/history/runs")
171+
async def list_history_runs(store: Store = Depends(_get_store)) -> list[dict]:
172+
"""Return all persisted runs from the SQLite store (survives server restarts)."""
173+
return await store.list_runs()
174+
175+
170176
@router.get("/runs/{run_id}/iterations")
171177
async def get_iterations(run_id: str, store: Store = Depends(_get_store)) -> list[dict]:
172178
"""Return persisted iteration data with check results for a run."""

src/ralphify/ui/static/dashboard.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const preSelectedPrompt = signal(null);
2121
const activeTab = signal('runs'); // runs | configure | history
2222
const toastMessage = signal(null); // { text, type: 'error' | 'info' }
2323
const sidebarOpen = signal(false); // mobile sidebar drawer
24+
const historyRuns = signal([]); // persisted runs from SQLite (survives restarts)
2425

2526
const activeRun = computed(() => runs.value.find(r => r.run_id === activeRunId.value));
2627

@@ -353,12 +354,20 @@ async function loadRuns() {
353354
} catch (e) { /* server may not be ready */ }
354355
}
355356

357+
async function loadHistoryRuns() {
358+
try {
359+
const data = await api('GET', '/history/runs');
360+
historyRuns.value = data || [];
361+
} catch { /* endpoint may not exist on older servers */ }
362+
}
363+
356364
// ── Components ─────────────────────────────────────────────────────
357365

358366
function App() {
359367
useEffect(() => {
360368
connectWs();
361369
loadRuns();
370+
loadHistoryRuns();
362371
return () => { if (ws) ws.close(); };
363372
}, []);
364373

@@ -488,7 +497,9 @@ function TabIcon({ tab, size = 16 }) {
488497
function Main() {
489498
const run = activeRun.value;
490499
const activeCount = runs.value.filter(r => ['running', 'paused', 'pending'].includes(r.status)).length;
491-
const historyCount = runs.value.filter(r => ['completed', 'stopped', 'failed'].includes(r.status)).length;
500+
const inMemoryHistoryIds = new Set(runs.value.filter(r => ['completed', 'stopped', 'failed'].includes(r.status)).map(r => r.run_id));
501+
const persistedHistoryCount = historyRuns.value.filter(r => !inMemoryHistoryIds.has(r.run_id) && ['completed', 'stopped', 'failed'].includes(r.status)).length;
502+
const historyCount = inMemoryHistoryIds.size + persistedHistoryCount;
492503

493504
return html`
494505
<div class="main">
@@ -1580,7 +1591,24 @@ function PrimCreateForm({ kind, meta, onBack, onCreated }) {
15801591
// ── History view ───────────────────────────────────────────────────
15811592

15821593
function HistoryView() {
1583-
const completedRuns = runs.value.filter(r => ['completed', 'stopped', 'failed'].includes(r.status));
1594+
// Merge in-memory runs with persisted history from SQLite (dedup by run_id).
1595+
// Persisted runs use store schema field names — normalise them.
1596+
const inMemory = runs.value.filter(r => ['completed', 'stopped', 'failed'].includes(r.status));
1597+
const inMemoryIds = new Set(inMemory.map(r => r.run_id));
1598+
const persisted = historyRuns.value
1599+
.filter(r => !inMemoryIds.has(r.run_id) && ['completed', 'stopped', 'failed'].includes(r.status))
1600+
.map(r => ({
1601+
run_id: r.run_id,
1602+
status: r.status,
1603+
started_at: r.started_at,
1604+
iteration: r.iterations || 0,
1605+
completed: r.completed || 0,
1606+
failed: r.failed || 0,
1607+
timed_out: r.timed_out || 0,
1608+
prompt_name: r.prompt_file ? r.prompt_file.split('/').slice(-2, -1)[0] : null,
1609+
}));
1610+
const completedRuns = [...inMemory, ...persisted]
1611+
.sort((a, b) => (b.started_at || '').localeCompare(a.started_at || ''));
15841612

15851613
if (completedRuns.length === 0) {
15861614
return html`

0 commit comments

Comments
 (0)