Define missing socket event types and enforce typed events in frontend build
Adds type definitions + forwarding for status, reconnect_attempt, reconnect_failed, reconnect events. Frontend build now runs tsc --noEmit before vite build so undefined socket events cause failures. Fixes pre-existing type errors exposed by strict mode in the frontend.
This commit is contained in:
parent
09d53ed4f2
commit
24975b58c4
@ -5,7 +5,8 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build"
|
"typecheck": "tsc --noEmit",
|
||||||
|
"build": "tsc --noEmit && vite build"
|
||||||
},
|
},
|
||||||
"author": "Robert Colbert <rob.colbert@openplatform.us>",
|
"author": "Robert Colbert <rob.colbert@openplatform.us>",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
import { useRef, useMemo, useEffect } from 'react';
|
import { useRef, useMemo, useEffect } from 'react';
|
||||||
import { Canvas, useFrame, useThree } from '@react-three/fiber';
|
import { Canvas, useFrame, useThree } from '@react-three/fiber';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|||||||
@ -99,7 +99,7 @@ export default function LogPanel({ logs, expanded, onToggleExpand }: LogPanelPro
|
|||||||
<span className="text-text-secondary break-all min-w-0">
|
<span className="text-text-secondary break-all min-w-0">
|
||||||
{entry.message}
|
{entry.message}
|
||||||
</span>
|
</span>
|
||||||
{entry.metadata && (
|
{entry.metadata != null && (
|
||||||
<button
|
<button
|
||||||
onClick={() => toggleMetadata(entry.id)}
|
onClick={() => toggleMetadata(entry.id)}
|
||||||
className="shrink-0 text-text-muted hover:text-text-secondary transition-colors"
|
className="shrink-0 text-text-muted hover:text-text-secondary transition-colors"
|
||||||
@ -108,7 +108,7 @@ export default function LogPanel({ logs, expanded, onToggleExpand }: LogPanelPro
|
|||||||
{expandedMetadata.has(entry.id) ? '⊟' : '⊞'}
|
{expandedMetadata.has(entry.id) ? '⊟' : '⊞'}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{entry.metadata && expandedMetadata.has(entry.id) && (
|
{entry.metadata != null && expandedMetadata.has(entry.id) && (
|
||||||
<div className="w-full text-text-muted pl-[1em] border-l border-border-subtle mt-0.5 mb-1">
|
<div className="w-full text-text-muted pl-[1em] border-l border-border-subtle mt-0.5 mb-1">
|
||||||
<pre className="whitespace-pre-wrap break-all">
|
<pre className="whitespace-pre-wrap break-all">
|
||||||
{JSON.stringify(entry.metadata, null, 2)}
|
{JSON.stringify(entry.metadata, null, 2)}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ export type ConnectionStatus = 'connected' | 'connecting' | 'error' | 'disconnec
|
|||||||
|
|
||||||
interface StatusBarProps {
|
interface StatusBarProps {
|
||||||
statusMessage?: string;
|
statusMessage?: string;
|
||||||
projectSlug?: string;
|
projectSlug?: string | null;
|
||||||
sessionMode?: string;
|
sessionMode?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -41,23 +41,6 @@ function setToken(token: string): void {
|
|||||||
localStorage.setItem(TOKEN_KEY, token);
|
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 {
|
function signOut(): void {
|
||||||
localStorage.removeItem(TOKEN_KEY);
|
localStorage.removeItem(TOKEN_KEY);
|
||||||
localStorage.removeItem(USER_KEY);
|
localStorage.removeItem(USER_KEY);
|
||||||
@ -73,7 +56,7 @@ function isTokenExpiringSoon(token: string, marginMs = 5 * 60 * 1000): boolean {
|
|||||||
try {
|
try {
|
||||||
const parts = token.split(".");
|
const parts = token.split(".");
|
||||||
if (parts.length < 2) return true;
|
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;
|
if (!payload.exp) return true;
|
||||||
const expiresAt = payload.exp * 1000; // seconds → ms
|
const expiresAt = payload.exp * 1000; // seconds → ms
|
||||||
return Date.now() > (expiresAt - marginMs);
|
return Date.now() > (expiresAt - marginMs);
|
||||||
@ -136,7 +119,7 @@ async function request<T>(
|
|||||||
isRefreshing = true;
|
isRefreshing = true;
|
||||||
refreshPromise = refreshAuthToken();
|
refreshPromise = refreshAuthToken();
|
||||||
}
|
}
|
||||||
token = await refreshPromise;
|
token = (await refreshPromise)!;
|
||||||
setToken(token);
|
setToken(token);
|
||||||
onTokenRefreshedCallback?.(token);
|
onTokenRefreshedCallback?.(token);
|
||||||
isRefreshing = false;
|
isRefreshing = false;
|
||||||
@ -177,12 +160,12 @@ async function request<T>(
|
|||||||
refreshPromise = refreshAuthToken();
|
refreshPromise = refreshAuthToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
const newToken = await refreshPromise;
|
const newToken = (await refreshPromise)!;
|
||||||
setToken(newToken);
|
setToken(newToken);
|
||||||
onTokenRefreshedCallback?.(newToken);
|
onTokenRefreshedCallback?.(newToken);
|
||||||
isRefreshing = false;
|
isRefreshing = false;
|
||||||
refreshPromise = null;
|
refreshPromise = null;
|
||||||
|
|
||||||
return request<T>(method, path, body, retryCount + 1);
|
return request<T>(method, path, body, retryCount + 1);
|
||||||
} catch {
|
} catch {
|
||||||
isRefreshing = false;
|
isRefreshing = false;
|
||||||
|
|||||||
@ -118,6 +118,10 @@ export interface SocketEvents {
|
|||||||
workspaceModeChanged: (mode: string) => void;
|
workspaceModeChanged: (mode: string) => void;
|
||||||
sessionUpdated: (updates: Partial<ChatSession>) => void;
|
sessionUpdated: (updates: Partial<ChatSession>) => void;
|
||||||
tabLockDenied: (data: { message: string }) => void;
|
tabLockDenied: (data: { message: string }) => void;
|
||||||
|
status: (content: string) => void;
|
||||||
|
reconnect_attempt: (attempt: number) => void;
|
||||||
|
reconnect_failed: () => void;
|
||||||
|
reconnect: (attempt: number) => void;
|
||||||
connect: () => void;
|
connect: () => void;
|
||||||
disconnect: (reason: string) => void;
|
disconnect: (reason: string) => void;
|
||||||
error: (error: Error) => void;
|
error: (error: Error) => void;
|
||||||
@ -246,7 +250,7 @@ class SocketClient {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.socket.on("agent:complete", (data: unknown) => {
|
this.socket.on("agent:complete", (data: unknown) => {
|
||||||
this.emit("agent:complete", data as { agentId: string; response?: string; subagent?: Record<string, unknown>; stats?: Record<string, unknown> });
|
this.emit("agent:complete", data as SocketEvents["agent:complete"] extends (data: infer T) => void ? T : never);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._socket.on("connect", () => {
|
this._socket.on("connect", () => {
|
||||||
@ -266,6 +270,22 @@ class SocketClient {
|
|||||||
this._socket.on("tabLockDenied", (data: { message: string }) => {
|
this._socket.on("tabLockDenied", (data: { message: string }) => {
|
||||||
this.emit("tabLockDenied", data);
|
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 {
|
disconnect(): void {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect, useRef, useContext, useCallback } from 'react';
|
import { useState, useEffect, useRef, useContext, useCallback } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { socketClient } from '../lib/socket';
|
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 { WorkspaceMode } from '../lib/types';
|
||||||
import WorkspaceModeIndicator from '../components/WorkspaceModeIndicator';
|
import WorkspaceModeIndicator from '../components/WorkspaceModeIndicator';
|
||||||
import FilesPanel from '../components/FilesPanel';
|
import FilesPanel from '../components/FilesPanel';
|
||||||
@ -70,7 +70,7 @@ export default function ChatSessionView() {
|
|||||||
const [isEditingName, setIsEditingName] = useState(false);
|
const [isEditingName, setIsEditingName] = useState(false);
|
||||||
const [editName, setEditName] = useState('');
|
const [editName, setEditName] = useState('');
|
||||||
const [isUpdatingName, setIsUpdatingName] = useState(false);
|
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 [isOtherTab, setIsOtherTab] = useState(false);
|
||||||
const [reconnectAttempts, setReconnectAttempts] = useState(0);
|
const [reconnectAttempts, setReconnectAttempts] = useState(0);
|
||||||
|
|
||||||
@ -284,12 +284,12 @@ export default function ChatSessionView() {
|
|||||||
const turnIndex = newTurns.findIndex(t => t._id === turnId);
|
const turnIndex = newTurns.findIndex(t => t._id === turnId);
|
||||||
if (turnIndex === -1) continue;
|
if (turnIndex === -1) continue;
|
||||||
|
|
||||||
const oldTurn = newTurns[turnIndex];
|
const oldTurn = newTurns[turnIndex]!;
|
||||||
const newTurn = { ...oldTurn, ...turnUpdates };
|
const newTurn = { ...oldTurn, ...turnUpdates };
|
||||||
|
|
||||||
if (turnUpdates.blocks !== undefined) {
|
if (turnUpdates.blocks !== undefined) {
|
||||||
const state = streamingStateRef.current.get(turnId);
|
const state = streamingStateRef.current.get(turnId);
|
||||||
const updatedBlocks = [...(oldTurn.blocks || [])];
|
const updatedBlocks = [...(oldTurn!.blocks || [])];
|
||||||
|
|
||||||
for (const updateBlock of turnUpdates.blocks) {
|
for (const updateBlock of turnUpdates.blocks) {
|
||||||
let blockIndex: number | null = state?.currentBlockIndex ?? null;
|
let blockIndex: number | null = state?.currentBlockIndex ?? null;
|
||||||
@ -329,9 +329,9 @@ export default function ChatSessionView() {
|
|||||||
newTurn.blocks = updatedBlocks;
|
newTurn.blocks = updatedBlocks;
|
||||||
}
|
}
|
||||||
if (turnUpdates.toolCalls !== undefined) {
|
if (turnUpdates.toolCalls !== undefined) {
|
||||||
newTurn.toolCalls = [...(oldTurn.toolCalls || []), ...turnUpdates.toolCalls];
|
newTurn.toolCalls = [...(oldTurn!.toolCalls || []), ...turnUpdates.toolCalls];
|
||||||
newTurn.stats = {
|
newTurn.stats = {
|
||||||
...oldTurn.stats,
|
...oldTurn!.stats,
|
||||||
toolCallCount: newTurn.toolCalls.length
|
toolCallCount: newTurn.toolCalls.length
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -888,7 +888,7 @@ export default function ChatSessionView() {
|
|||||||
user: session?.user?._id || '',
|
user: session?.user?._id || '',
|
||||||
project: session?.project?._id || projectId || '',
|
project: session?.project?._id || projectId || '',
|
||||||
session: sessionId || '',
|
session: sessionId || '',
|
||||||
provider: session?.provider?._id || '',
|
provider: typeof session?.provider === 'object' ? session?.provider?._id || '' : session?.provider || '',
|
||||||
llm: session?.selectedModel || '',
|
llm: session?.selectedModel || '',
|
||||||
mode: session?.mode || 'develop',
|
mode: session?.mode || 'develop',
|
||||||
status: 'processing',
|
status: 'processing',
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export default function DroneManager({ user }: DroneManagerProps) {
|
|||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
setLogEntries(prev => {
|
setLogEntries(prev => {
|
||||||
const newEntry: LogEntry = {
|
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(),
|
timestamp: new Date().toLocaleTimeString(),
|
||||||
message: `[PLACEHOLDER] New log entry at ${new Date().toLocaleTimeString()} - Auto-generated for testing`,
|
message: `[PLACEHOLDER] New log entry at ${new Date().toLocaleTimeString()} - Auto-generated for testing`,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -93,7 +93,7 @@ function DroneInspector({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DashboardSidebar({
|
function DashboardSidebar({
|
||||||
onNavigate,
|
onNavigate: _onNavigate,
|
||||||
selectedDrone,
|
selectedDrone,
|
||||||
onSelectDrone,
|
onSelectDrone,
|
||||||
}: DashboardSidebarProps) {
|
}: DashboardSidebarProps) {
|
||||||
|
|||||||
12
gadget-code/frontend/src/types/react-dom.d.ts
vendored
Normal file
12
gadget-code/frontend/src/types/react-dom.d.ts
vendored
Normal file
@ -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;
|
||||||
|
}
|
||||||
17
gadget-code/frontend/tsconfig.json
Normal file
17
gadget-code/frontend/tsconfig.json
Normal file
@ -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"]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user