Compare commits

...
Author SHA1 Message Date
MervinPraisonandClaude Opus 5.5 1dad99b2c8 fix(frontend): make the dashboard build pass again (26 type errors since April)
The frontend build gate added on 2026-09-23 (166091b) has failed on every
main run since: five April PRs left code that never compiled — imports of
shadcn components that were never added (ui/alert, ui/textarea), seven
example widgets that never existed (CustomElementHost), react-router-dom
(not a dependency), a useSSE call written for a different hook, a wrong
ChatMessages prop set, and unused locals under noUnusedLocals.

- add the standard shadcn alert and textarea components
- CustomElementHost: empty registry instead of imports of missing widgets
- useWindowMessage: small local EventSource hook for /sse/window-message
- SharedThread: read the token from /shared/{token}; pass the props
  ChatMessages takes; type messages as ChatMessage[]
- drop unused imports/locals

None of these files are imported by the app today, so nothing users see
changes; the gate now builds and catches real breakage again.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-27 10:08:44 +00:00
11 changed files with 112 additions and 56 deletions

No files matched your search

+1 -1
View File
@@ -38,7 +38,7 @@ interface ActionButtonsProps {
*/
export function ActionButtons({ actions, messageId, sessionId }: ActionButtonsProps) {
const [pendingActions, setPendingActions] = useState<Set<string>>(new Set())
const [hiddenActions, setHiddenActions] = useState<Set<string>>(new Set())
const [hiddenActions] = useState<Set<string>>(new Set())
const handleActionClick = useCallback(async (action: ActionButton) => {
if (pendingActions.has(action.id) || hiddenActions.has(action.id)) {
+1 -1
View File
@@ -73,7 +73,7 @@ export function AskActionPrompt({
{/* Action buttons */}
<div className="space-y-3">
{actions.map((action, index) => (
{actions.map((action) => (
<button
key={action.name}
type="button"
+2 -2
View File
@@ -15,7 +15,7 @@ import { Textarea } from '../components/ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../components/ui/select';
import { Slider } from '../components/ui/slider';
import { Switch } from '../components/ui/switch';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '../components/ui/card';
import { Card, CardContent } from '../components/ui/card';
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '../components/ui/sheet';
interface SettingsWidget {
@@ -74,7 +74,7 @@ const WidgetRenderer: React.FC<{
id={name}
placeholder={placeholder}
value={value || ''}
onChange={(e) => onChange(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => onChange(e.target.value)}
maxLength={widget.maxLength}
rows={4}
/>
+7 -34
View File
@@ -20,43 +20,16 @@ interface CustomElementProps {
onError?: (error: Error) => void;
}
interface CustomComponentModule {
default: React.ComponentType<any>;
}
// Registry of loaded components (cache)
const componentCache = new Map<string, Promise<React.ComponentType<any>>>();
// List of available custom components (in practice, this would be populated from backend)
const availableComponents = new Set([
'ExampleWidget',
'UserCard',
'DataChart',
'FormBuilder',
'CodeEditor',
'ImageGallery',
'ChatEmbed'
]);
// Static imports for build-tool compatibility
const ExampleWidget = React.lazy(() => import('../components/custom/ExampleWidget'));
const UserCard = React.lazy(() => import('../components/custom/UserCard'));
const DataChart = React.lazy(() => import('../components/custom/DataChart'));
const FormBuilder = React.lazy(() => import('../components/custom/FormBuilder'));
const CodeEditor = React.lazy(() => import('../components/custom/CodeEditor'));
const ImageGallery = React.lazy(() => import('../components/custom/ImageGallery'));
const ChatEmbed = React.lazy(() => import('../components/custom/ChatEmbed'));
// Static mapping of component names to imports
const componentMap: Record<string, React.ComponentType<any>> = {
ExampleWidget,
UserCard,
DataChart,
FormBuilder,
CodeEditor,
ImageGallery,
ChatEmbed,
};
// Custom components available to <CustomElementHost name="..." />. The example
// widgets this once listed (ExampleWidget, UserCard, ...) were never added, and
// importing them broke the build. Register real components here as they land:
// const MyWidget = React.lazy(() => import('../components/custom/MyWidget'));
// componentMap.MyWidget = MyWidget;
const componentMap: Record<string, React.ComponentType<any>> = {};
const availableComponents = new Set(Object.keys(componentMap));
/**
* Load a custom component with static mapping for build-tool compatibility
-1
View File
@@ -4,7 +4,6 @@
* Shows live-updating task lists with states, progress bars, and click-to-jump functionality.
*/
import React from 'react'
import { ChevronRight, ChevronDown, CheckCircle, Clock, XCircle, Circle, ExternalLink } from 'lucide-react'
import { useTaskState, setTasksCollapsed, type Task, type TaskList, type TaskStatus } from '../state/tasks'
import { cn } from '../lib/utils'
+44
View File
@@ -0,0 +1,44 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
{
variants: {
variant: {
default: "bg-background text-foreground",
destructive:
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
))
Alert.displayName = "Alert"
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h5 ref={ref} className={cn("mb-1 font-medium leading-none tracking-tight", className)} {...props} />
)
)
AlertTitle.displayName = "AlertTitle"
const AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("text-sm [&_p]:leading-relaxed", className)} {...props} />
)
)
AlertDescription.displayName = "AlertDescription"
export { Alert, AlertTitle, AlertDescription }
@@ -0,0 +1,19 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<"textarea">>(
({ className, ...props }, ref) => (
<textarea
className={cn(
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props}
/>
)
)
Textarea.displayName = "Textarea"
export { Textarea }
+1 -1
View File
@@ -183,7 +183,7 @@ export function useMicStream(options: UseMicStreamOptions = {}): UseMicStreamRet
onConnectionChangeRef.current?.(false);
};
ws.onerror = (error) => {
ws.onerror = () => {
onErrorRef.current?.(new Error('WebSocket error'));
setConnected(false);
onConnectionChangeRef.current?.(false);
+28 -3
View File
@@ -5,8 +5,33 @@
* incoming messages from parent.
*/
import { useEffect, useCallback, useRef } from 'react';
import { useSSE } from './useSSE';
import { useEffect, useCallback, useRef, useState } from 'react';
interface EventSourceOptions {
enabled?: boolean;
onMessage?: (event: MessageEvent) => void;
onOpen?: () => void;
onError?: () => void;
}
/**
* Minimal EventSource subscription. (useSSE in this package is the chat
* streaming store, not a generic SSE hook, so it cannot open this stream.)
*/
function useEventSource(url: string, { enabled = true, onMessage, onOpen, onError }: EventSourceOptions) {
const [connected, setConnected] = useState(false);
const handlers = useRef({ onMessage, onOpen, onError });
handlers.current = { onMessage, onOpen, onError };
useEffect(() => {
if (!enabled || typeof EventSource === 'undefined') return;
const source = new EventSource(url);
source.onopen = () => { setConnected(true); handlers.current.onOpen?.(); };
source.onmessage = (event) => handlers.current.onMessage?.(event);
source.onerror = () => { setConnected(false); handlers.current.onError?.(); };
return () => { source.close(); setConnected(false); };
}, [url, enabled]);
return { connected };
}
export interface WindowMessageData {
type: string;
@@ -85,7 +110,7 @@ export function useWindowMessage(options: UseWindowMessageOptions = {}): UseWind
const isIframe = window !== window.parent;
// SSE connection for outbound messages
const { connected } = useSSE('/sse/window-message', {
const { connected } = useEventSource('/sse/window-message', {
enabled: enabled && isIframe,
onMessage: (event) => {
try {
@@ -7,9 +7,7 @@ import React, { useState } from 'react';
import { Button } from '../components/ui/button';
import { Input } from '../components/ui/input';
import { Label } from '../components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../components/ui/select';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '../components/ui/dialog';
import { Separator } from '../components/ui/separator';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../components/ui/tabs';
interface AddMCPServerModalProps {
+9 -11
View File
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { Eye, AlertCircle } from 'lucide-react';
import { ChatMessages } from '../chat/ChatMessages';
import type { ChatMessage } from '../types';
import { Button } from '../components/ui/button';
interface SharedThreadData {
@@ -11,18 +11,13 @@ interface SharedThreadData {
title: string;
created_at: string;
};
messages: Array<{
id: string;
role: string;
content: string;
timestamp: string;
toolCalls?: any[];
}>;
messages: ChatMessage[];
read_only: boolean;
}
export function SharedThread() {
const { token } = useParams<{ token: string }>();
// Served at /shared/{token} (server.py); this app has no client-side router.
const token = decodeURIComponent(window.location.pathname.split('/shared/')[1]?.split('/')[0] ?? '');
const [threadData, setThreadData] = useState<SharedThreadData | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string>('');
@@ -128,10 +123,13 @@ export function SharedThread() {
<div className="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
{threadData.messages.length > 0 ? (
<div className="divide-y divide-gray-100">
<ChatMessages
<ChatMessages
messages={threadData.messages}
sessionId={threadData.thread_id}
isSharedView={true}
currentResponse=""
thinkingSteps={[]}
toolCalls={[]}
isStreaming={false}
/>
</div>
) : (