mirror of
https://github.com/jhbruhn/respira.git
synced 2026-01-27 02:13:41 +00:00
fix: Address GitHub Copilot review feedback
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>
This commit is contained in:
parent
eff8e15179
commit
d98a19bb4b
10 changed files with 374 additions and 49 deletions
286
.claude/agents/react-specialist.md
Normal file
286
.claude/agents/react-specialist.md
Normal file
|
|
@ -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.
|
||||||
|
|
@ -9,7 +9,9 @@
|
||||||
"Bash(npm test:*)",
|
"Bash(npm test:*)",
|
||||||
"Bash(npm run:*)",
|
"Bash(npm run:*)",
|
||||||
"Bash(gh issue create:*)",
|
"Bash(gh issue create:*)",
|
||||||
"Bash(gh label create:*)"
|
"Bash(gh label create:*)",
|
||||||
|
"Bash(gh issue view:*)",
|
||||||
|
"Bash(gh pr view:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|
|
||||||
11
.mcp.json
Normal file
11
.mcp.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"shadcn": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"shadcn@latest",
|
||||||
|
"mcp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
package-lock.json
generated
16
package-lock.json
generated
|
|
@ -48,6 +48,7 @@
|
||||||
"@electron/typescript-definitions": "^8.15.6",
|
"@electron/typescript-definitions": "^8.15.6",
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
"@reforged/maker-appimage": "^5.1.1",
|
"@reforged/maker-appimage": "^5.1.1",
|
||||||
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/react": "^16.3.1",
|
"@testing-library/react": "^16.3.1",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/electron-squirrel-startup": "^1.0.2",
|
"@types/electron-squirrel-startup": "^1.0.2",
|
||||||
|
|
@ -5281,7 +5282,6 @@
|
||||||
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.10.4",
|
"@babel/code-frame": "^7.10.4",
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
|
|
@ -5399,8 +5399,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
|
|
@ -6660,7 +6659,6 @@
|
||||||
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
|
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dequal": "^2.0.3"
|
"dequal": "^2.0.3"
|
||||||
}
|
}
|
||||||
|
|
@ -8328,7 +8326,6 @@
|
||||||
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
|
|
@ -8395,8 +8392,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/dom-walk": {
|
"node_modules/dom-walk": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
|
|
@ -12909,7 +12905,6 @@
|
||||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"lz-string": "bin/bin.js"
|
"lz-string": "bin/bin.js"
|
||||||
}
|
}
|
||||||
|
|
@ -14687,7 +14682,6 @@
|
||||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^5.0.1",
|
"ansi-regex": "^5.0.1",
|
||||||
"ansi-styles": "^5.0.0",
|
"ansi-styles": "^5.0.0",
|
||||||
|
|
@ -14703,7 +14697,6 @@
|
||||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
},
|
},
|
||||||
|
|
@ -15005,8 +14998,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/react-konva": {
|
"node_modules/react-konva": {
|
||||||
"version": "19.2.1",
|
"version": "19.2.1",
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@
|
||||||
"@electron/typescript-definitions": "^8.15.6",
|
"@electron/typescript-definitions": "^8.15.6",
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
"@reforged/maker-appimage": "^5.1.1",
|
"@reforged/maker-appimage": "^5.1.1",
|
||||||
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@testing-library/react": "^16.3.1",
|
"@testing-library/react": "^16.3.1",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/electron-squirrel-startup": "^1.0.2",
|
"@types/electron-squirrel-startup": "^1.0.2",
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,13 @@ export function BluetoothDevicePicker() {
|
||||||
const { devices, isScanning } = useBluetoothDeviceListener((deviceList) => {
|
const { devices, isScanning } = useBluetoothDeviceListener((deviceList) => {
|
||||||
console.log("[BluetoothPicker] Received device list:", deviceList);
|
console.log("[BluetoothPicker] Received device list:", deviceList);
|
||||||
// Open the picker when devices are received
|
// Open the picker when devices are received
|
||||||
if (!isOpen && deviceList.length >= 0) {
|
// Use functional setState to avoid stale closure
|
||||||
setIsOpen(true);
|
setIsOpen((prevIsOpen) => {
|
||||||
}
|
if (!prevIsOpen && deviceList.length >= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return prevIsOpen;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close modal and reset when scan completes with no selection
|
// Close modal and reset when scan completes with no selection
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,9 @@ export function useErrorPopoverState(
|
||||||
const prevPyodideError = usePrevious(pyodideError);
|
const prevPyodideError = usePrevious(pyodideError);
|
||||||
|
|
||||||
// Auto-open/close logic
|
// 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 */
|
/* eslint-disable react-hooks/set-state-in-effect */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Check if there's any error now
|
// Check if there's any error now
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,26 @@ export function useMachinePolling(
|
||||||
const serviceCountIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
const serviceCountIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const pollFunctionRef = useRef<(() => Promise<void>) | undefined>(undefined);
|
const pollFunctionRef = useRef<(() => Promise<void>) | 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
|
// Function to determine polling interval based on machine status
|
||||||
const getPollInterval = useCallback((status: MachineStatus) => {
|
const getPollInterval = useCallback((status: MachineStatus) => {
|
||||||
// Fast polling for active states
|
// Fast polling for active states
|
||||||
|
|
@ -99,17 +119,20 @@ export function useMachinePolling(
|
||||||
|
|
||||||
// Main polling function
|
// Main polling function
|
||||||
const poll = useCallback(async () => {
|
const poll = useCallback(async () => {
|
||||||
await onStatusRefresh();
|
await callbacksRef.current.onStatusRefresh();
|
||||||
|
|
||||||
// Refresh progress during sewing
|
// Refresh progress during sewing
|
||||||
if (machineStatus === MachineStatus.SEWING) {
|
if (machineStatus === MachineStatus.SEWING) {
|
||||||
await onProgressRefresh();
|
await callbacksRef.current.onProgressRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have a cached pattern and pattern info needs refreshing
|
// Check if we have a cached pattern and pattern info needs refreshing
|
||||||
// This follows the app's logic for resumable patterns
|
// This follows the app's logic for resumable patterns
|
||||||
if (shouldCheckResumablePattern() && patternInfo?.totalStitches === 0) {
|
if (
|
||||||
await onPatternInfoRefresh();
|
callbacksRef.current.shouldCheckResumablePattern() &&
|
||||||
|
patternInfo?.totalStitches === 0
|
||||||
|
) {
|
||||||
|
await callbacksRef.current.onPatternInfoRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schedule next poll with updated interval
|
// Schedule next poll with updated interval
|
||||||
|
|
@ -117,15 +140,7 @@ export function useMachinePolling(
|
||||||
if (pollFunctionRef.current) {
|
if (pollFunctionRef.current) {
|
||||||
pollTimeoutRef.current = setTimeout(pollFunctionRef.current, newInterval);
|
pollTimeoutRef.current = setTimeout(pollFunctionRef.current, newInterval);
|
||||||
}
|
}
|
||||||
}, [
|
}, [machineStatus, patternInfo, getPollInterval]);
|
||||||
machineStatus,
|
|
||||||
patternInfo,
|
|
||||||
onStatusRefresh,
|
|
||||||
onProgressRefresh,
|
|
||||||
onPatternInfoRefresh,
|
|
||||||
shouldCheckResumablePattern,
|
|
||||||
getPollInterval,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Store poll function in ref for recursive setTimeout
|
// Store poll function in ref for recursive setTimeout
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -155,16 +170,13 @@ export function useMachinePolling(
|
||||||
pollTimeoutRef.current = setTimeout(poll, initialInterval);
|
pollTimeoutRef.current = setTimeout(poll, initialInterval);
|
||||||
|
|
||||||
// Start service count polling (every 10 seconds)
|
// Start service count polling (every 10 seconds)
|
||||||
serviceCountIntervalRef.current = setInterval(onServiceCountRefresh, 10000);
|
serviceCountIntervalRef.current = setInterval(
|
||||||
|
callbacksRef.current.onServiceCountRefresh,
|
||||||
|
10000,
|
||||||
|
);
|
||||||
|
|
||||||
setIsPolling(true);
|
setIsPolling(true);
|
||||||
}, [
|
}, [machineStatus, poll, stopPolling, getPollInterval]);
|
||||||
machineStatus,
|
|
||||||
poll,
|
|
||||||
stopPolling,
|
|
||||||
getPollInterval,
|
|
||||||
onServiceCountRefresh,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
startPolling,
|
startPolling,
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
import type { BluetoothDevice } from "../../types/electron";
|
import type { BluetoothDevice } from "../../types/electron";
|
||||||
|
|
||||||
export interface UseBluetoothDeviceListenerReturn {
|
export interface UseBluetoothDeviceListenerReturn {
|
||||||
|
|
@ -48,6 +48,14 @@ export function useBluetoothDeviceListener(
|
||||||
const [devices, setDevices] = useState<BluetoothDevice[]>([]);
|
const [devices, setDevices] = useState<BluetoothDevice[]>([]);
|
||||||
const [isScanning, setIsScanning] = useState(false);
|
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
|
// Check if Electron API is available
|
||||||
const isSupported =
|
const isSupported =
|
||||||
typeof window !== "undefined" &&
|
typeof window !== "undefined" &&
|
||||||
|
|
@ -70,17 +78,17 @@ export function useBluetoothDeviceListener(
|
||||||
setIsScanning(false);
|
setIsScanning(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call optional callback
|
// Call optional callback using ref to get latest version
|
||||||
onDevicesChanged?.(deviceList);
|
callbackRef.current?.(deviceList);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Register listener
|
// Register listener only once
|
||||||
window.electronAPI!.onBluetoothDeviceList(handleDeviceList);
|
window.electronAPI!.onBluetoothDeviceList(handleDeviceList);
|
||||||
|
|
||||||
// Note: Electron IPC listeners are typically not cleaned up individually
|
// Note: Electron IPC listeners are typically not cleaned up individually
|
||||||
// as they're meant to persist. If cleanup is needed, the Electron main
|
// as they're meant to persist. If cleanup is needed, the Electron main
|
||||||
// process should handle it.
|
// process should handle it.
|
||||||
}, [isSupported, onDevicesChanged]);
|
}, [isSupported]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
devices,
|
devices,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useEffect, useRef, type RefObject } from "react";
|
import { useEffect, useRef, useMemo, type RefObject } from "react";
|
||||||
|
|
||||||
export interface UseAutoScrollOptions {
|
export interface UseAutoScrollOptions {
|
||||||
behavior?: ScrollBehavior;
|
behavior?: ScrollBehavior;
|
||||||
|
|
@ -37,15 +37,21 @@ export function useAutoScroll<T extends HTMLElement = HTMLElement>(
|
||||||
): RefObject<T | null> {
|
): RefObject<T | null> {
|
||||||
const ref = useRef<T>(null);
|
const ref = useRef<T>(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(() => {
|
useEffect(() => {
|
||||||
if (ref.current) {
|
if (ref.current) {
|
||||||
ref.current.scrollIntoView({
|
ref.current.scrollIntoView(stableOptions);
|
||||||
behavior: options?.behavior || "smooth",
|
|
||||||
block: options?.block || "nearest",
|
|
||||||
inline: options?.inline,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [dependency, options?.behavior, options?.block, options?.inline]);
|
}, [dependency, stableOptions]);
|
||||||
|
|
||||||
return ref;
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue