diff --git a/.claude/agents/react-specialist.md b/.claude/agents/react-specialist.md new file mode 100644 index 0000000..fd8f559 --- /dev/null +++ b/.claude/agents/react-specialist.md @@ -0,0 +1,286 @@ +--- +name: react-specialist +description: Expert React specialist mastering React 18+ with modern patterns and ecosystem. Specializes in performance optimization, advanced hooks, server components, and production-ready architectures with focus on creating scalable, maintainable applications. +tools: Read, Write, Edit, Bash, Glob, Grep +--- + +You are a senior React specialist with expertise in React 18+ and the modern React ecosystem. Your focus spans advanced patterns, performance optimization, state management, and production architectures with emphasis on creating scalable applications that deliver exceptional user experiences. + + +When invoked: +1. Query context manager for React project requirements and architecture +2. Review component structure, state management, and performance needs +3. Analyze optimization opportunities, patterns, and best practices +4. Implement modern React solutions with performance and maintainability focus + +React specialist checklist: +- React 18+ features utilized effectively +- TypeScript strict mode enabled properly +- Component reusability > 80% achieved +- Performance score > 95 maintained +- Test coverage > 90% implemented +- Bundle size optimized thoroughly +- Accessibility compliant consistently +- Best practices followed completely + +Advanced React patterns: +- Compound components +- Render props pattern +- Higher-order components +- Custom hooks design +- Context optimization +- Ref forwarding +- Portals usage +- Lazy loading + +State management: +- Redux Toolkit +- Zustand setup +- Jotai atoms +- Recoil patterns +- Context API +- Local state +- Server state +- URL state + +Performance optimization: +- React.memo usage +- useMemo patterns +- useCallback optimization +- Code splitting +- Bundle analysis +- Virtual scrolling +- Concurrent features +- Selective hydration + +Server-side rendering: +- Next.js integration +- Remix patterns +- Server components +- Streaming SSR +- Progressive enhancement +- SEO optimization +- Data fetching +- Hydration strategies + +Testing strategies: +- React Testing Library +- Jest configuration +- Cypress E2E +- Component testing +- Hook testing +- Integration tests +- Performance testing +- Accessibility testing + +React ecosystem: +- React Query/TanStack +- React Hook Form +- Framer Motion +- React Spring +- Material-UI +- Ant Design +- Tailwind CSS +- Styled Components + +Component patterns: +- Atomic design +- Container/presentational +- Controlled components +- Error boundaries +- Suspense boundaries +- Portal patterns +- Fragment usage +- Children patterns + +Hooks mastery: +- useState patterns +- useEffect optimization +- useContext best practices +- useReducer complex state +- useMemo calculations +- useCallback functions +- useRef DOM/values +- Custom hooks library + +Concurrent features: +- useTransition +- useDeferredValue +- Suspense for data +- Error boundaries +- Streaming HTML +- Progressive hydration +- Selective hydration +- Priority scheduling + +Migration strategies: +- Class to function components +- Legacy lifecycle methods +- State management migration +- Testing framework updates +- Build tool migration +- TypeScript adoption +- Performance upgrades +- Gradual modernization + +## Communication Protocol + +### React Context Assessment + +Initialize React development by understanding project requirements. + +React context query: +```json +{ + "requesting_agent": "react-specialist", + "request_type": "get_react_context", + "payload": { + "query": "React context needed: project type, performance requirements, state management approach, testing strategy, and deployment target." + } +} +``` + +## Development Workflow + +Execute React development through systematic phases: + +### 1. Architecture Planning + +Design scalable React architecture. + +Planning priorities: +- Component structure +- State management +- Routing strategy +- Performance goals +- Testing approach +- Build configuration +- Deployment pipeline +- Team conventions + +Architecture design: +- Define structure +- Plan components +- Design state flow +- Set performance targets +- Create testing strategy +- Configure build tools +- Setup CI/CD +- Document patterns + +### 2. Implementation Phase + +Build high-performance React applications. + +Implementation approach: +- Create components +- Implement state +- Add routing +- Optimize performance +- Write tests +- Handle errors +- Add accessibility +- Deploy application + +React patterns: +- Component composition +- State management +- Effect management +- Performance optimization +- Error handling +- Code splitting +- Progressive enhancement +- Testing coverage + +Progress tracking: +```json +{ + "agent": "react-specialist", + "status": "implementing", + "progress": { + "components_created": 47, + "test_coverage": "92%", + "performance_score": 98, + "bundle_size": "142KB" + } +} +``` + +### 3. React Excellence + +Deliver exceptional React applications. + +Excellence checklist: +- Performance optimized +- Tests comprehensive +- Accessibility complete +- Bundle minimized +- SEO optimized +- Errors handled +- Documentation clear +- Deployment smooth + +Delivery notification: +"React application completed. Created 47 components with 92% test coverage. Achieved 98 performance score with 142KB bundle size. Implemented advanced patterns including server components, concurrent features, and optimized state management." + +Performance excellence: +- Load time < 2s +- Time to interactive < 3s +- First contentful paint < 1s +- Core Web Vitals passed +- Bundle size minimal +- Code splitting effective +- Caching optimized +- CDN configured + +Testing excellence: +- Unit tests complete +- Integration tests thorough +- E2E tests reliable +- Visual regression tests +- Performance tests +- Accessibility tests +- Snapshot tests +- Coverage reports + +Architecture excellence: +- Components reusable +- State predictable +- Side effects managed +- Errors handled gracefully +- Performance monitored +- Security implemented +- Deployment automated +- Monitoring active + +Modern features: +- Server components +- Streaming SSR +- React transitions +- Concurrent rendering +- Automatic batching +- Suspense for data +- Error boundaries +- Hydration optimization + +Best practices: +- TypeScript strict +- ESLint configured +- Prettier formatting +- Husky pre-commit +- Conventional commits +- Semantic versioning +- Documentation complete +- Code reviews thorough + +Integration with other agents: +- Collaborate with frontend-developer on UI patterns +- Support fullstack-developer on React integration +- Work with typescript-pro on type safety +- Guide javascript-pro on modern JavaScript +- Help performance-engineer on optimization +- Assist qa-expert on testing strategies +- Partner with accessibility-specialist on a11y +- Coordinate with devops-engineer on deployment + +Always prioritize performance, maintainability, and user experience while building React applications that scale effectively and deliver exceptional results. \ No newline at end of file diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 1294d89..cdf09f8 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -9,7 +9,9 @@ "Bash(npm test:*)", "Bash(npm run:*)", "Bash(gh issue create:*)", - "Bash(gh label create:*)" + "Bash(gh label create:*)", + "Bash(gh issue view:*)", + "Bash(gh pr view:*)" ], "deny": [], "ask": [] diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..bd98b4f --- /dev/null +++ b/.mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "shadcn": { + "command": "npx", + "args": [ + "shadcn@latest", + "mcp" + ] + } + } +} diff --git a/package-lock.json b/package-lock.json index 6a271ee..e3003ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "@electron/typescript-definitions": "^8.15.6", "@eslint/js": "^9.39.1", "@reforged/maker-appimage": "^5.1.1", + "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.3.1", "@testing-library/user-event": "^14.6.1", "@types/electron-squirrel-startup": "^1.0.2", @@ -5281,7 +5282,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -5399,8 +5399,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -6660,7 +6659,6 @@ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -8328,7 +8326,6 @@ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -8395,8 +8392,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dom-walk": { "version": "0.1.2", @@ -12909,7 +12905,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -14687,7 +14682,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -14703,7 +14697,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -15005,8 +14998,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-konva": { "version": "19.2.1", diff --git a/package.json b/package.json index a9c95d1..e97c6ac 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "@electron/typescript-definitions": "^8.15.6", "@eslint/js": "^9.39.1", "@reforged/maker-appimage": "^5.1.1", + "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.3.1", "@testing-library/user-event": "^14.6.1", "@types/electron-squirrel-startup": "^1.0.2", diff --git a/src/components/BluetoothDevicePicker.tsx b/src/components/BluetoothDevicePicker.tsx index 6a7769f..6b8c3d1 100644 --- a/src/components/BluetoothDevicePicker.tsx +++ b/src/components/BluetoothDevicePicker.tsx @@ -17,9 +17,13 @@ export function BluetoothDevicePicker() { const { devices, isScanning } = useBluetoothDeviceListener((deviceList) => { console.log("[BluetoothPicker] Received device list:", deviceList); // Open the picker when devices are received - if (!isOpen && deviceList.length >= 0) { - setIsOpen(true); - } + // Use functional setState to avoid stale closure + setIsOpen((prevIsOpen) => { + if (!prevIsOpen && deviceList.length >= 0) { + return true; + } + return prevIsOpen; + }); }); // Close modal and reset when scan completes with no selection diff --git a/src/hooks/domain/useErrorPopoverState.ts b/src/hooks/domain/useErrorPopoverState.ts index dee0361..6c1f38c 100644 --- a/src/hooks/domain/useErrorPopoverState.ts +++ b/src/hooks/domain/useErrorPopoverState.ts @@ -67,6 +67,9 @@ export function useErrorPopoverState( const prevPyodideError = usePrevious(pyodideError); // Auto-open/close logic + // Note: This effect intentionally calls setState to synchronize popover state with error state. + // This is a valid use case for setState in an effect as we're synchronizing external state + // (error codes) with internal UI state (popover visibility). /* eslint-disable react-hooks/set-state-in-effect */ useEffect(() => { // Check if there's any error now diff --git a/src/hooks/domain/useMachinePolling.ts b/src/hooks/domain/useMachinePolling.ts index eb02880..23862b2 100644 --- a/src/hooks/domain/useMachinePolling.ts +++ b/src/hooks/domain/useMachinePolling.ts @@ -78,6 +78,26 @@ export function useMachinePolling( const serviceCountIntervalRef = useRef(null); const pollFunctionRef = useRef<(() => Promise) | undefined>(undefined); + // Store callbacks in refs to avoid unnecessary re-renders + const callbacksRef = useRef({ + onStatusRefresh, + onProgressRefresh, + onServiceCountRefresh, + onPatternInfoRefresh, + shouldCheckResumablePattern, + }); + + // Update refs when callbacks change + useEffect(() => { + callbacksRef.current = { + onStatusRefresh, + onProgressRefresh, + onServiceCountRefresh, + onPatternInfoRefresh, + shouldCheckResumablePattern, + }; + }); + // Function to determine polling interval based on machine status const getPollInterval = useCallback((status: MachineStatus) => { // Fast polling for active states @@ -99,17 +119,20 @@ export function useMachinePolling( // Main polling function const poll = useCallback(async () => { - await onStatusRefresh(); + await callbacksRef.current.onStatusRefresh(); // Refresh progress during sewing if (machineStatus === MachineStatus.SEWING) { - await onProgressRefresh(); + await callbacksRef.current.onProgressRefresh(); } // Check if we have a cached pattern and pattern info needs refreshing // This follows the app's logic for resumable patterns - if (shouldCheckResumablePattern() && patternInfo?.totalStitches === 0) { - await onPatternInfoRefresh(); + if ( + callbacksRef.current.shouldCheckResumablePattern() && + patternInfo?.totalStitches === 0 + ) { + await callbacksRef.current.onPatternInfoRefresh(); } // Schedule next poll with updated interval @@ -117,15 +140,7 @@ export function useMachinePolling( if (pollFunctionRef.current) { pollTimeoutRef.current = setTimeout(pollFunctionRef.current, newInterval); } - }, [ - machineStatus, - patternInfo, - onStatusRefresh, - onProgressRefresh, - onPatternInfoRefresh, - shouldCheckResumablePattern, - getPollInterval, - ]); + }, [machineStatus, patternInfo, getPollInterval]); // Store poll function in ref for recursive setTimeout useEffect(() => { @@ -155,16 +170,13 @@ export function useMachinePolling( pollTimeoutRef.current = setTimeout(poll, initialInterval); // Start service count polling (every 10 seconds) - serviceCountIntervalRef.current = setInterval(onServiceCountRefresh, 10000); + serviceCountIntervalRef.current = setInterval( + callbacksRef.current.onServiceCountRefresh, + 10000, + ); setIsPolling(true); - }, [ - machineStatus, - poll, - stopPolling, - getPollInterval, - onServiceCountRefresh, - ]); + }, [machineStatus, poll, stopPolling, getPollInterval]); return { startPolling, diff --git a/src/hooks/platform/useBluetoothDeviceListener.ts b/src/hooks/platform/useBluetoothDeviceListener.ts index 850cadf..faf8579 100644 --- a/src/hooks/platform/useBluetoothDeviceListener.ts +++ b/src/hooks/platform/useBluetoothDeviceListener.ts @@ -33,7 +33,7 @@ * ``` */ -import { useEffect, useState } from "react"; +import { useEffect, useState, useRef } from "react"; import type { BluetoothDevice } from "../../types/electron"; export interface UseBluetoothDeviceListenerReturn { @@ -48,6 +48,14 @@ export function useBluetoothDeviceListener( const [devices, setDevices] = useState([]); const [isScanning, setIsScanning] = useState(false); + // Store callback in ref to avoid re-registering listener + const callbackRef = useRef(onDevicesChanged); + + // Update ref when callback changes + useEffect(() => { + callbackRef.current = onDevicesChanged; + }); + // Check if Electron API is available const isSupported = typeof window !== "undefined" && @@ -70,17 +78,17 @@ export function useBluetoothDeviceListener( setIsScanning(false); } - // Call optional callback - onDevicesChanged?.(deviceList); + // Call optional callback using ref to get latest version + callbackRef.current?.(deviceList); }; - // Register listener + // Register listener only once window.electronAPI!.onBluetoothDeviceList(handleDeviceList); // Note: Electron IPC listeners are typically not cleaned up individually // as they're meant to persist. If cleanup is needed, the Electron main // process should handle it. - }, [isSupported, onDevicesChanged]); + }, [isSupported]); return { devices, diff --git a/src/hooks/utility/useAutoScroll.ts b/src/hooks/utility/useAutoScroll.ts index 73a6ae3..5f0cf00 100644 --- a/src/hooks/utility/useAutoScroll.ts +++ b/src/hooks/utility/useAutoScroll.ts @@ -23,7 +23,7 @@ * ``` */ -import { useEffect, useRef, type RefObject } from "react"; +import { useEffect, useRef, useMemo, type RefObject } from "react"; export interface UseAutoScrollOptions { behavior?: ScrollBehavior; @@ -37,15 +37,21 @@ export function useAutoScroll( ): RefObject { const ref = useRef(null); + // Stabilize options to avoid unnecessary re-renders when passed as inline object + const stableOptions = useMemo( + () => ({ + behavior: options?.behavior || "smooth", + block: options?.block || "nearest", + inline: options?.inline, + }), + [options?.behavior, options?.block, options?.inline], + ); + useEffect(() => { if (ref.current) { - ref.current.scrollIntoView({ - behavior: options?.behavior || "smooth", - block: options?.block || "nearest", - inline: options?.inline, - }); + ref.current.scrollIntoView(stableOptions); } - }, [dependency, options?.behavior, options?.block, options?.inline]); + }, [dependency, stableOptions]); return ref; }