ui/goose2/ui_improvements/state_management/refactor-progress.md
Goose2 Zustand Refactor Progress
Current Status
Documents
goose2-zustand-state-management-review.mdgoose2-zustand-state-management-improvement-plan.mdphase-1-selector-cleanup.mdphase-2-selector-read-layer.mdphase-3-session-side-effects.mdphase-4-store-boundaries.mdphase-5-session-workflow-failure-policy.mdphase-6-project-store.mdphase-7-persistence.mdphase-8-test-reset-coverage.mdphase-9-optional-immer.mdDecisions
A/B sub-phases.usePersonas() should keep returning personas and isLoading even though its current production consumer only uses command functions.chatSessionOperations.ts functions for backend-aware chat-session workflows. Use patchSession for local-only store updates. For title and project updates, call backend first and patch local state only after success.Phase Status
| Phase | Name | Status | Notes |
|---|---|---|---|
| 1 | Remove whole-store subscriptions | Complete | Replaced broad subscriptions in usePersonas.ts, useChat.ts, Sidebar.tsx, and AppShell.tsx. |
| 2 | Introduce selector-first read layer | Complete | Added initial chatSessionStore, chatStore, agentStore, and projectStore selector helpers. Deferred derived values, store-boundary issues, and component-boundary issues to later phases. |
| 3 | Separate session side effects | Complete | Removed generic updateSession; local patches use patchSession, and title/project persistence uses explicit operations. |
| 4 | Split clearest store boundaries | Pending | Split agentStore UI state, then chatSessionStore UI state. |
| 5 | Decide session workflow failure policy | Pending | Behavior-policy follow-up after Phase 4 clarifies session-store boundaries. |
| 6 | Refactor project store | Pending | Make mutation policy explicit. |
| 7 | Standardize persistence | Pending | Do after boundaries are clearer. |
| 8 | Test reset and coverage | Pending | Dedicated cleanup after boundary changes stabilize. |
| 9 | Optional Immer | Pending | Skip unless update readability still justifies it. |
Known Current-Code Findings To Recheck Before Phase 1
rg "use[A-Za-z0-9]+Store\\(\\)" ui/goose2/srcuseShallow is currently unused in ui/goose2/src.chatSessionStore.updateSession; local-only session changes now use patchSession.projectStore currently owns local cache, API orchestration, state, active selection, and reorder behavior.agentStore currently mixes catalog/provider state with persona editor UI state.Next Step
phase-4-store-boundaries.md.Validation Log
cd ui/goose2 && pnpm exec tsc --noEmit passed.cd ui/goose2 && pnpm test -- useChat usePersonas Sidebar passed.cd ui/goose2 && pnpm exec tsc --noEmit passed.cd ui/goose2 && pnpm exec tsc --noEmit passed.cd ui/goose2 && pnpm exec tsc --noEmit passed.cd ui/goose2 && pnpm exec tsc --noEmit passed.cd ui/goose2 && pnpm exec tsc --noEmit passed.cd ui/goose2 && pnpm exec tsc --noEmit passed.cd ui/goose2 && pnpm test -- useChat usePersonas Sidebar useProviderInventory passed.useProviderInventory / useShallow assessment complete: no code change recommended.rg "use[A-Za-z0-9]+Store\\(\\)" ui/goose2/src found no matches.useShallow scan: rg "useShallow|zustand/shallow" ui/goose2/src found no matches, as expected.cd ui/goose2 && pnpm exec tsc --noEmit passed.cd ui/goose2 && pnpm test -- chatSessionOperations chatSessionStore passed; Vitest ran 109 files / 632 tests.updateSession scan: no chatSessionStore.updateSession action or call sites remain; only explicit updateSessionTitle / updateSessionProject operation/API names remain.Follow-Ups To Revisit Later
usePersonas() should remain both a read hook and command hook. Today AgentsView already reads personas and personasLoading through direct store selectors and only uses usePersonas() for createPersona, updatePersona, deletePersona, and refreshFromDisk. Do not change that contract during Phase 1.sessionStateById record to avoid a complex derived selector, which is still narrower than the whole chatStore; Phase 2 should evaluate whether visible sidebar session items need a dedicated selector/helper.[] from a selector fallback in useChat can trigger React snapshot churn; use module-level constants or derive fallback values outside the selector.useStore((state) => ...) as well as any remaining direct mock patterns.retryLastMessage, buildSkillRetryOptions, and the unused findLastIndex helper.chatSessionStore boundaries, revisit archive/unarchive. They currently remain store actions with API calls and optimistic-without-rollback behavior: local visibility changes immediately, backend failures are logged, and local state is not restored.Phase 2 Audit Notes
chatSessionSelectors.ts with selectSessions, selectActiveSessionId, selectHasHydratedSessions, and selectSessionsLoading.AppShell.tsx and Sidebar.tsx to use those simple field selectors.chatSelectors.ts with selectMessagesBySession and selectSessionStateById.AppShell.tsx and Sidebar.tsx to use those simple field selectors.agentSelectors.ts with selectPersonas and selectPersonasLoading.usePersonas.ts, AgentsView.tsx, and useChatSessionController.ts to use persona domain selectors.selectSelectedProvider to agentSelectors.ts.AppShell.tsx and useProviderSelection.ts to use the selected-provider selector.agentStore boundary work.ProjectState type to exported ProjectStore because the type includes state and actions.projectSelectors.ts with selectProjects.projects field reads in AppShell.tsx, Sidebar.tsx, SkillsView.tsx, SessionHistoryView.tsx, and useChatSessionController.ts.SessionHistoryView.tsx to use existing selectSessions and selectMessagesBySession.useProviderInventory / useShallow assessment:
useProviderInventory reads entries and loading with separate narrow selectors.useShallow would not meaningfully reduce rerenders because consumers should still update when either value changes.selectMessagesBySession, selectSessionStateByIdselectSessions, selectActiveSessionId, selectHasHydratedSessions, selectSessionsLoadingselectPersonas, selectPersonasLoading, selectSelectedProviderselectProjects, selectFetchProjects, selectReorderProjectssessions + activeSessionIdsessions + homeSessionIdprojects + projectIdsessions + messagesBySession + sessionStateById + project idschatStore.activeSessionId and chatSessionStore.activeSessionId duplicate active-session selection. Track for Phase 4 boundary review.agentStore.isLoading may be a legacy/general loading flag now that personas, agents, and providers have specific loading fields. Track for Phase 4 and test-reset cleanup in Phase 8.chatStore.getActiveMessageschatSessionStore.getActiveSessionchatSessionStore.getArchivedSessionsprojectStore.getActiveProjectagentStore.getActiveAgentagentStore.getAgentsByPersonaagentStore.getBuiltinPersonasagentStore.getCustomPersonasgetState()/callback code; prefer selectors or pure helpers for React reads.agentStore mixes personas, providers, active agent, selected provider, and persona editor UI. Track for Phase 4.chatStore mixes messages, runtime, drafts, queue, loading, scroll targets, and cleanup. Track for Phase 4 / Phase 9 reassessment.chatSessionStore mixes session records, hydration/loading, active selection, creation, archive, mutation, context panel state, and workspace UI. Track for Phase 3 and Phase 4.agentStore, use selectors only for stable repeated domain reads such as personas and selected provider.AppShell is orchestration-heavy: projects, sessions, Home setup, provider/model choice, keyboard shortcuts, dialogs, sidebar wiring, and chat activation. Track as a later component decomposition concern.Sidebar performs visible-session filtering, project grouping, runtime badge derivation, search resolver creation, and expanded-project persistence. Track derived helpers during Phase 2 and possible component decomposition later.AppShell and prop-drilled through Sidebar/sidebar sections to SidebarChatRow, and through SessionHistoryView to SessionCard. Track for later component/action-controller cleanup.AppShell.tsx and Sidebar.tsx first.