mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-25 22:34:56 +00:00
446 lines
12 KiB
Markdown
446 lines
12 KiB
Markdown
# WorkflowUI Canvas Hook Refactoring - COMPLETE
|
|
|
|
**Status**: ✓ COMPLETE
|
|
**Date**: 2026-01-23
|
|
**Author**: Claude Code (AI Assistant)
|
|
**Breaking Changes**: NONE (100% backward compatible)
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
The monolithic `useProjectCanvas.ts` hook (322 LOC) has been successfully refactored into **8 focused, modular hooks**, each under 150 LOC. The refactoring maintains complete backward compatibility while providing a more maintainable, testable, and flexible API.
|
|
|
|
### Key Metrics
|
|
|
|
| Metric | Before | After | Status |
|
|
|--------|--------|-------|--------|
|
|
| **Total Files** | 1 | 8 | ✓ +7 focused files |
|
|
| **Max File Size** | 322 LOC | 145 LOC | ✓ -55% reduction |
|
|
| **Min File Size** | 322 LOC | 40 LOC | ✓ Granular control |
|
|
| **Avg File Size** | 322 LOC | 83 LOC | ✓ -74% average |
|
|
| **Compliance** | FAIL (>150) | PASS (all ≤150) | ✓ Full compliance |
|
|
| **Breaking Changes** | N/A | 0 | ✓ Safe migration |
|
|
|
|
---
|
|
|
|
## File Structure
|
|
|
|
### New Directory: `/src/hooks/canvas/`
|
|
|
|
```
|
|
src/hooks/canvas/
|
|
├── index.ts (145 LOC) - Composition
|
|
├── useCanvasZoom.ts (52 LOC) - Zoom control
|
|
├── useCanvasPan.ts (52 LOC) - Pan/drag state
|
|
├── useCanvasSettings.ts (55 LOC) - Grid settings
|
|
├── useCanvasSelection.ts (85 LOC) - Selection mgmt
|
|
├── useCanvasItems.ts (121 LOC) - Load/delete items
|
|
├── useCanvasItemsOperations.ts (113 LOC) - Create/update items
|
|
└── useCanvasGridUtils.ts (40 LOC) - Grid math
|
|
```
|
|
|
|
### Modified Files
|
|
|
|
- **`/src/hooks/index.ts`** - Updated exports to include new hooks
|
|
- **`/src/hooks/useProjectCanvas.ts.old`** - Original file (archived for reference)
|
|
|
|
---
|
|
|
|
## Detailed Hook Breakdown
|
|
|
|
### 1. useCanvasZoom (52 LOC)
|
|
|
|
**Purpose**: Manage canvas viewport zoom level
|
|
|
|
```typescript
|
|
export interface UseCanvasZoomReturn {
|
|
zoom: number; // Current zoom (0.1 to 3.0)
|
|
zoomIn: () => void; // Increase by 1.2x
|
|
zoomOut: () => void; // Decrease by 1.2x
|
|
resetView: () => void; // Reset to 1.0
|
|
setZoom: (zoom: number) => void; // Set specific level
|
|
}
|
|
```
|
|
|
|
**Redux Slices Used**: `canvasSlice`
|
|
|
|
---
|
|
|
|
### 2. useCanvasPan (52 LOC)
|
|
|
|
**Purpose**: Manage canvas panning and dragging state
|
|
|
|
```typescript
|
|
export interface UseCanvasPanReturn {
|
|
pan: CanvasPosition; // { x, y } offset
|
|
isDragging: boolean; // Current drag state
|
|
panTo: (position: CanvasPosition) => void; // Absolute position
|
|
panBy: (delta: CanvasPosition) => void; // Relative movement
|
|
setDraggingState: (isDragging: boolean) => void; // Control drag state
|
|
}
|
|
```
|
|
|
|
**Redux Slices Used**: `canvasSlice`
|
|
|
|
---
|
|
|
|
### 3. useCanvasSettings (55 LOC)
|
|
|
|
**Purpose**: Manage grid and snap settings
|
|
|
|
```typescript
|
|
export interface UseCanvasSettingsReturn {
|
|
gridSnap: boolean; // Snap-to-grid enabled
|
|
showGrid: boolean; // Grid display enabled
|
|
snapSize: number; // Grid size (pixels)
|
|
toggleGridSnap: () => void; // Toggle snap behavior
|
|
toggleShowGrid: () => void; // Toggle grid display
|
|
setSnapSizeValue: (size: number) => void; // Set grid size
|
|
}
|
|
```
|
|
|
|
**Redux Slices Used**: `canvasSlice`
|
|
|
|
---
|
|
|
|
### 4. useCanvasSelection (85 LOC)
|
|
|
|
**Purpose**: Manage item selection
|
|
|
|
```typescript
|
|
export interface UseCanvasSelectionReturn {
|
|
selectedItemIds: string[]; // Selected IDs
|
|
selectedItems: ProjectCanvasItem[]; // Resolved items
|
|
selectItem: (itemId: string) => void; // Single select
|
|
addToSelection: (itemId: string) => void; // Add item
|
|
removeFromSelection: (itemId: string) => void; // Remove item
|
|
toggleSelection: (itemId: string) => void; // Toggle item
|
|
setSelectionIds: (itemIds: string[]) => void; // Set selection
|
|
clearSelection: () => void; // Deselect all
|
|
selectAllItems: () => void; // Select all
|
|
}
|
|
```
|
|
|
|
**Redux Slices Used**: `canvasSlice`, `canvasItemsSlice`
|
|
|
|
**Note**: Automatically resolves selected IDs to full item objects
|
|
|
|
---
|
|
|
|
### 5. useCanvasItems (121 LOC)
|
|
|
|
**Purpose**: Load and delete canvas items
|
|
|
|
```typescript
|
|
export interface UseCanvasItemsReturn {
|
|
canvasItems: ProjectCanvasItem[]; // All items
|
|
isLoading: boolean; // Loading state
|
|
error: string | null; // Error message
|
|
isResizing: boolean; // Resize state
|
|
loadCanvasItems: () => Promise<void>; // Fetch from server
|
|
deleteCanvasItem: (itemId: string) => Promise<void>; // Delete item
|
|
setResizingState: (isResizing: boolean) => void; // Control resize
|
|
}
|
|
```
|
|
|
|
**Redux Slices Used**: `projectSlice`, `canvasSlice`, `canvasItemsSlice`
|
|
|
|
**Lifecycle**: Auto-loads items when `projectId` changes
|
|
|
|
**Storage**: Server (projectService) + IndexedDB cache
|
|
|
|
---
|
|
|
|
### 6. useCanvasItemsOperations (113 LOC)
|
|
|
|
**Purpose**: Create, update, and bulk-update items
|
|
|
|
```typescript
|
|
export interface UseCanvasItemsOperationsReturn {
|
|
createCanvasItem: (data: CreateCanvasItemRequest) => Promise<ProjectCanvasItem | null>;
|
|
updateCanvasItem: (itemId: string, data: UpdateCanvasItemRequest) => Promise<ProjectCanvasItem | null>;
|
|
bulkUpdateItems: (updates: Array<Partial<ProjectCanvasItem> & { id: string }>) => Promise<void>;
|
|
}
|
|
```
|
|
|
|
**Redux Slices Used**: `projectSlice`, `canvasItemsSlice`
|
|
|
|
**Storage**: Server (projectService) + IndexedDB cache
|
|
|
|
---
|
|
|
|
### 7. useCanvasGridUtils (40 LOC)
|
|
|
|
**Purpose**: Grid utility functions
|
|
|
|
```typescript
|
|
export interface UseCanvasGridUtilsReturn {
|
|
snapToGrid: (position: { x: number; y: number }) => { x: number; y: number };
|
|
}
|
|
```
|
|
|
|
**Redux Slices Used**: `canvasSlice`
|
|
|
|
**Pure Function**: No side effects, just math
|
|
|
|
---
|
|
|
|
### 8. index.ts Composition Hook (145 LOC)
|
|
|
|
**Purpose**: Compose all 7 hooks into single `useProjectCanvas()` interface
|
|
|
|
```typescript
|
|
export function useProjectCanvas(): UseProjectCanvasReturn {
|
|
// Structured API (new, recommended)
|
|
const zoomHook = useCanvasZoom();
|
|
const panHook = useCanvasPan();
|
|
const selectionHook = useCanvasSelection();
|
|
const itemsHook = useCanvasItems();
|
|
const settingsHook = useCanvasSettings();
|
|
const operationsHook = useCanvasItemsOperations();
|
|
const gridUtilsHook = useCanvasGridUtils();
|
|
|
|
return {
|
|
// Provide both structured AND flattened APIs
|
|
zoomHook, panHook, selectionHook, itemsHook, settingsHook,
|
|
operationsHook, gridUtilsHook,
|
|
|
|
// Backward compatible snake_case API
|
|
zoom, zoom_in, zoom_out, reset_view,
|
|
pan, pan_canvas, set_dragging,
|
|
gridSnap, showGrid, snapSize, toggle_grid_snap, toggle_show_grid,
|
|
select_item, select_add, select_remove, select_toggle, select_clear,
|
|
canvasItems, selectedItemIds, selectedItems,
|
|
// ... etc
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Redux Integration
|
|
|
|
### Slice Distribution
|
|
|
|
**Before (Incorrect)**:
|
|
- All canvas actions imported from `projectSlice`
|
|
|
|
**After (Corrected)**:
|
|
- `canvasSlice` - Zoom, pan, selection, grid settings, drag/resize state
|
|
- `canvasItemsSlice` - Canvas items CRUD operations
|
|
- `projectSlice` - Project state, loading, errors
|
|
|
|
### Import Mapping
|
|
|
|
| Action/Selector | Original Slice | Corrected Slice |
|
|
|-----------------|----------------|-----------------|
|
|
| `setCanvasZoom` | projectSlice ❌ | canvasSlice ✓ |
|
|
| `setCanvasPan` | projectSlice ❌ | canvasSlice ✓ |
|
|
| `selectCanvasZoom` | projectSlice ❌ | canvasSlice ✓ |
|
|
| `selectCanvasItem` | projectSlice ❌ | canvasSlice ✓ |
|
|
| `setCanvasItems` | projectSlice ❌ | canvasItemsSlice ✓ |
|
|
| `addCanvasItem` | projectSlice ❌ | canvasItemsSlice ✓ |
|
|
| `selectCanvasItems` | projectSlice ❌ | canvasItemsSlice ✓ |
|
|
| `setLoading` | projectSlice ✓ | projectSlice ✓ |
|
|
| `selectCurrentProjectId` | projectSlice ✓ | projectSlice ✓ |
|
|
|
|
---
|
|
|
|
## Backward Compatibility
|
|
|
|
### API Parity
|
|
|
|
All original methods remain available with identical signatures:
|
|
|
|
```typescript
|
|
// Original usage (STILL WORKS)
|
|
const canvas = useProjectCanvas();
|
|
|
|
// Zoom
|
|
canvas.zoom_in();
|
|
canvas.zoom_out();
|
|
canvas.reset_view();
|
|
|
|
// Pan
|
|
canvas.pan_canvas({ x: 10, y: 20 });
|
|
canvas.set_dragging(true);
|
|
|
|
// Selection
|
|
canvas.select_item('item-123');
|
|
canvas.select_add('item-456');
|
|
canvas.select_clear();
|
|
canvas.select_all_items();
|
|
|
|
// Settings
|
|
canvas.toggle_grid_snap();
|
|
canvas.toggle_show_grid();
|
|
canvas.set_snap_size(25);
|
|
|
|
// Items
|
|
canvas.loadCanvasItems();
|
|
canvas.createCanvasItem(data);
|
|
canvas.updateCanvasItem('id', data);
|
|
canvas.deleteCanvasItem('id');
|
|
canvas.bulkUpdateItems(updates);
|
|
|
|
// Utilities
|
|
canvas.snap_to_grid({ x: 100, y: 200 });
|
|
|
|
// State
|
|
const { zoom, pan, selectedItems, canvasItems, isLoading } = canvas;
|
|
```
|
|
|
|
### Recommended New Usage
|
|
|
|
```typescript
|
|
// Structured API (new, more maintainable)
|
|
const canvas = useProjectCanvas();
|
|
|
|
// Zoom operations
|
|
canvas.zoomHook.zoomIn();
|
|
canvas.zoomHook.zoomOut();
|
|
|
|
// Selection operations
|
|
canvas.selectionHook.selectItem('item-123');
|
|
canvas.selectionHook.addToSelection('item-456');
|
|
|
|
// Grid utilities
|
|
canvas.gridUtilsHook.snapToGrid({ x: 100, y: 200 });
|
|
|
|
// Items operations
|
|
canvas.itemsHook.loadCanvasItems();
|
|
canvas.operationsHook.createCanvasItem(data);
|
|
|
|
// State access
|
|
const { zoom } = canvas.zoomHook;
|
|
const { selectedItems } = canvas.selectionHook;
|
|
const { canvasItems } = canvas.itemsHook;
|
|
```
|
|
|
|
### Direct Hook Usage
|
|
|
|
```typescript
|
|
// Most flexible - import only what you need
|
|
import { useCanvasZoom, useCanvasSelection } from '../hooks/canvas';
|
|
|
|
const { zoomIn, zoomOut } = useCanvasZoom();
|
|
const { selectItem, addToSelection } = useCanvasSelection();
|
|
|
|
zoomIn();
|
|
selectItem('item-123');
|
|
```
|
|
|
|
---
|
|
|
|
## Testing Strategy
|
|
|
|
### Unit Tests
|
|
- Test each hook independently
|
|
- Mock Redux store
|
|
- Verify state transitions
|
|
- Validate action dispatches
|
|
|
|
### Integration Tests
|
|
- Test hook composition
|
|
- Verify backward compatibility
|
|
- Test Redux flow end-to-end
|
|
|
|
### Type Safety
|
|
- TypeScript strict mode
|
|
- Explicit interfaces for each hook
|
|
- Proper action/selector typing
|
|
|
|
---
|
|
|
|
## Performance Considerations
|
|
|
|
### Optimization Benefits
|
|
|
|
1. **Granular Re-renders**: Components using only `useCanvasZoom` won't re-render when selection changes
|
|
2. **Selective Imports**: Tree-shaking will eliminate unused hooks
|
|
3. **Lazy Initialization**: Hooks initialize selectors only when used
|
|
|
|
### Memoization
|
|
|
|
All callbacks properly memoized with dependency arrays:
|
|
```typescript
|
|
const zoomIn = useCallback(() => {
|
|
dispatch(setCanvasZoom(Math.min(zoom * 1.2, 3)));
|
|
}, [zoom, dispatch]); // ✓ Proper dependencies
|
|
```
|
|
|
|
---
|
|
|
|
## Migration Path
|
|
|
|
### Phase 1: Deploy (No Changes Required)
|
|
- Deploy new hooks
|
|
- Existing code continues to work
|
|
- Old `useProjectCanvas.ts` can be removed
|
|
|
|
### Phase 2: Gradual Modernization (Optional)
|
|
- Components gradually adopt new `zoomHook`, `panHook` API
|
|
- No pressure - backward compat maintained indefinitely
|
|
|
|
### Phase 3: Future Cleanup (Optional)
|
|
- Remove flattened API from composition hook
|
|
- Update all components to use structured API
|
|
- Simplify types and interfaces
|
|
|
|
---
|
|
|
|
## Files Changed
|
|
|
|
### Created
|
|
```
|
|
✓ /src/hooks/canvas/index.ts
|
|
✓ /src/hooks/canvas/useCanvasZoom.ts
|
|
✓ /src/hooks/canvas/useCanvasPan.ts
|
|
✓ /src/hooks/canvas/useCanvasSelection.ts
|
|
✓ /src/hooks/canvas/useCanvasItems.ts
|
|
✓ /src/hooks/canvas/useCanvasItemsOperations.ts
|
|
✓ /src/hooks/canvas/useCanvasSettings.ts
|
|
✓ /src/hooks/canvas/useCanvasGridUtils.ts
|
|
```
|
|
|
|
### Modified
|
|
```
|
|
✓ /src/hooks/index.ts - Updated exports
|
|
```
|
|
|
|
### Archived
|
|
```
|
|
✓ /src/hooks/useProjectCanvas.ts.old
|
|
```
|
|
|
|
---
|
|
|
|
## Verification Checklist
|
|
|
|
- [x] All files created under 150 LOC
|
|
- [x] Code-only LOC (without comments) under 110 for most files
|
|
- [x] Redux imports corrected to proper slices
|
|
- [x] TypeScript compilation passes (except unrelated realtimeSlice warning)
|
|
- [x] Backward compatibility maintained (all original methods available)
|
|
- [x] New structured API provided (zoomHook, panHook, etc.)
|
|
- [x] Proper type definitions for all hooks
|
|
- [x] Dependencies properly tracked in useCallback dependencies
|
|
- [x] No breaking changes to existing code
|
|
- [x] All hooks properly exported from index.ts
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
✓ **322 LOC monolith** → **8 focused hooks (max 145 LOC)**
|
|
✓ **Proper separation of concerns**
|
|
✓ **Redux slices correctly mapped**
|
|
✓ **100% backward compatible**
|
|
✓ **Cleaner, more testable code**
|
|
✓ **Type-safe interfaces**
|
|
✓ **Ready for production**
|
|
|
|
**The refactoring is complete and ready for integration.**
|
|
|