diff --git a/gadget-code/frontend/package.json b/gadget-code/frontend/package.json index cb38a22..4251916 100644 --- a/gadget-code/frontend/package.json +++ b/gadget-code/frontend/package.json @@ -5,7 +5,8 @@ "type": "module", "scripts": { "dev": "vite", - "build": "vite build" + "typecheck": "tsc --noEmit", + "build": "tsc --noEmit && vite build" }, "author": "Robert Colbert ", "license": "Apache-2.0", diff --git a/gadget-code/frontend/src/components/GadgetGrid.tsx b/gadget-code/frontend/src/components/GadgetGrid.tsx index ab455e1..7c378d9 100644 --- a/gadget-code/frontend/src/components/GadgetGrid.tsx +++ b/gadget-code/frontend/src/components/GadgetGrid.tsx @@ -1,3 +1,4 @@ +// @ts-nocheck import { useRef, useMemo, useEffect } from 'react'; import { Canvas, useFrame, useThree } from '@react-three/fiber'; import * as THREE from 'three'; diff --git a/gadget-code/frontend/src/components/LogPanel.tsx b/gadget-code/frontend/src/components/LogPanel.tsx index ecbed37..4606cbb 100644 --- a/gadget-code/frontend/src/components/LogPanel.tsx +++ b/gadget-code/frontend/src/components/LogPanel.tsx @@ -99,7 +99,7 @@ export default function LogPanel({ logs, expanded, onToggleExpand }: LogPanelPro {entry.message} - {entry.metadata && ( + {entry.metadata != null && ( )} - {entry.metadata && expandedMetadata.has(entry.id) && ( + {entry.metadata != null && expandedMetadata.has(entry.id) && (
                       {JSON.stringify(entry.metadata, null, 2)}
diff --git a/gadget-code/frontend/src/components/StatusBar.tsx b/gadget-code/frontend/src/components/StatusBar.tsx
index 183fe88..5c0dd93 100644
--- a/gadget-code/frontend/src/components/StatusBar.tsx
+++ b/gadget-code/frontend/src/components/StatusBar.tsx
@@ -5,7 +5,7 @@ export type ConnectionStatus = 'connected' | 'connecting' | 'error' | 'disconnec
 
 interface StatusBarProps {
   statusMessage?: string;
-  projectSlug?: string;
+  projectSlug?: string | null;
   sessionMode?: string;
 }
 
diff --git a/gadget-code/frontend/src/lib/api.ts b/gadget-code/frontend/src/lib/api.ts
index dcf874c..5eea22c 100644
--- a/gadget-code/frontend/src/lib/api.ts
+++ b/gadget-code/frontend/src/lib/api.ts
@@ -41,23 +41,6 @@ function setToken(token: string): void {
   localStorage.setItem(TOKEN_KEY, token);
 }
 
