Files
claudetools/imported-conversations/auto-claude-variants/autocode-remix-fork/d5905662-519d-4470-938d-665bf081ec4c/tool-results/toolu_018tRxtsnttgM29M2NZBRtTK.txt
Mike Swanson 75ce1c2fd5 feat: Add Sequential Thinking to Code Review + Frontend Validation
Enhanced code review and frontend validation with intelligent triggers:

Code Review Agent Enhancement:
- Added Sequential Thinking MCP integration for complex issues
- Triggers on 2+ rejections or 3+ critical issues
- New escalation format with root cause analysis
- Comprehensive solution strategies with trade-off evaluation
- Educational feedback to break rejection cycles
- Files: .claude/agents/code-review.md (+308 lines)
- Docs: CODE_REVIEW_ST_ENHANCEMENT.md, CODE_REVIEW_ST_TESTING.md

Frontend Design Skill Enhancement:
- Automatic invocation for ANY UI change
- Comprehensive validation checklist (200+ checkpoints)
- 8 validation categories (visual, interactive, responsive, a11y, etc.)
- 3 validation levels (quick, standard, comprehensive)
- Integration with code review workflow
- Files: .claude/skills/frontend-design/SKILL.md (+120 lines)
- Docs: UI_VALIDATION_CHECKLIST.md (462 lines), AUTOMATIC_VALIDATION_ENHANCEMENT.md (587 lines)

Settings Optimization:
- Repaired .claude/settings.local.json (fixed m365 pattern)
- Reduced permissions from 49 to 33 (33% reduction)
- Removed duplicates, sorted alphabetically
- Created SETTINGS_PERMISSIONS.md documentation

Checkpoint Command Enhancement:
- Dual checkpoint system (git + database)
- Saves session context to API for cross-machine recall
- Includes git metadata in database context
- Files: .claude/commands/checkpoint.md (+139 lines)

Decision Rationale:
- Sequential Thinking MCP breaks rejection cycles by identifying root causes
- Automatic frontend validation catches UI issues before code review
- Dual checkpoints enable complete project memory across machines
- Settings optimization improves maintainability

Total: 1,200+ lines of documentation and enhancements

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-17 16:23:52 -07:00

296 lines
11 KiB
Plaintext

