@@ -21,6 +21,7 @@ const preSelectedPrompt = signal(null);
2121const activeTab = signal ( 'runs' ) ; // runs | configure | history
2222const toastMessage = signal ( null ) ; // { text, type: 'error' | 'info' }
2323const sidebarOpen = signal ( false ) ; // mobile sidebar drawer
24+ const historyRuns = signal ( [ ] ) ; // persisted runs from SQLite (survives restarts)
2425
2526const 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
358366function 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 }) {
488497function 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
15821593function 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