2026-04-22 03:34:06 +08:00
|
|
|
import { create } from 'zustand';
|
2026-06-30 04:47:52 -04:00
|
|
|
import { devtools, persist } from 'zustand/middleware';
|
2026-04-22 03:34:06 +08:00
|
|
|
import type { Todo } from '@opencode-ai/sdk/v2/client';
|
2026-07-21 20:52:20 +03:00
|
|
|
import { getRuntimeKey } from '@/lib/runtime-switch';
|
|
|
|
|
import { normalizePath } from '@/lib/pathNormalization';
|
2026-06-30 04:47:52 -04:00
|
|
|
import { createDeferredSafeJSONStorage } from './utils/safeStorage';
|
2026-04-22 03:34:06 +08:00
|
|
|
|
|
|
|
|
const MAX_SESSIONS = 50;
|
|
|
|
|
|
|
|
|
|
interface SessionTodosRecord {
|
|
|
|
|
todos: Todo[];
|
|
|
|
|
touchedAt: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface TodosPersistState {
|
|
|
|
|
sessions: Record<string, SessionTodosRecord>;
|
2026-07-21 20:52:20 +03:00
|
|
|
setSessionTodos: (directory: string, sessionId: string, todos: Todo[] | undefined) => void;
|
|
|
|
|
getSessionTodos: (directory: string, sessionId: string) => Todo[] | undefined;
|
|
|
|
|
clearSessionTodos: (runtimeKey: string, directory: string, sessionId: string) => void;
|
2026-04-22 03:34:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-21 20:52:20 +03:00
|
|
|
export const getTodosPersistenceKey = (runtimeKey: string, directory: string, sessionId: string): string =>
|
|
|
|
|
JSON.stringify([runtimeKey, normalizePath(directory), sessionId]);
|
|
|
|
|
|
|
|
|
|
const getCurrentSessionKey = (directory: string, sessionId: string): string | null => {
|
|
|
|
|
if (!directory || !sessionId) return null;
|
|
|
|
|
return getTodosPersistenceKey(getRuntimeKey(), directory, sessionId);
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-22 03:34:06 +08:00
|
|
|
const evictOldest = (sessions: Record<string, SessionTodosRecord>): Record<string, SessionTodosRecord> => {
|
|
|
|
|
const ids = Object.keys(sessions);
|
|
|
|
|
if (ids.length <= MAX_SESSIONS) return sessions;
|
|
|
|
|
|
|
|
|
|
const sorted = ids
|
|
|
|
|
.map((id) => [id, sessions[id].touchedAt] as const)
|
|
|
|
|
.sort((a, b) => a[1] - b[1]);
|
|
|
|
|
const drop = sorted.slice(0, ids.length - MAX_SESSIONS).map(([id]) => id);
|
|
|
|
|
const next = { ...sessions };
|
|
|
|
|
for (const id of drop) delete next[id];
|
|
|
|
|
return next;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useTodosPersistStore = create<TodosPersistState>()(
|
|
|
|
|
devtools(
|
|
|
|
|
persist(
|
|
|
|
|
(set, get) => ({
|
|
|
|
|
sessions: {},
|
2026-07-21 20:52:20 +03:00
|
|
|
setSessionTodos: (directory, sessionId, todos) => {
|
|
|
|
|
const key = getCurrentSessionKey(directory, sessionId);
|
|
|
|
|
if (!key) return;
|
2026-04-22 03:34:06 +08:00
|
|
|
set((state) => {
|
|
|
|
|
const next = { ...state.sessions };
|
|
|
|
|
if (!todos || todos.length === 0) {
|
2026-07-21 20:52:20 +03:00
|
|
|
if (!(key in next)) return state;
|
|
|
|
|
delete next[key];
|
2026-04-22 03:34:06 +08:00
|
|
|
return { sessions: next };
|
|
|
|
|
}
|
2026-07-21 20:52:20 +03:00
|
|
|
next[key] = { todos, touchedAt: Date.now() };
|
2026-04-22 03:34:06 +08:00
|
|
|
return { sessions: evictOldest(next) };
|
|
|
|
|
});
|
|
|
|
|
},
|
2026-07-21 20:52:20 +03:00
|
|
|
getSessionTodos: (directory, sessionId) => {
|
|
|
|
|
const key = getCurrentSessionKey(directory, sessionId);
|
|
|
|
|
return key ? get().sessions[key]?.todos : undefined;
|
|
|
|
|
},
|
|
|
|
|
clearSessionTodos: (runtimeKey, directory, sessionId) => {
|
|
|
|
|
if (!runtimeKey || !directory || !sessionId) return;
|
|
|
|
|
const key = getTodosPersistenceKey(runtimeKey, directory, sessionId);
|
|
|
|
|
set((state) => {
|
|
|
|
|
if (!(key in state.sessions)) return state;
|
|
|
|
|
const sessions = { ...state.sessions };
|
|
|
|
|
delete sessions[key];
|
|
|
|
|
return { sessions };
|
|
|
|
|
});
|
2026-04-22 03:34:06 +08:00
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
{
|
|
|
|
|
name: 'openchamber-session-todos',
|
2026-07-21 20:52:20 +03:00
|
|
|
version: 2,
|
2026-06-30 04:47:52 -04:00
|
|
|
storage: createDeferredSafeJSONStorage(),
|
2026-04-22 03:34:06 +08:00
|
|
|
partialize: (state) => ({ sessions: state.sessions }),
|
2026-07-21 20:52:20 +03:00
|
|
|
migrate: () => ({ sessions: {} }),
|
2026-04-22 03:34:06 +08:00
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
{ name: 'TodosPersistStore' },
|
|
|
|
|
),
|
|
|
|
|
);
|