11import { createLogger } from '@sim/logger'
22import { create } from 'zustand'
3- import { devtools , persist } from 'zustand/middleware'
3+ import { createJSONStorage , devtools , persist } from 'zustand/middleware'
44import { redactApiKeys } from '@/lib/core/security/redaction'
55import type { NormalizedBlockOutput } from '@/executor/types'
66import { useExecutionStore } from '@/stores/execution'
77import { useNotificationStore } from '@/stores/notifications'
88import { useGeneralStore } from '@/stores/settings/general'
9+ import { indexedDBStorage } from '@/stores/terminal/console/storage'
910import type { ConsoleEntry , ConsoleStore , ConsoleUpdate } from '@/stores/terminal/console/types'
1011
1112const logger = createLogger ( 'TerminalConsoleStore' )
1213
1314/**
14- * Updates a NormalizedBlockOutput with new content
15+ * Maximum number of console entries to keep per workflow.
16+ * Keeps the stored data size reasonable and improves performance.
1517 */
18+ const MAX_ENTRIES_PER_WORKFLOW = 500
19+
1620const updateBlockOutput = (
1721 existingOutput : NormalizedBlockOutput | undefined ,
1822 contentUpdate : string
@@ -23,9 +27,6 @@ const updateBlockOutput = (
2327 }
2428}
2529
26- /**
27- * Checks if output represents a streaming object that should be skipped
28- */
2930const isStreamingOutput = ( output : any ) : boolean => {
3031 if ( typeof ReadableStream !== 'undefined' && output instanceof ReadableStream ) {
3132 return true
@@ -44,9 +45,6 @@ const isStreamingOutput = (output: any): boolean => {
4445 )
4546}
4647
47- /**
48- * Checks if entry should be skipped to prevent duplicates
49- */
5048const shouldSkipEntry = ( output : any ) : boolean => {
5149 if ( typeof output !== 'object' || ! output ) {
5250 return false
@@ -69,6 +67,9 @@ export const useTerminalConsoleStore = create<ConsoleStore>()(
6967 ( set , get ) => ( {
7068 entries : [ ] ,
7169 isOpen : false ,
70+ _hasHydrated : false ,
71+
72+ setHasHydrated : ( hasHydrated ) => set ( { _hasHydrated : hasHydrated } ) ,
7273
7374 addConsole : ( entry : Omit < ConsoleEntry , 'id' | 'timestamp' > ) => {
7475 set ( ( state ) => {
@@ -94,7 +95,15 @@ export const useTerminalConsoleStore = create<ConsoleStore>()(
9495 timestamp : new Date ( ) . toISOString ( ) ,
9596 }
9697
97- return { entries : [ newEntry , ...state . entries ] }
98+ const newEntries = [ newEntry , ...state . entries ]
99+ const workflowCounts = new Map < string , number > ( )
100+ const trimmedEntries = newEntries . filter ( ( entry ) => {
101+ const count = workflowCounts . get ( entry . workflowId ) || 0
102+ if ( count >= MAX_ENTRIES_PER_WORKFLOW ) return false
103+ workflowCounts . set ( entry . workflowId , count + 1 )
104+ return true
105+ } )
106+ return { entries : trimmedEntries }
98107 } )
99108
100109 const newEntry = get ( ) . entries [ 0 ]
@@ -130,10 +139,6 @@ export const useTerminalConsoleStore = create<ConsoleStore>()(
130139 return newEntry
131140 } ,
132141
133- /**
134- * Clears console entries for a specific workflow and clears the run path
135- * @param workflowId - The workflow ID to clear entries for
136- */
137142 clearWorkflowConsole : ( workflowId : string ) => {
138143 set ( ( state ) => ( {
139144 entries : state . entries . filter ( ( entry ) => entry . workflowId !== workflowId ) ,
@@ -148,9 +153,6 @@ export const useTerminalConsoleStore = create<ConsoleStore>()(
148153 return
149154 }
150155
151- /**
152- * Formats a value for CSV export
153- */
154156 const formatCSVValue = ( value : any ) : string => {
155157 if ( value === null || value === undefined ) {
156158 return ''
@@ -297,7 +299,35 @@ export const useTerminalConsoleStore = create<ConsoleStore>()(
297299 } ) ,
298300 {
299301 name : 'terminal-console-store' ,
302+ storage : createJSONStorage ( ( ) => indexedDBStorage ) ,
303+ partialize : ( state ) => ( {
304+ entries : state . entries ,
305+ isOpen : state . isOpen ,
306+ } ) ,
307+ onRehydrateStorage : ( ) => ( _state , error ) => {
308+ if ( error ) {
309+ logger . error ( 'Failed to rehydrate console store' , { error } )
310+ }
311+ } ,
312+ merge : ( persistedState , currentState ) => {
313+ const persisted = persistedState as Partial < ConsoleStore > | undefined
314+ return {
315+ ...currentState ,
316+ entries : persisted ?. entries ?? currentState . entries ,
317+ isOpen : persisted ?. isOpen ?? currentState . isOpen ,
318+ }
319+ } ,
300320 }
301321 )
302322 )
303323)
324+
325+ if ( typeof window !== 'undefined' ) {
326+ useTerminalConsoleStore . persist . onFinishHydration ( ( ) => {
327+ useTerminalConsoleStore . setState ( { _hasHydrated : true } )
328+ } )
329+
330+ if ( useTerminalConsoleStore . persist . hasHydrated ( ) ) {
331+ useTerminalConsoleStore . setState ( { _hasHydrated : true } )
332+ }
333+ }
0 commit comments