feat: add localStorage persistence to focus store

Wraps the focus store with zustand/middleware persist to maintain
queue state across page refreshes and app restarts.

Persists:
- current: The currently focused item
- queue: Remaining items in the work queue

Not persisted (transient state):
- isLoading: Reset on mount
- error: Reset on mount

Storage key: "mc-focus-store"

This prevents losing your place in the queue when the app restarts
or the page refreshes during development.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
teernisse
2026-02-26 09:55:53 -05:00
parent 7120323295
commit 6c04c2efe7

View File

@@ -6,6 +6,7 @@
*/
import { create } from "zustand";
import { persist } from "zustand/middleware";
import type { FocusAction, FocusItem } from "@/lib/types";
export interface FocusState {
@@ -34,72 +35,83 @@ export interface FocusState {
setError: (error: string | null) => void;
}
export const useFocusStore = create<FocusState>((set, get) => ({
current: null,
queue: [],
isLoading: false,
error: null,
setItems: (items) => {
const [first, ...rest] = items;
set({
current: first ?? null,
queue: rest,
export const useFocusStore = create<FocusState>()(
persist(
(set, get) => ({
current: null,
queue: [],
isLoading: false,
error: null,
});
},
act: (action, _reason) => {
const { current, queue } = get();
const [next, ...rest] = queue;
setItems: (items) => {
const [first, ...rest] = items;
set({
current: first ?? null,
queue: rest,
isLoading: false,
error: null,
});
},
// Log the decision (will be wired to backend decision log in Phase 7)
console.debug("[focus] act:", action, "on:", current?.id);
act: (action, _reason) => {
const { current, queue } = get();
const [next, ...rest] = queue;
const nextItem = next ?? null;
set({
current: nextItem,
queue: rest,
});
// Log the decision (will be wired to backend decision log in Phase 7)
console.debug("[focus] act:", action, "on:", current?.id);
return nextItem;
},
const nextItem = next ?? null;
set({
current: nextItem,
queue: rest,
});
setFocus: (itemId) => {
const { current, queue } = get();
const allItems = current ? [current, ...queue] : [...queue];
const target = allItems.find((item) => item.id === itemId);
return nextItem;
},
if (!target) return;
setFocus: (itemId) => {
const { current, queue } = get();
const allItems = current ? [current, ...queue] : [...queue];
const target = allItems.find((item) => item.id === itemId);
const remaining = allItems.filter((item) => item.id !== itemId);
set({
current: target,
queue: remaining,
});
},
if (!target) return;
reorderQueue: (fromIndex, toIndex) => {
const { queue } = get();
if (
fromIndex < 0 ||
fromIndex >= queue.length ||
toIndex < 0 ||
toIndex >= queue.length ||
fromIndex === toIndex
) {
return;
const remaining = allItems.filter((item) => item.id !== itemId);
set({
current: target,
queue: remaining,
});
},
reorderQueue: (fromIndex, toIndex) => {
const { queue } = get();
if (
fromIndex < 0 ||
fromIndex >= queue.length ||
toIndex < 0 ||
toIndex >= queue.length ||
fromIndex === toIndex
) {
return;
}
const updated = [...queue];
const [moved] = updated.splice(fromIndex, 1);
updated.splice(toIndex, 0, moved);
console.debug("[focus] reorder:", moved.id, "from", fromIndex, "to", toIndex);
set({ queue: updated });
},
setLoading: (loading) => set({ isLoading: loading }),
setError: (error) => set({ error }),
}),
{
name: "mc-focus-store",
partialize: (state) => ({
current: state.current,
queue: state.queue,
}),
}
const updated = [...queue];
const [moved] = updated.splice(fromIndex, 1);
updated.splice(toIndex, 0, moved);
console.debug("[focus] reorder:", moved.id, "from", fromIndex, "to", toIndex);
set({ queue: updated });
},
setLoading: (loading) => set({ isLoading: loading }),
setError: (error) => set({ error }),
}));
)
);