Browse Source

docs(16): create phase plan for store modularization

Phase 16: Store Modularization
- 3 plans created
- 9 total tasks defined
- Ready for execution

Plans:
- 16-01: Extract localStorage helpers and node defaults
- 16-02: Extract execution logic into dedicated modules
- 16-03: Create Zustand slices and compose store
handoff-20260429-1057
shrimbly 6 months ago
parent
commit
a0d7f51145
  1. 139
      .planning/phases/16-store-modularization/16-01-PLAN.md
  2. 168
      .planning/phases/16-store-modularization/16-02-PLAN.md
  3. 212
      .planning/phases/16-store-modularization/16-03-PLAN.md

139
.planning/phases/16-store-modularization/16-01-PLAN.md

@ -0,0 +1,139 @@
---
phase: 16-store-modularization
plan: 01
type: execute
---
<objective>
Extract localStorage helpers and configuration utilities from workflowStore.ts into dedicated modules.
Purpose: Begin store modularization by extracting pure utility functions that don't depend on Zustand state, making them easier to test and reducing the main store file size.
Output: New utility modules for localStorage operations and default node data creation, with workflowStore.ts importing from these modules.
</objective>
<execution_context>
~/.claude/get-shit-done/workflows/execute-phase.md
~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/15-test-infrastructure/15-01-SUMMARY.md
@src/store/workflowStore.ts
**From Phase 15 Summary:**
- Vitest and React Testing Library configured
- Zustand store mocking patterns established
- Test infrastructure ready for validating refactored modules
**Current Store Analysis (2786 lines):**
The workflowStore.ts contains several pure utility functions at the top that don't depend on Zustand state:
- localStorage helpers for workflow configs (~40 lines)
- localStorage helpers for cost tracking (~40 lines)
- localStorage helpers for GenerateImage defaults (~40 lines)
- localStorage helpers for provider settings (~50 lines)
- createDefaultNodeData function (~90 lines)
- GROUP_COLORS and GROUP_COLOR_ORDER constants (~15 lines)
- generateWorkflowId utility (~3 lines)
</context>
<tasks>
<task type="auto">
<name>Task 1: Create store utilities module</name>
<files>src/store/utils/localStorage.ts</files>
<action>
Extract all localStorage helpers into a new module:
1. Create src/store/utils/localStorage.ts
2. Move these functions/constants:
- STORAGE_KEY constant
- COST_DATA_STORAGE_KEY constant
- GENERATE_IMAGE_DEFAULTS_KEY constant
- PROVIDER_SETTINGS_KEY constant
- loadSaveConfigs()
- saveSaveConfig()
- loadWorkflowCostData()
- saveWorkflowCostData()
- loadGenerateImageDefaults() (rename from loadNanoBananaDefaults)
- saveGenerateImageDefaults() (existing export, keep as-is)
- getProviderSettings()
- saveProviderSettings()
- defaultProviderSettings constant
- GenerateImageDefaults interface
3. Export all functions and types
4. Keep backward-compatible aliases (NanoBananaDefaults, saveNanoBananaDefaults) in workflowStore.ts but have them re-export from the new module
Note: These functions use localStorage directly and don't depend on Zustand state, making them safe to extract.
</action>
<verify>npm run build succeeds with no TypeScript errors</verify>
<done>All localStorage helpers extracted, workflowStore.ts imports from new module, build passes</done>
</task>
<task type="auto">
<name>Task 2: Create node defaults module</name>
<files>src/store/utils/nodeDefaults.ts</files>
<action>
Extract node creation utilities:
1. Create src/store/utils/nodeDefaults.ts
2. Move these items:
- createDefaultNodeData() function (~90 lines)
- defaultDimensions record (duplicated in addNode and createGroup - consolidate)
- GROUP_COLORS record
- GROUP_COLOR_ORDER array
- GroupColor type import from @/types
3. The createDefaultNodeData function calls loadGenerateImageDefaults() - update import to use new localStorage module
4. Export createDefaultNodeData, defaultNodeDimensions, GROUP_COLORS, GROUP_COLOR_ORDER
5. Update workflowStore.ts to import these from the new module
</action>
<verify>npm run build succeeds, npm test passes all 112+ tests</verify>
<done>Node defaults extracted, GROUP_COLORS exported from new location, all imports updated, tests pass</done>
</task>
<task type="auto">
<name>Task 3: Add unit tests for extracted modules</name>
<files>src/store/utils/__tests__/localStorage.test.ts, src/store/utils/__tests__/nodeDefaults.test.ts</files>
<action>
Create tests for the extracted modules:
1. Create src/store/utils/__tests__/localStorage.test.ts:
- Test loadSaveConfigs returns empty object when localStorage empty
- Test saveSaveConfig stores and retrieves config
- Test loadWorkflowCostData handles missing data
- Test loadGenerateImageDefaults returns defaults when localStorage empty
- Test getProviderSettings merges with defaults for new providers
- Mock localStorage using vi.stubGlobal or in-memory implementation
2. Create src/store/utils/__tests__/nodeDefaults.test.ts:
- Test createDefaultNodeData for each node type returns correct structure
- Test defaultNodeDimensions has all expected node types
- Test GROUP_COLORS has expected color keys
- Mock localStorage for createDefaultNodeData's loadGenerateImageDefaults call
</action>
<verify>npm test passes with new tests included, coverage for new modules</verify>
<done>Unit tests added for both modules, all tests pass including new ones</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] `npm run build` succeeds without errors
- [ ] `npm test` passes all tests (existing + new)
- [ ] workflowStore.ts is reduced by ~280 lines
- [ ] New modules are properly typed with exports
- [ ] Backward compatibility maintained (saveNanoBananaDefaults still works)
</verification>
<success_criteria>
- All tasks completed
- All verification checks pass
- No TypeScript errors
- workflowStore.ts imports from new utility modules
- New modules have test coverage
</success_criteria>
<output>
After completion, create `.planning/phases/16-store-modularization/16-01-SUMMARY.md`
</output>

