Create a Zustand store backed by a mock/real API client switched by VITE_MOCK, with auth-token and persisted boards.
Trigger when the user asks to:
Also applies to mentions of useAuthStore, useBoardStore, useAiStore, ApiClient, mockClient, realClient.
The web-app has three Zustand stores (auth, board, ai) and a single api facade that picks mockClient vs realClient at module load time based on import.meta.env.VITE_MOCK. The facade is typed against ApiClient so adding endpoints is a one-line change on both clients.
The auth store holds the token that useYjs reads via useAuthStore(s => s.token). Calling setToken triggers a y-websocket reconnect - this is intentional.
Canonical files: stores/auth.store.ts, stores/board.store.ts, stores/ai.store.ts, api/index.ts, api/client.interface.ts, api/mock-client.ts, api/real-client.ts.
When adding a new endpoint (example: duplicateBoard):
When adding persistence, use zustand/middleware persist and partialize to whitelist durable fields only.
import { mockClient } from './mock-client';
import { realClient } from './real-client';
const isMock = import.meta.env.VITE_MOCK === 'true';
export const api = isMock ? mockClient : realClient;
See auth.store.ts for the canonical example. Actions call api.* and update local state on success. Persist via zustand/middleware whitelisting only durable fields.
Mock clients return plausible shapes matching the real API. For AI, use a MOCK_RESPONSES dictionary keyed by prompt with a DEFAULT_RESPONSE fallback (ai.store.ts pattern).