Browse Source
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 storehandoff-20260429-1057
3 changed files with 519 additions and 0 deletions
@ -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> |
|||
@ -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> |
|||
@ -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…
Reference in new issue