168
.planning/phases/16-store-modularization/16-02-PLAN.md

@ -0,0 +1,168 @@
---
phase: 16-store-modularization
plan: 02
type: execute
---
<objective>
Extract workflow execution logic from workflowStore.ts into a dedicated execution module.
Purpose: The execution logic (~800 lines) is the largest section and most complex. Extracting it enables focused testing and cleaner separation of concerns between state management and execution orchestration.
Output: New execution module with executeWorkflow, regenerateNode, and supporting execution utilities.
</objective>
<execution_context>
~/.claude/get-shit-done/workflows/execute-phase.md
~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/16-store-modularization/16-01-PLAN.md
@src/store/workflowStore.ts
**Dependencies from 16-01:**
- localStorage utilities extracted
- nodeDefaults module exists
**Execution Logic Analysis (~800 lines):**
The executeWorkflow and regenerateNode functions are tightly coupled to store state but can be extracted as action creators that receive state getters/setters. Key components:
- Topological sort algorithm (~30 lines)
- Node execution switch statement (~600 lines for all node types)
- API call patterns (repeated for each provider)
- Logger calls throughout
- State updates via updateNodeData
</context>
<tasks>
<task type="auto">
<name>Task 1: Extract execution utilities</name>
<files>src/store/execution/utils.ts</files>
<action>
Create src/store/execution/utils.ts with reusable execution utilities:
1. Extract topologicalSort function:
- Takes nodes and edges as input
- Returns sorted WorkflowNode array
- Throws on cycle detection
2. Extract buildProviderHeaders function:
- Takes provider type and providerSettings
- Returns headers object with API key headers
- Consolidates the repeated header building logic from executeWorkflow/regenerateNode
3. Extract getSourceOutput helper:
- Takes a WorkflowNode
- Returns { type: "image" | "text" | "video", value: string | null }
- Currently duplicated in getConnectedInputs
4. Export types:
- ExecutionResult = { success: boolean; error?: string; data?: unknown }
- NodeExecutionContext = { nodeId: string; get: () => StoreState; set: (partial: Partial<StoreState>) => void }
These are pure functions that don't directly depend on Zustand, making them testable.
</action>
<verify>npm run build succeeds, new module compiles without errors</verify>
<done>Execution utilities extracted, types defined, build passes</done>
</task>
<task type="auto">
<name>Task 2: Extract node executors</name>
<files>src/store/execution/nodeExecutors.ts</files>
<action>
Create src/store/execution/nodeExecutors.ts with individual node type executors:
1. Create executor type:
```typescript
type NodeExecutor = (
node: WorkflowNode,
context: {
getConnectedInputs: (nodeId: string) => ConnectedInputs;
updateNodeData: (nodeId: string, data: Partial<WorkflowNodeData>) => void;
get: () => WorkflowStoreState;
set: (partial: Partial<WorkflowStoreState>) => void;
providerSettings: ProviderSettings;
}
) => Promise<{ continue: boolean; error?: string }>;
```
2. Extract executors for each node type:
- executeImageInput (no-op, returns continue: true)
- executeAnnotation
- executePrompt (no-op)
- executeNanoBanana (image generation)
- executeGenerateVideo (video generation)
- executeLlmGenerate (text generation)
- executeSplitGrid
- executeOutput
3. Create nodeExecutorMap:
```typescript
const nodeExecutorMap: Record<NodeType, NodeExecutor> = { ... }
```
4. Export executeNode function that dispatches to the correct executor
This keeps the individual node logic but allows testing each executor in isolation.
</action>
<verify>npm run build succeeds, no TypeScript errors</verify>
<done>Node executors extracted as individual functions with shared interface</done>
</task>
<task type="auto">
<name>Task 3: Refactor executeWorkflow to use extracted modules</name>
<files>src/store/workflowStore.ts</files>
<action>
Update executeWorkflow in workflowStore.ts to use the extracted modules:
1. Import from execution/utils.ts:
- topologicalSort
- buildProviderHeaders
2. Import from execution/nodeExecutors.ts:
- executeNode
3. Refactor executeWorkflow to use the imported utilities:
- Replace inline topological sort with topologicalSort()
- Replace switch statement with executeNode() calls
- Keep the overall orchestration logic (pause handling, locked groups, logging) in the store
4. Refactor regenerateNode similarly:
- Use buildProviderHeaders
- Could call individual executors but for now keep inline (regenerate has slightly different flow)
5. Ensure getConnectedInputs stays in the store (it needs edge state)
The goal is to reduce executeWorkflow from ~800 lines to ~200 lines of orchestration logic.
</action>
<verify>npm run build succeeds, npm test passes, manual test of workflow execution works</verify>
<done>executeWorkflow refactored to ~200 lines, uses extracted modules, all tests pass</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] `npm run build` succeeds without errors
- [ ] `npm test` passes all tests
- [ ] executeWorkflow function reduced from ~800 to ~200 lines
- [ ] Workflow execution still works (test with a simple prompt -> generate -> output flow)
- [ ] regenerateNode still works
- [ ] Error handling preserved
</verification>
<success_criteria>
- All tasks completed
- All verification checks pass
- executeWorkflow is significantly shorter and more readable
- Node execution logic is isolated and testable
- No regression in workflow execution behavior
</success_criteria>
<output>
After completion, create `.planning/phases/16-store-modularization/16-02-SUMMARY.md`
</output>

