Fix issues identified in Copilot review:
1. Remove throttling from stage drag cursor updates - cursor now updates immediately on drag start for better UX
2. Accumulate wheel deltaY values during throttle period instead of only processing last event - prevents jerky zoom behavior
3. Remove redundant listening={false} props from child elements (inherited from parent Group)
4. Update file documentation to reflect stage drag cursor update functionality
These changes improve both performance and user experience while maintaining code clarity.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The selectPatternValidation selector doesn't work well with Zustand because:
1. It requires machineInfo parameter from a different store
2. Passing machineInfo creates new object references on every render
3. This breaks Zustand's memoization and causes infinite loops
Solution:
- Reverted usePatternValidation.ts to original implementation with useMemo
- Removed selectPatternValidation and usePatternValidationFromStore
- Removed PatternValidationResult type (not needed)
- Updated tests to remove validation selector tests (12 tests remain)
The store selectors (selectPatternCenter, selectRotatedBounds, etc) are
still useful for components that only need those specific values, but
validation logic that depends on external state should stay local.
Co-authored-by: jhbruhn <1036566+jhbruhn@users.noreply.github.com>
The selectors were creating new object references on every call, causing
Zustand's subscription system to detect changes and trigger infinite
re-render loops. This was particularly evident in usePatternValidationFromStore.
Solution:
- Import useShallow from zustand/react/shallow
- Wrap all selector hooks (usePatternCenter, useUploadedPatternCenter,
useRotatedBounds, usePatternValidationFromStore) with useShallow
- useShallow performs shallow comparison on returned objects, preventing
re-renders when values haven't actually changed
This follows the established pattern in the codebase where useShallow is
already used extensively (App.tsx, FileUpload.tsx, etc).
Co-authored-by: jhbruhn <1036566+jhbruhn@users.noreply.github.com>
Fix TypeScript build error "Type 'null' cannot be used as an index type"
by adding explicit null check for variant before indexing defaultIcons.
The variant from VariantProps can be null even with a default value,
so TypeScript requires an explicit check.
Changes:
- Added null check: showDefaultIcon && variant
- Added 'as const' to defaultIcons for better type inference
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add documentation to usePatternRotationUpload about using same helpers as store
- Add documentation to App.tsx pattern resume logic
- Mark PatternLayer to continue using local memoization (receives props)
- All tests passing
Co-authored-by: jhbruhn <1036566+jhbruhn@users.noreply.github.com>
- Add selectPatternCenter for pattern center calculation
- Add selectRotatedBounds for rotated bounds with memoization
- Add selectRotationCenterShift for center shift calculation
- Add selectPatternValidation for bounds checking against hoop
- Add comprehensive tests for all selectors
- Update usePatternValidation to use store selectors
- All tests passing and linter clean
Co-authored-by: jhbruhn <1036566+jhbruhn@users.noreply.github.com>
Add error handling and documentation to event subscriptions based on
Copilot review feedback.
Changes:
- Added try-catch blocks to all event callbacks for graceful error handling
- Added comments documenting that subscriptions persist for app lifetime
- Improved JSDoc for onPatternDeleted function with lifecycle details
- Added error logging to help debug potential issues
Benefits:
- Prevents silent failures in event callbacks
- Clear documentation about subscription lifecycle
- Better developer experience with error messages
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace direct store imports and calls with a Zustand-based event system
for decoupled cross-store communication.
Changes:
- Created storeEvents.ts using Zustand for event management
- Removed direct usePatternStore import from useMachineStore
- Removed dynamic imports for useMachineUploadStore and useMachineCacheStore
- Added event subscriptions in usePatternStore, useMachineUploadStore, and useMachineCacheStore
- useMachineStore now emits patternDeleted event instead of calling other stores directly
Benefits:
- Stores can be tested in isolation
- No tight coupling between stores
- Clear, explicit event-driven data flow
- Uses Zustand's built-in subscription system
- Easier to refactor stores independently
Fixes#37🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Extract color block calculation from ProgressMonitor component to
colorBlockHelpers utility module for better testability and reusability.
Changes:
- Created colorBlockHelpers.ts with calculateColorBlocks() and findCurrentBlockIndex()
- Added comprehensive unit tests (11 test cases, all passing)
- Updated ProgressMonitor to use new utility functions
- Reduced component complexity by removing embedded business logic
Benefits:
- Logic can be tested in isolation
- Can be reused elsewhere if needed
- Cleaner component code
- Better separation of concerns
Fixes#44🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Address Copilot review feedback: PatternCanvas doesn't accept any props,
so React.memo has no effect. The component re-renders are driven by
Zustand store subscriptions which trigger regardless of memoization.
Keep React.memo on PatternLayer since it does receive props and benefits
from memoization.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Wrap PatternCanvas and PatternLayer components with React.memo to
prevent unnecessary re-renders when props haven't changed. This builds
on the existing useMemo optimizations and provides better performance
with large patterns and frequent UI updates.
Benefits:
- Prevents re-renders when parent components update
- Improves render performance during active sewing
- Smoother user experience with complex patterns
Fixes#43🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Extract inline calculations to useMemo hooks to prevent unnecessary
recalculations on every render. Memoized displayPattern selection
and pattern dimensions calculation improve performance with large patterns.
Fixes#34🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Simplify StepCircle cursor logic to use isComplete || isCurrent
- Fix UploadButton to use boundsFits prop instead of !!boundsError
- Remove XSS vulnerability by parsing markdown safely without dangerouslySetInnerHTML
- Move ColorBlock type to shared types.ts file to reduce coupling
- Rename useDisplayFilename to getDisplayFilename and move to utils (not a hook)
- Improve threadMetadata JSDoc with detailed examples
- Make WorkflowStep interface properties readonly for full immutability
- Fix PyodideProgress redundant negation logic
All issues from Copilot review resolved.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Move FileUpload into dedicated folder with sub-components
- Extract FileSelector, PyodideProgress, UploadButton, UploadProgress, BoundsValidator
- Create useDisplayFilename hook for filename priority logic
- Reduce FileUpload.tsx from 391 to 261 lines (33% reduction)
Part of #33: Extract sub-components from large components
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Move ProgressMonitor into dedicated folder with sub-components
- Extract ProgressStats, ProgressSection, ColorBlockList, ColorBlockItem, ProgressActions
- Create threadMetadata utility for formatting thread metadata
- Reduce ProgressMonitor.tsx from 389 to 178 lines (54% reduction)
Part of #33: Extract sub-components from large components
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Move WorkflowStepper into dedicated folder with sub-components
- Extract StepCircle, StepLabel, and StepPopover components
- Create workflowSteps constant for step definitions
- Extract getCurrentStep logic to workflowStepCalculation utility
- Extract getGuideContent logic to workflowGuideContent utility
- Reduce WorkflowStepper.tsx from 487 to 140 lines (71% reduction)
Part of #33: Extract sub-components from large components
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Resolved all 7 issues identified in PR review:
1. @testing-library/dom peer dependency already explicitly listed
2. Removed invalid eslint-disable comments (replaced with correct rule)
3. Fixed unstable callbacks in useMachinePolling using refs to prevent unnecessary re-renders
4. Fixed useAutoScroll options dependency with useMemo for stability
5. Fixed stale closure in BluetoothDevicePicker using functional setState
6. Fixed memory leak in useBluetoothDeviceListener by preventing re-registration of IPC listeners
7. Added proper eslint-disable for intentional setState in effect with detailed comment
All tests passing (91/91), build successful, linter clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add type assertions to useErrorPopoverState test rerender calls
- Use non-null assertions for callback invocations in useBluetoothDeviceListener tests
- Fix type inference issues with union types (number | undefined, string | null)
- All 91 tests passing with proper TypeScript compliance
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Fix import paths in domain hooks (useErrorPopoverState, useMachinePolling)
- Fix import path in platform hooks (useBluetoothDeviceListener)
- Correct RefObject type signatures in useAutoScroll and useClickOutside
- Add proper type parameters to hook usages in components
- Fix useRef initialization in useMachinePolling
- Add type guard for undefined in useErrorPopoverState
All TypeScript build errors resolved. Build and tests passing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
TypeScript requires an initial value argument when calling useRef.
Changed useRef<T>() to useRef<T | undefined>(undefined) to fix
build error and properly type the ref for the first render.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Update usePrevious hook to use useEffect pattern instead of mutating
refs during render (addresses Concurrent Mode compatibility)
- Add wasManuallyDismissed flag to properly track dismissal of all error
types (machineError, machineErrorMessage, and pyodideError)
- Add proper eslint-disable comment with explanation for ref access
- Update handlePopoverOpenChange to handle dismissal of all error types
These changes address all feedback from PR review #54🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace manual ref tracking with usePrevious custom hook for cleaner,
more maintainable code. Simplifies error state change detection by
eliminating manual ref updates and reducing complexity.
Resolves#36🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Remove redundant useMemo wrapper in usePatternValidation
Inline the calculation logic directly in useMemo instead of
useCallback + useMemo pattern for better clarity and efficiency
- Remove unnecessary 'Early return' comment in usePatternRotationUpload
The return statement is self-explanatory
**Problem:**
Store initialization was happening at module load via side effect:
```typescript
useMachineStore.getState()._setupSubscriptions();
```
This caused several issues:
- Executed before app was ready
- Made testing difficult (runs before test setup)
- Hard to control initialization timing
- Could cause issues in different environments
**Solution:**
- Added public `initialize()` method to useMachineStore
- Call initialization from App component's useEffect (proper lifecycle)
- Removed module-level side effect
**Benefits:**
- ✅ Controlled initialization timing
- ✅ Better testability (no side effects on import)
- ✅ Follows React lifecycle patterns
- ✅ No behavioral changes for end users
**Testing:**
- Build tested successfully
- Linter passed
- All TypeScript types validated
Fixes#38🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Split the 570-line useMachineStore into three focused stores for better
separation of concerns and improved re-render optimization:
**New Stores:**
- useMachineUploadStore (128 lines): Pattern upload state and logic
- useMachineCacheStore (194 lines): Pattern caching and resume functionality
- useMachineStore (reduced to ~245 lines): Connection, status, and polling
**Benefits:**
- Components only subscribe to relevant state (reduces unnecessary re-renders)
- Clear separation of concerns (upload, cache, connection)
- Easier to test and maintain individual stores
- 30% reduction in main store size
**Technical Details:**
- Uses dynamic imports to avoid circular dependencies
- Maintains all existing functionality
- Updated FileUpload, PatternCanvas, and App components
- All TypeScript compilation errors resolved
- Build tested successfully
Implements conservative 2-store extraction approach from issue #31.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>