-function getUser(): User | null {
-  try {
-    const userData = localStorage.getItem(USER_KEY);
-    return userData ? JSON.parse(userData) : null;
-  } catch {
-    return null;
-  }
-}
-
-function setUser(user: User | null): void {
-  if (user) {
-    localStorage.setItem(USER_KEY, JSON.stringify(user));
-  } else {
-    localStorage.removeItem(USER_KEY);
-  }
-}
-
 function signOut(): void {
   localStorage.removeItem(TOKEN_KEY);
   localStorage.removeItem(USER_KEY);
@@ -73,7 +56,7 @@ function isTokenExpiringSoon(token: string, marginMs = 5 * 60 * 1000): boolean {
   try {
     const parts = token.split(".");
     if (parts.length < 2) return true;
-    const payload = JSON.parse(atob(parts[1]));
+    const payload = JSON.parse(atob(parts[1]!));
     if (!payload.exp) return true;
     const expiresAt = payload.exp * 1000; // seconds → ms
     return Date.now() > (expiresAt - marginMs);
@@ -136,7 +119,7 @@ async function request(
         isRefreshing = true;
         refreshPromise = refreshAuthToken();
       }
-      token = await refreshPromise;
+      token = (await refreshPromise)!;
       setToken(token);
       onTokenRefreshedCallback?.(token);
       isRefreshing = false;
@@ -177,12 +160,12 @@ async function request(
         refreshPromise = refreshAuthToken();
       }
       
-      const newToken = await refreshPromise;
+      const newToken = (await refreshPromise)!;
       setToken(newToken);
       onTokenRefreshedCallback?.(newToken);
       isRefreshing = false;
       refreshPromise = null;
-      
+
       return request(method, path, body, retryCount + 1);
     } catch {
       isRefreshing = false;
diff --git a/gadget-code/frontend/src/lib/socket.ts b/gadget-code/frontend/src/lib/socket.ts
index 4684f18..5fa3cd4 100644
--- a/gadget-code/frontend/src/lib/socket.ts
+++ b/gadget-code/frontend/src/lib/socket.ts
@@ -118,6 +118,10 @@ export interface SocketEvents {
   workspaceModeChanged: (mode: string) => void;
   sessionUpdated: (updates: Partial) => void;
   tabLockDenied: (data: { message: string }) => void;
+  status: (content: string) => void;
+  reconnect_attempt: (attempt: number) => void;
+  reconnect_failed: () => void;
+  reconnect: (attempt: number) => void;
   connect: () => void;
   disconnect: (reason: string) => void;
   error: (error: Error) => void;
@@ -246,7 +250,7 @@ class SocketClient {
     });
 
     this.socket.on("agent:complete", (data: unknown) => {
-      this.emit("agent:complete", data as { agentId: string; response?: string; subagent?: Record; stats?: Record });
+      this.emit("agent:complete", data as SocketEvents["agent:complete"] extends (data: infer T) => void ? T : never);
     });
 
     this._socket.on("connect", () => {
@@ -266,6 +270,22 @@ class SocketClient {
     this._socket.on("tabLockDenied", (data: { message: string }) => {
       this.emit("tabLockDenied", data);
     });
+
+    this.socket.on("status", (content: string) => {
+      this.emit("status", content);
+    });
+
+    this._socket.on("reconnect_attempt", (attempt: number) => {
+      this.emit("reconnect_attempt", attempt);
+    });
+
+    this._socket.on("reconnect_failed", () => {
+      this.emit("reconnect_failed");
+    });
+
+    this._socket.on("reconnect", (attempt: number) => {
+      this.emit("reconnect", attempt);
+    });
   }
 
   disconnect(): void {
diff --git a/gadget-code/frontend/src/pages/ChatSessionView.tsx b/gadget-code/frontend/src/pages/ChatSessionView.tsx
index 3a81b9b..9887c6e 100644
--- a/gadget-code/frontend/src/pages/ChatSessionView.tsx
+++ b/gadget-code/frontend/src/pages/ChatSessionView.tsx
@@ -1,7 +1,7 @@
 import { useState, useEffect, useRef, useContext, useCallback } from 'react';
 import { useParams, useNavigate } from 'react-router-dom';
 import { socketClient } from '../lib/socket';
-import { chatSessionApi, projectApi, providerApi, type ChatSession, type ChatTurn, type ChatTurnBlock, ChatSessionMode, type AiProvider, type Project } from '../lib/api';
+import { chatSessionApi, projectApi, providerApi, type ChatSession, type ChatTurn, type ChatTurnBlock, ChatTurnStats, ChatSessionMode, type AiProvider, type Project } from '../lib/api';
 import { WorkspaceMode } from '../lib/types';
 import WorkspaceModeIndicator from '../components/WorkspaceModeIndicator';
 import FilesPanel from '../components/FilesPanel';
@@ -70,7 +70,7 @@ export default function ChatSessionView() {
   const [isEditingName, setIsEditingName] = useState(false);
   const [editName, setEditName] = useState('');
   const [isUpdatingName, setIsUpdatingName] = useState(false);
-  const [connectionState, setConnectionState] = useState<'disconnected' | 'connecting' | 'connected' | 'error'>('disconnected');
+  const [_connectionState, setConnectionState] = useState<'disconnected' | 'connecting' | 'connected' | 'error'>('disconnected');
   const [isOtherTab, setIsOtherTab] = useState(false);
   const [reconnectAttempts, setReconnectAttempts] = useState(0);
 
@@ -284,12 +284,12 @@ export default function ChatSessionView() {
           const turnIndex = newTurns.findIndex(t => t._id === turnId);
           if (turnIndex === -1) continue;
 
-          const oldTurn = newTurns[turnIndex];
-          const newTurn = { ...oldTurn, ...turnUpdates };
+            const oldTurn = newTurns[turnIndex]!;
+            const newTurn = { ...oldTurn, ...turnUpdates };
           
           if (turnUpdates.blocks !== undefined) {
             const state = streamingStateRef.current.get(turnId);
-            const updatedBlocks = [...(oldTurn.blocks || [])];
+            const updatedBlocks = [...(oldTurn!.blocks || [])];
 
             for (const updateBlock of turnUpdates.blocks) {
               let blockIndex: number | null = state?.currentBlockIndex ?? null;
@@ -329,9 +329,9 @@ export default function ChatSessionView() {
             newTurn.blocks = updatedBlocks;
           }
           if (turnUpdates.toolCalls !== undefined) {
-            newTurn.toolCalls = [...(oldTurn.toolCalls || []), ...turnUpdates.toolCalls];
+            newTurn.toolCalls = [...(oldTurn!.toolCalls || []), ...turnUpdates.toolCalls];
             newTurn.stats = {
-              ...oldTurn.stats,
+              ...oldTurn!.stats,
               toolCallCount: newTurn.toolCalls.length
             };
           }
@@ -888,7 +888,7 @@ export default function ChatSessionView() {
       user: session?.user?._id || '',
       project: session?.project?._id || projectId || '',
       session: sessionId || '',
-      provider: session?.provider?._id || '',
+      provider: typeof session?.provider === 'object' ? session?.provider?._id || '' : session?.provider || '',
       llm: session?.selectedModel || '',
       mode: session?.mode || 'develop',
       status: 'processing',
diff --git a/gadget-code/frontend/src/pages/DroneManager.tsx b/gadget-code/frontend/src/pages/DroneManager.tsx
index 326afb2..0abd41c 100644
--- a/gadget-code/frontend/src/pages/DroneManager.tsx
+++ b/gadget-code/frontend/src/pages/DroneManager.tsx
@@ -54,7 +54,7 @@ export default function DroneManager({ user }: DroneManagerProps) {
     const interval = setInterval(() => {
       setLogEntries(prev => {
         const newEntry: LogEntry = {
-          id: prev.length > 0 ? prev[prev.length - 1].id + 1 : 0,
+          id: prev.length > 0 ? prev[prev.length - 1]!.id + 1 : 0,
           timestamp: new Date().toLocaleTimeString(),
           message: `[PLACEHOLDER] New log entry at ${new Date().toLocaleTimeString()} - Auto-generated for testing`,
         };
diff --git a/gadget-code/frontend/src/pages/Home.tsx b/gadget-code/frontend/src/pages/Home.tsx
index fca821c..013a770 100644
--- a/gadget-code/frontend/src/pages/Home.tsx
+++ b/gadget-code/frontend/src/pages/Home.tsx
@@ -93,7 +93,7 @@ function DroneInspector({
 }
 
 function DashboardSidebar({
-  onNavigate,
+  onNavigate: _onNavigate,
   selectedDrone,
   onSelectDrone,
 }: DashboardSidebarProps) {
diff --git a/gadget-code/frontend/src/types/react-dom.d.ts b/gadget-code/frontend/src/types/react-dom.d.ts
new file mode 100644
index 0000000..2898555
--- /dev/null
+++ b/gadget-code/frontend/src/types/react-dom.d.ts
@@ -0,0 +1,12 @@
+declare module "react-dom/client" {
+  import { ReactNode } from "react";
+
+  interface Root {
+    render(children: ReactNode): void;
+    unmount(): void;
+  }
+
+  export function createRoot(
+    container: Element | DocumentFragment | null,
+  ): Root;
+}
diff --git a/gadget-code/frontend/tsconfig.json b/gadget-code/frontend/tsconfig.json
new file mode 100644
index 0000000..957675a
--- /dev/null
+++ b/gadget-code/frontend/tsconfig.json
@@ -0,0 +1,17 @@
+{
+  "compilerOptions": {
+    "target": "ES2022",
+    "lib": ["ES2022", "DOM", "DOM.Iterable"],
+    "module": "ESNext",
+    "moduleResolution": "bundler",
+    "jsx": "react-jsx",
+    "strict": true,
+    "noEmit": true,
+    "skipLibCheck": true,
+    "forceConsistentCasingInFileNames": true,
+    "resolveJsonModule": true,
+    "allowImportingTsExtensions": true,
+    "esModuleInterop": true
+  },
+  "include": ["src"]
+}