212
.planning/phases/16-store-modularization/16-03-PLAN.md

@ -0,0 +1,212 @@
---
phase: 16-store-modularization
plan: 03
type: execute
---
<objective>
Extract remaining store slices into focused modules using Zustand slice pattern.
Purpose: Break up the remaining monolithic store into logical slices (groups, clipboard, persistence, providers) that can be developed and tested independently while maintaining a single unified store.
Output: Zustand slices for groups, clipboard, persistence, and provider settings, composed into the main store.
</objective>
<execution_context>
~/.claude/get-shit-done/workflows/execute-phase.md
~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/16-store-modularization/16-01-PLAN.md
@.planning/phases/16-store-modularization/16-02-PLAN.md
@src/store/workflowStore.ts
**Dependencies from 16-01 and 16-02:**
- localStorage utilities extracted to src/store/utils/localStorage.ts
- Node defaults extracted to src/store/utils/nodeDefaults.ts
- Execution logic extracted to src/store/execution/
**Remaining Store Sections:**
After 16-01 and 16-02, the store still contains:
- Group operations (~200 lines): createGroup, deleteGroup, addNodesToGroup, etc.
- Clipboard operations (~100 lines): copySelectedNodes, pasteNodes, clearClipboard
- Persistence operations (~300 lines): saveWorkflow, loadWorkflow, auto-save
- Provider settings (~100 lines): updateProviderSettings, updateProviderApiKey
- Core node/edge operations (~200 lines): These stay in main store
- UI state (~50 lines): modal count, quickstart - stays in main store
</context>
<tasks>
<task type="auto">
<name>Task 1: Create group operations slice</name>
<files>src/store/slices/groupSlice.ts</files>
<action>
Create src/store/slices/groupSlice.ts using Zustand slice pattern:
1. Define GroupSlice interface:
```typescript
interface GroupSlice {
groups: Record<string, NodeGroup>;
createGroup: (nodeIds: string[]) => string;
deleteGroup: (groupId: string) => void;
addNodesToGroup: (nodeIds: string[], groupId: string) => void;
removeNodesFromGroup: (nodeIds: string[]) => void;
updateGroup: (groupId: string, updates: Partial<NodeGroup>) => void;
toggleGroupLock: (groupId: string) => void;
moveGroupNodes: (groupId: string, delta: { x: number; y: number }) => void;
setNodeGroupId: (nodeId: string, groupId: string | undefined) => void;
}
```
2. Create slice creator function:
```typescript
export const createGroupSlice: StateCreator<
WorkflowStore,
[],
[],
GroupSlice
> = (set, get) => ({
groups: {},
createGroup: (nodeIds) => { ... },
// ... other methods
});
```
3. Import GROUP_COLORS and GROUP_COLOR_ORDER from utils/nodeDefaults
4. Import defaultNodeDimensions for bounding box calculation
5. Manage groupIdCounter within the slice
The slice needs access to nodes for createGroup bounding box calculation, which it gets via get().
</action>
<verify>npm run build succeeds, slice compiles without errors</verify>
<done>Group slice created with all group operations</done>
</task>
<task type="auto">
<name>Task 2: Create persistence slice</name>
<files>src/store/slices/persistenceSlice.ts</files>
<action>
Create src/store/slices/persistenceSlice.ts:
1. Define PersistenceSlice interface:
```typescript
interface PersistenceSlice {
// State
workflowId: string | null;
workflowName: string | null;
saveDirectoryPath: string | null;
generationsPath: string | null;
lastSavedAt: number | null;
hasUnsavedChanges: boolean;
autoSaveEnabled: boolean;
isSaving: boolean;
useExternalImageStorage: boolean;
// Actions
setWorkflowMetadata: (id: string, name: string, path: string, generationsPath?: string | null) => void;
setWorkflowName: (name: string) => void;
setGenerationsPath: (path: string | null) => void;
setAutoSaveEnabled: (enabled: boolean) => void;
setUseExternalImageStorage: (enabled: boolean) => void;
markAsUnsaved: () => void;
saveToFile: () => Promise<boolean>;
initializeAutoSave: () => void;
cleanupAutoSave: () => void;
saveWorkflow: (name?: string) => void;
loadWorkflow: (workflow: WorkflowFile, workflowPath?: string) => Promise<void>;
clearWorkflow: () => void;
}
```
2. Move autoSaveIntervalId to slice file scope
3. Import localStorage helpers from utils/localStorage
4. Import externalizeWorkflowImages, hydrateWorkflowImages from @/utils/imageStorage
5. Keep nodeIdCounter and groupIdCounter management in main store (loadWorkflow needs to update them)
Note: loadWorkflow also needs to reset execution state (isRunning, currentNodeId) which stays in main store. The slice's loadWorkflow will need to call through to main store's set().
</action>
<verify>npm run build succeeds, slice compiles without errors</verify>
<done>Persistence slice created with save/load/auto-save logic</done>
</task>
<task type="auto">
<name>Task 3: Compose slices into main store</name>
<files>src/store/workflowStore.ts</files>
<action>
Refactor workflowStore.ts to compose the slices:
1. Import slices:
```typescript
import { createGroupSlice, GroupSlice } from './slices/groupSlice';
import { createPersistenceSlice, PersistenceSlice } from './slices/persistenceSlice';
```
2. Define combined store type:
```typescript
type WorkflowStore = CoreSlice & GroupSlice & PersistenceSlice & ExecutionSlice;
```
3. Update store creation using spread:
```typescript
export const useWorkflowStore = create<WorkflowStore>()((...args) => ({
// Core state and actions inline
nodes: [],
edges: [],
// ...
// Spread slices
...createGroupSlice(...args),
...createPersistenceSlice(...args),
}));
```
4. Keep in main store (CoreSlice):
- nodes, edges, edgeStyle state
- Node CRUD: addNode, updateNodeData, removeNode, onNodesChange
- Edge operations: onEdgesChange, onConnect, addEdgeWithType, removeEdge, toggleEdgePause
- getNodeById, getConnectedInputs, validateWorkflow
- UI state: openModalCount, isModalOpen, showQuickstart
- Execution state: isRunning, currentNodeId, pausedAtNodeId
- executeWorkflow, regenerateNode, stopWorkflow (uses extracted execution modules)
- globalImageHistory, incurredCost state and actions
- Provider settings state and actions
- Model search dialog state
5. Export backward-compatible aliases (GROUP_COLORS, saveNanoBananaDefaults, etc.)
Target: Main store file reduced from ~2800 to ~1500 lines.
</action>
<verify>npm run build succeeds, npm test passes all tests, manual test of group creation and save/load works</verify>
<done>Store composed from slices, main file significantly reduced, all functionality preserved</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] `npm run build` succeeds without errors
- [ ] `npm test` passes all tests
- [ ] workflowStore.ts reduced from ~2800 to ~1500 lines
- [ ] Group operations work (create, lock, delete)
- [ ] Save/load workflow works
- [ ] Auto-save works
- [ ] All exports maintained for backward compatibility
</verification>
<success_criteria>
- All tasks completed
- All verification checks pass
- Store properly composed from slices
- Each slice is focused and cohesive
- No regression in any store functionality
- Phase 16 complete: Store Modularization achieved
</success_criteria>
<output>
After completion, create `.planning/phases/16-store-modularization/16-03-SUMMARY.md`
</output>
Loading…
Cancel
Save