1→/**
2→ * WebSocket Hook
3→ * ==============
4→ *
5→ * Custom React hook for managing WebSocket connections with auto-reconnect.
6→ */
7→
8→import { useState, useEffect, useRef, useCallback } from 'react';
9→
10→export type WebSocketStatus = 'connecting' | 'connected' | 'disconnected' | 'reconnecting';
11→
12→export interface WebSocketMessage {
13→ type: string;
14→ [key: string]: unknown;
15→}
16→
17→export interface UseWebSocketOptions {
18→ /** Project name to connect to */
19→ projectName: string | null;
20→ /** Called when a message is received */
21→ onMessage?: (message: WebSocketMessage) => void;
22→ /** Called when connection status changes */
23→ onStatusChange?: (status: WebSocketStatus) => void;
24→ /** Enable auto-reconnect (default: true) */
25→ autoReconnect?: boolean;
26→ /** Initial reconnect delay in ms (default: 1000) */
27→ reconnectDelay?: number;
28→ /** Maximum reconnect delay in ms (default: 30000) */
29→ maxReconnectDelay?: number;
30→ /** Reconnect delay multiplier (default: 2) */
31→ reconnectMultiplier?: number;
32→ /** Maximum number of reconnect attempts (default: 10, 0 = infinite) */
33→ maxReconnectAttempts?: number;
34→ /** Heartbeat interval in ms (default: 25000) */
35→ heartbeatInterval?: number;
36→}
37→
38→export interface UseWebSocketReturn {
39→ /** Current connection status */
40→ status: WebSocketStatus;
41→ /** Send a message through the WebSocket */
42→ sendMessage: (message: WebSocketMessage) => void;
43→ /** Manually disconnect */
44→ disconnect: () => void;
45→ /** Manually reconnect */
46→ reconnect: () => void;
47→ /** Last received message */
48→ lastMessage: WebSocketMessage | null;
49→ /** Number of reconnect attempts */
50→ reconnectAttempts: number;
51→ /** Last error message */
52→ error: string | null;
53→}
54→
55→export function useWebSocket(options: UseWebSocketOptions): UseWebSocketReturn {
56→ const {
57→ projectName,
58→ onMessage,
59→ onStatusChange,
60→ autoReconnect = true,
61→ reconnectDelay = 1000,
62→ maxReconnectDelay = 30000,
63→ reconnectMultiplier = 2,
64→ maxReconnectAttempts = 10,
65→ heartbeatInterval = 25000,
66→ } = options;
67→
68→ const [status, setStatus] = useState<WebSocketStatus>('disconnected');
69→ const [lastMessage, setLastMessage] = useState<WebSocketMessage | null>(null);
70→ const [reconnectAttempts, setReconnectAttempts] = useState(0);
71→ const [error, setError] = useState<string | null>(null);
72→
73→ const wsRef = useRef<WebSocket | null>(null);
74→ const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
75→ const heartbeatIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
76→ const currentDelayRef = useRef(reconnectDelay);
77→ const shouldReconnectRef = useRef(true);
78→ const isManualDisconnectRef = useRef(false);
79→
80→ // Update status and notify callback
81→ const updateStatus = useCallback((newStatus: WebSocketStatus) => {
82→ setStatus(newStatus);
83→ onStatusChange?.(newStatus);
84→ }, [onStatusChange]);
85→
86→ // Clear all timers
87→ const clearTimers = useCallback(() => {
88→ if (reconnectTimeoutRef.current) {
89→ clearTimeout(reconnectTimeoutRef.current);
90→ reconnectTimeoutRef.current = null;
91→ }
92→ if (heartbeatIntervalRef.current) {
93→ clearInterval(heartbeatIntervalRef.current);
94→ heartbeatIntervalRef.current = null;
95→ }
96→ }, []);
97→
98→ // Start heartbeat ping
99→ const startHeartbeat = useCallback(() => {
100→ if (heartbeatIntervalRef.current) {
101→ clearInterval(heartbeatIntervalRef.current);
102→ }
103→ heartbeatIntervalRef.current = setInterval(() => {
104→ if (wsRef.current?.readyState === WebSocket.OPEN) {
105→ wsRef.current.send(JSON.stringify({ type: 'ping' }));
106→ }
107→ }, heartbeatInterval);
108→ }, [heartbeatInterval]);
109→
110→ // Connect to WebSocket
111→ const connect = useCallback(() => {
112→ if (!projectName) {
113→ return;
114→ }
115→
116→ // Don't connect if already connected or connecting
117→ if (wsRef.current?.readyState === WebSocket.OPEN ||
118→ wsRef.current?.readyState === WebSocket.CONNECTING) {
119→ return;
120→ }
121→
122→ clearTimers();
123→ setError(null);
124→
125→ // Determine WebSocket URL based on current location
126→ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
127→ const wsUrl = `${protocol}//${window.location.host}/ws/projects/${projectName}`;
128→
129→ updateStatus('connecting');
130→
131→ try {
132→ const ws = new WebSocket(wsUrl);
133→ wsRef.current = ws;
134→
135→ ws.onopen = () => {
136→ updateStatus('connected');
137→ setReconnectAttempts(0);
138→ currentDelayRef.current = reconnectDelay;
139→ setError(null);
140→ startHeartbeat();
141→ };
142→
143→ ws.onmessage = (event) => {
144→ try {
145→ const message = JSON.parse(event.data) as WebSocketMessage;
146→ setLastMessage(message);
147→
148→ // Don't call onMessage for heartbeat/pong - those are internal
149→ if (message.type !== 'heartbeat' && message.type !== 'pong') {
150→ onMessage?.(message);
151→ }
152→ } catch (err) {
153→ console.error('Failed to parse WebSocket message:', err);
154→ }
155→ };
156→
157→ ws.onerror = () => {
158→ setError('WebSocket connection error');
159→ };
160→
161→ ws.onclose = (event) => {
162→ clearTimers();
163→ wsRef.current = null;
164→
165→ // Don't reconnect if manually disconnected
166→ if (isManualDisconnectRef.current) {
167→ updateStatus('disconnected');
168→ isManualDisconnectRef.current = false;
169→ return;
170→ }
171→
172→ // Check if we should attempt to reconnect
173→ if (autoReconnect && shouldReconnectRef.current) {
174→ if (maxReconnectAttempts === 0 || reconnectAttempts < maxReconnectAttempts) {
175→ updateStatus('reconnecting');
176→
177→ // Schedule reconnection with exponential backoff
178→ const delay = Math.min(currentDelayRef.current, maxReconnectDelay);
179→
180→ reconnectTimeoutRef.current = setTimeout(() => {
181→ setReconnectAttempts(prev => prev + 1);
182→ currentDelayRef.current = Math.min(
183→ currentDelayRef.current * reconnectMultiplier,
184→ maxReconnectDelay
185→ );
186→ connect();
187→ }, delay);
188→ } else {
189→ setError(`Max reconnect attempts (${maxReconnectAttempts}) reached`);
190→ updateStatus('disconnected');
191→ }
192→ } else {
193→ updateStatus('disconnected');
194→ }
195→
196→ // Include close code in error if abnormal closure
197→ if (event.code !== 1000 && event.code !== 1001) {
198→ setError(`WebSocket closed: ${event.reason || 'Unknown reason'} (code: ${event.code})`);
199→ }
200→ };
201→ } catch (err) {
202→ setError(`Failed to create WebSocket: ${err}`);
203→ updateStatus('disconnected');
204→ }
205→ }, [
206→ projectName,
207→ autoReconnect,
208→ reconnectDelay,
209→ maxReconnectDelay,
210→ reconnectMultiplier,
211→ maxReconnectAttempts,
212→ reconnectAttempts,
213→ onMessage,
214→ updateStatus,
215→ clearTimers,
216→ startHeartbeat
217→ ]);
218→
219→ // Send message through WebSocket
220→ const sendMessage = useCallback((message: WebSocketMessage) => {
221→ if (wsRef.current?.readyState === WebSocket.OPEN) {
222→ wsRef.current.send(JSON.stringify(message));
223→ } else {
224→ console.warn('Cannot send message: WebSocket is not connected');
225→ }
226→ }, []);
227→
228→ // Manual disconnect
229→ const disconnect = useCallback(() => {
230→ shouldReconnectRef.current = false;
231→ isManualDisconnectRef.current = true;
232→ clearTimers();
233→ if (wsRef.current) {
234→ wsRef.current.close(1000, 'Client disconnected');
235→ wsRef.current = null;
236→ }
237→ setReconnectAttempts(0);
238→ currentDelayRef.current = reconnectDelay;
239→ updateStatus('disconnected');
240→ }, [clearTimers, reconnectDelay, updateStatus]);
241→
242→ // Manual reconnect
243→ const reconnect = useCallback(() => {
244→ shouldReconnectRef.current = true;
245→ isManualDisconnectRef.current = false;
246→ setReconnectAttempts(0);
247→ currentDelayRef.current = reconnectDelay;
248→
249→ // Close existing connection if any
250→ if (wsRef.current) {
251→ wsRef.current.close();
252→ wsRef.current = null;
253→ }
254→
255→ connect();
256→ }, [connect, reconnectDelay]);
257→
258→ // Connect when projectName changes
259→ useEffect(() => {
260→ if (projectName) {
261→ shouldReconnectRef.current = true;
262→ isManualDisconnectRef.current = false;
263→ connect();
264→ } else {
265→ disconnect();
266→ }
267→
268→ return () => {
269→ // Cleanup on unmount
270→ shouldReconnectRef.current = false;
271→ clearTimers();
272→ if (wsRef.current) {
273→ wsRef.current.close(1000, 'Component unmounted');
274→ wsRef.current = null;
275→ }
276→ };
277→ }, [projectName]); // Only depend on projectName to avoid reconnect loops
278→
279→ return {
280→ status,
281→ sendMessage,
282→ disconnect,
283→ reconnect,
284→ lastMessage,
285→ reconnectAttempts,
286→ error,
287→ };
288→}
289→
290→export default useWebSocket;
291→
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>