T2: Zustand + themeStore + wizardStore
T2: Zustand + themeStore + wizardStore
Section titled “T2: Zustand + themeStore + wizardStore”Phase: BCG Phase 1 Depends on: T1 (safety-net тесты зелёные) Effort: ~2h
Context
Section titled “Context”Первый шаг миграции state с useState в App.tsx на Zustand. Начинаем с самых простых стейтов — theme и wizard navigation. Цель — положить seam, не ломая App.tsx.
Читать перед стартом:
app/frontend/src/App.tsx(487 стр) — строки сtheme,step,showOnboarding,importingDraftarchive/2026-04-23-bcg-planning-docs/BCG_plan.md§1.2.1-1.2.3
- Установить Zustand
- Создать
themeStore(theme + localStorage sync) - Создать
wizardStore(step + onboarding + importingDraft) - В
App.tsxзаменить соответствующийuseStateна эти stores - Остальной state (
useAnalysis,useProjectManager,useDraftPersistence) НЕ трогать
1. Установить Zustand
Section titled “1. Установить Zustand”cd app/frontendnpm i zustandПроверить package.json: "zustand": "^5.x" в dependencies (НЕ devDependencies).
2. Создать src/stores/themeStore.ts
Section titled “2. Создать src/stores/themeStore.ts”Перенести логику theme из App.tsx:
- state:
theme: "light" | "dark" - action:
setTheme(theme),toggleTheme() - persist: localStorage ключ
ab-test:theme - initial: читать localStorage, fallback
"light" - side effect: при смене — применять
document.documentElement.dataset.theme = theme
Интерфейс:
export type Theme = "light" | "dark";export interface ThemeState { theme: Theme; setTheme: (theme: Theme) => void; toggleTheme: () => void;}export const useThemeStore: () => ThemeState;3. Создать src/stores/wizardStore.ts
Section titled “3. Создать src/stores/wizardStore.ts”Перенести:
- state:
step: number,showOnboarding: boolean,importingDraft: boolean - actions:
setStep,setShowOnboarding,setImportingDraft,openWizard()(сброс step в 0, закрытие onboarding)
Onboarding persist: localStorage ключ ab-test:onboarding-seen (boolean). Если true → showOnboarding = false при старте.
4. Тесты
Section titled “4. Тесты”Создать:
src/stores/themeStore.test.ts— set/toggle, localStorage persist, initial из localStoragesrc/stores/wizardStore.test.ts— setStep, openWizard сброс, onboarding persist
Использовать beforeEach(() => { localStorage.clear(); useThemeStore.setState(initialState); }) для изоляции.
5. Интегрировать в App.tsx
Section titled “5. Интегрировать в App.tsx”- Удалить
useStateдля theme/step/showOnboarding/importingDraft - Заменить на
const { theme, setTheme } = useThemeStore()и т.д. - Проверить: theme applies к
<html data-theme>, onboarding показывается первый раз, wizard navigation работает
6. Verify
Section titled “6. Verify”cd app/frontendnpm ls zustandnpx vitest runnpx tsc --noEmitnpm run buildВсе команды без ошибок. Ручная проверка: npm run dev → toggle theme, onboarding first-visit, step navigation.
Done When
Section titled “Done When”-
zustandвdependencies -
src/stores/themeStore.ts+.test.ts(≥ 3 теста) -
src/stores/wizardStore.ts+.test.ts(≥ 3 теста) -
App.tsxиспользует stores вместо useState для theme/step/onboarding/importingDraft -
npx vitest runиnpm run buildзелёные - Ручная UI-проверка пройдена
Constraints
Section titled “Constraints”- НЕ трогать
useAnalysis,useProjectManager,useDraftPersistenceв T2 - НЕ создавать
analysisStore/projectStore/draftStore— это T3-T4 App.tsxостаётся функциональным, НО размер ещё не нужно резать до 120 стр — это T5