export interface ChatViewPreferences { showThinking: boolean; showTimestamps: boolean; /** Include reasoning, tool summaries, and execution narration in exports. */ includeExecution: boolean; } type ChatViewPreferencePatch = Partial; type StoredChatViewPreferences = Record; export const CHAT_VIEW_PREFERENCES_STORAGE_KEY = 'makelore.chat-view-preferences.v1'; export const DEFAULT_CHAT_VIEW_PREFERENCES: ChatViewPreferences = { showThinking: true, showTimestamps: true, includeExecution: false, }; function browserStorage(): Storage | undefined { try { return typeof window === 'undefined' ? undefined : window.localStorage; } catch { return undefined; } } function readStoredPreferences(storage: Storage | undefined): StoredChatViewPreferences { const preferences = Object.create(null) as StoredChatViewPreferences; if (!storage) return preferences; try { const parsed = JSON.parse(storage.getItem(CHAT_VIEW_PREFERENCES_STORAGE_KEY) ?? '{}'); if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return preferences; for (const [sessionId, candidate] of Object.entries(parsed as Record)) { if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) continue; const record = candidate as Record; preferences[sessionId] = { ...(typeof record.showThinking === 'boolean' ? { showThinking: record.showThinking } : {}), ...(typeof record.showTimestamps === 'boolean' ? { showTimestamps: record.showTimestamps } : {}), ...(typeof record.includeExecution === 'boolean' ? { includeExecution: record.includeExecution } : {}), }; } } catch { return preferences; } return preferences; } export function loadChatViewPreferences( sessionId: string, storage: Storage | undefined = browserStorage(), ): ChatViewPreferences { const candidate = readStoredPreferences(storage)[sessionId]; return { showThinking: typeof candidate?.showThinking === 'boolean' ? candidate.showThinking : DEFAULT_CHAT_VIEW_PREFERENCES.showThinking, showTimestamps: typeof candidate?.showTimestamps === 'boolean' ? candidate.showTimestamps : DEFAULT_CHAT_VIEW_PREFERENCES.showTimestamps, includeExecution: typeof candidate?.includeExecution === 'boolean' ? candidate.includeExecution : DEFAULT_CHAT_VIEW_PREFERENCES.includeExecution, }; } export function saveChatViewPreferences( sessionId: string, patch: ChatViewPreferencePatch, storage: Storage | undefined = browserStorage(), ): ChatViewPreferences { const current = loadChatViewPreferences(sessionId, storage); const next = { showThinking: typeof patch.showThinking === 'boolean' ? patch.showThinking : current.showThinking, showTimestamps: typeof patch.showTimestamps === 'boolean' ? patch.showTimestamps : current.showTimestamps, includeExecution: typeof patch.includeExecution === 'boolean' ? patch.includeExecution : current.includeExecution, }; if (!storage) return next; try { const all = readStoredPreferences(storage); all[sessionId] = next; storage.setItem(CHAT_VIEW_PREFERENCES_STORAGE_KEY, JSON.stringify(all)); } catch { // View preferences are best-effort and must never block the chat UI. } return next; } export function deleteChatViewPreferences( sessionId: string, storage: Storage | undefined = browserStorage(), ): void { if (!storage) return; try { const all = readStoredPreferences(storage); if (!Object.prototype.hasOwnProperty.call(all, sessionId)) return; delete all[sessionId]; if (Object.keys(all).length === 0) { storage.removeItem(CHAT_VIEW_PREFERENCES_STORAGE_KEY); } else { storage.setItem(CHAT_VIEW_PREFERENCES_STORAGE_KEY, JSON.stringify(all)); } } catch { // Host deletion already succeeded; unavailable storage must not undo it. } }