From 4d82b71a7a4afd40dc84e03a2c7cfd8f4faf04f2 Mon Sep 17 00:00:00 2001 From: luccast <2213102+luccast@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:59:15 -0500 Subject: [PATCH] Refactor ActionGraph and related components for improved type safety and code clarity. Removed unnecessary peer dependency in package-lock.json. Updated session and action node components to use explicit types and fixed minor styling issues. Enhanced local collection options in clawdbot integration and demo-db. Adjusted live query handling in demo and monitor routes for better data management. --- package-lock.json | 1 - src/components/monitor/ActionGraph.tsx | 45 ++++++++++++------------ src/components/monitor/ActionNode.tsx | 27 +++++++++----- src/components/monitor/SessionList.tsx | 2 +- src/components/monitor/SessionNode.tsx | 15 ++++---- src/integrations/clawdbot/client.ts | 2 +- src/integrations/clawdbot/collections.ts | 32 ++++++++++------- src/integrations/db/devtools.tsx | 3 +- src/integrations/trpc/router.ts | 2 -- src/lib/demo-db.ts | 12 ++++--- src/routes/api/trpc.$.ts | 1 - src/routes/demo/db.tsx | 7 ++-- src/routes/index.tsx | 2 +- src/routes/monitor/index.tsx | 10 +++--- src/vite-env.d.ts | 6 ++++ 15 files changed, 98 insertions(+), 69 deletions(-) create mode 100644 src/vite-env.d.ts diff --git a/package-lock.json b/package-lock.json index f6eb0f6..d5a6a24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2154,7 +2154,6 @@ "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } diff --git a/src/components/monitor/ActionGraph.tsx b/src/components/monitor/ActionGraph.tsx index 571e703..142deea 100644 --- a/src/components/monitor/ActionGraph.tsx +++ b/src/components/monitor/ActionGraph.tsx @@ -24,9 +24,10 @@ interface ActionGraphProps { onSessionSelect: (key: string | null) => void } +// eslint-disable-next-line @typescript-eslint/no-explicit-any const nodeTypes: NodeTypes = { - session: SessionNode, - action: ActionNode, + session: SessionNode as any, + action: ActionNode as any, } export function ActionGraph({ @@ -55,7 +56,7 @@ export function ActionGraph({ id: `session-${session.key}`, type: 'session', position: { x: 0, y: 0 }, - data: session, + data: session as unknown as Record, }) } @@ -65,7 +66,7 @@ export function ActionGraph({ id: `action-${action.id}`, type: 'action', position: { x: 0, y: 0 }, - data: action, + data: action as unknown as Record, }) } @@ -87,8 +88,8 @@ export function ActionGraph({ // Connect session to first action of each run for (const [runId, runActs] of runActions) { const sorted = [...runActs].sort((a, b) => a.seq - b.seq) - if (sorted.length > 0) { - const first = sorted[0] + const first = sorted[0] + if (first) { const sessionId = `session-${first.sessionKey}` edges.push({ id: `e-session-${runId}`, @@ -98,20 +99,20 @@ export function ActionGraph({ markerEnd: { type: MarkerType.ArrowClosed }, style: { stroke: '#6b7280' }, }) - } - // Connect actions in sequence - for (let i = 1; i < sorted.length; i++) { - const prev = sorted[i - 1] - const curr = sorted[i] - edges.push({ - id: `e-${prev.id}-${curr.id}`, - source: `action-${prev.id}`, - target: `action-${curr.id}`, - animated: curr.type === 'delta', - markerEnd: { type: MarkerType.ArrowClosed }, - style: { stroke: '#6b7280' }, - }) + // Connect actions in sequence + for (let i = 1; i < sorted.length; i++) { + const prev = sorted[i - 1]! + const curr = sorted[i]! + edges.push({ + id: `e-${prev.id}-${curr.id}`, + source: `action-${prev.id}`, + target: `action-${curr.id}`, + animated: curr.type === 'delta', + markerEnd: { type: MarkerType.ArrowClosed }, + style: { stroke: '#6b7280' }, + }) + } } } @@ -142,7 +143,7 @@ export function ActionGraph({ const onNodeClick = useCallback( (_: React.MouseEvent, node: Node) => { if (node.type === 'session') { - const sessionKey = (node.data as MonitorSession).key + const sessionKey = (node.data as unknown as MonitorSession).key onSessionSelect(selectedSession === sessionKey ? null : sessionKey) } }, @@ -165,9 +166,9 @@ export function ActionGraph({ proOptions={{ hideAttribution: true }} > - + { if (node.type === 'session') return '#06b6d4' return '#6b7280' diff --git a/src/components/monitor/ActionNode.tsx b/src/components/monitor/ActionNode.tsx index 9bb1593..6fa3219 100644 --- a/src/components/monitor/ActionNode.tsx +++ b/src/components/monitor/ActionNode.tsx @@ -1,12 +1,23 @@ import { memo, useState } from 'react' -import { Handle, Position, type NodeProps } from '@xyflow/react' +import { Handle, Position } from '@xyflow/react' import { motion } from 'framer-motion' import { Loader2, CheckCircle, XCircle, Wrench, MessageSquare } from 'lucide-react' import type { MonitorAction } from '~/integrations/clawdbot' -type ActionNodeData = MonitorAction +interface ActionNodeProps { + data: MonitorAction + selected?: boolean +} -const typeConfig = { +const typeConfig: Record< + MonitorAction['type'], + { + icon: typeof Loader2 + color: string + iconColor: string + animate: boolean + } +> = { delta: { icon: Loader2, color: 'border-blue-500 bg-blue-500/10', @@ -48,7 +59,7 @@ const typeConfig = { export const ActionNode = memo(function ActionNode({ data, selected, -}: NodeProps) { +}: ActionNodeProps) { const [expanded, setExpanded] = useState(false) const config = typeConfig[data.type] const Icon = config.icon @@ -71,7 +82,7 @@ export const ActionNode = memo(function ActionNode({ ${selected ? 'ring-2 ring-white/50' : ''} `} > - +
)} - {expanded && data.toolArgs && ( + {expanded && data.toolArgs != null && (
-          {JSON.stringify(data.toolArgs, null, 2)}
+          {JSON.stringify(data.toolArgs, null, 2) as string}
         
)} - + ) }) diff --git a/src/components/monitor/SessionList.tsx b/src/components/monitor/SessionList.tsx index 54bff0d..9b549fa 100644 --- a/src/components/monitor/SessionList.tsx +++ b/src/components/monitor/SessionList.tsx @@ -1,6 +1,6 @@ import { useState } from 'react' import { motion, AnimatePresence } from 'framer-motion' -import { Search, Filter } from 'lucide-react' +import { Search } from 'lucide-react' import { StatusIndicator } from './StatusIndicator' import type { MonitorSession } from '~/integrations/clawdbot' diff --git a/src/components/monitor/SessionNode.tsx b/src/components/monitor/SessionNode.tsx index bf682da..4ca3af8 100644 --- a/src/components/monitor/SessionNode.tsx +++ b/src/components/monitor/SessionNode.tsx @@ -1,11 +1,14 @@ import { memo } from 'react' -import { Handle, Position, type NodeProps } from '@xyflow/react' +import { Handle, Position } from '@xyflow/react' import { motion } from 'framer-motion' -import { MessageSquare, Users, User } from 'lucide-react' +import { Users, User } from 'lucide-react' import { StatusIndicator } from './StatusIndicator' import type { MonitorSession } from '~/integrations/clawdbot' -type SessionNodeData = MonitorSession +interface SessionNodeProps { + data: MonitorSession + selected?: boolean +} const platformIcons: Record = { whatsapp: '💬', @@ -17,7 +20,7 @@ const platformIcons: Record = { export const SessionNode = memo(function SessionNode({ data, selected, -}: NodeProps) { +}: SessionNodeProps) { const platformIcon = platformIcons[data.platform] ?? '📱' return ( @@ -31,7 +34,7 @@ export const SessionNode = memo(function SessionNode({ ${data.status === 'thinking' ? 'border-yellow-500' : ''} `} > - +
{platformIcon} @@ -52,7 +55,7 @@ export const SessionNode = memo(function SessionNode({ {data.agentId}
- + ) }) diff --git a/src/integrations/clawdbot/client.ts b/src/integrations/clawdbot/client.ts index 714dace..e1f80f2 100644 --- a/src/integrations/clawdbot/client.ts +++ b/src/integrations/clawdbot/client.ts @@ -67,7 +67,7 @@ export class ClawdbotClient { private handleMessage( msg: GatewayFrame | HelloOk, connectResolve?: (v: HelloOk) => void, - connectReject?: (e: Error) => void + _connectReject?: (e: Error) => void ) { if ('type' in msg) { switch (msg.type) { diff --git a/src/integrations/clawdbot/collections.ts b/src/integrations/clawdbot/collections.ts index 84f4ecc..02b4bf7 100644 --- a/src/integrations/clawdbot/collections.ts +++ b/src/integrations/clawdbot/collections.ts @@ -1,21 +1,27 @@ -import { createCollection } from '@tanstack/db' +import { createCollection, localOnlyCollectionOptions } from '@tanstack/db' import type { MonitorSession, MonitorAction } from './protocol' -export const sessionsCollection = createCollection({ - id: 'clawdbot-sessions', - primaryKey: 'key', -}) +export const sessionsCollection = createCollection( + localOnlyCollectionOptions({ + id: 'clawdbot-sessions', + getKey: (item) => item.key, + }) +) -export const actionsCollection = createCollection({ - id: 'clawdbot-actions', - primaryKey: 'id', -}) +export const actionsCollection = createCollection( + localOnlyCollectionOptions({ + id: 'clawdbot-actions', + getKey: (item) => item.id, + }) +) // Helper to update or insert session export function upsertSession(session: MonitorSession) { const existing = sessionsCollection.state.get(session.key) if (existing) { - sessionsCollection.update(session.key, session) + sessionsCollection.update(session.key, (draft) => { + Object.assign(draft, session) + }) } else { sessionsCollection.insert(session) } @@ -36,9 +42,9 @@ export function updateSessionStatus( ) { const session = sessionsCollection.state.get(key) if (session) { - sessionsCollection.update(key, { - status, - lastActivityAt: Date.now(), + sessionsCollection.update(key, (draft) => { + draft.status = status + draft.lastActivityAt = Date.now() }) } } diff --git a/src/integrations/db/devtools.tsx b/src/integrations/db/devtools.tsx index 4ba2a7e..9d1a25f 100644 --- a/src/integrations/db/devtools.tsx +++ b/src/integrations/db/devtools.tsx @@ -12,7 +12,8 @@ export const dbDevtoolsPlugin = { } function DbInspector() { - const todos = useLiveQuery(todosCollection) + const todosQuery = useLiveQuery(todosCollection) + const todos = todosQuery.data ?? [] return (
todos ({todos.length})
diff --git a/src/integrations/trpc/router.ts b/src/integrations/trpc/router.ts index 9daa984..f2a06bb 100644 --- a/src/integrations/trpc/router.ts +++ b/src/integrations/trpc/router.ts @@ -4,8 +4,6 @@ import superjson from 'superjson' import { z } from 'zod' import { getClawdbotClient, - isChatEvent, - isAgentEvent, parseEventFrame, sessionInfoToMonitor, type MonitorSession, diff --git a/src/lib/demo-db.ts b/src/lib/demo-db.ts index a45b32e..cedfdb6 100644 --- a/src/lib/demo-db.ts +++ b/src/lib/demo-db.ts @@ -1,4 +1,4 @@ -import { createCollection } from '@tanstack/db' +import { createCollection, localOnlyCollectionOptions } from '@tanstack/db' export interface Todo { id: string @@ -7,7 +7,9 @@ export interface Todo { createdAt: number } -export const todosCollection = createCollection({ - id: 'todos', - primaryKey: 'id', -}) +export const todosCollection = createCollection( + localOnlyCollectionOptions({ + id: 'todos', + getKey: (item) => item.id, + }) +) diff --git a/src/routes/api/trpc.$.ts b/src/routes/api/trpc.$.ts index 31650f2..837b808 100644 --- a/src/routes/api/trpc.$.ts +++ b/src/routes/api/trpc.$.ts @@ -12,7 +12,6 @@ async function handler({ request }: { request: Request }) { } export const Route = createFileRoute('/api/trpc/$')({ - // @ts-expect-error server property not in route types yet server: { handlers: { GET: handler, diff --git a/src/routes/demo/db.tsx b/src/routes/demo/db.tsx index 88b060d..816e530 100644 --- a/src/routes/demo/db.tsx +++ b/src/routes/demo/db.tsx @@ -9,7 +9,8 @@ export const Route = createFileRoute('/demo/db')({ function DbDemo() { const [newTodo, setNewTodo] = useState('') - const todos = useLiveQuery(todosCollection) + const todosQuery = useLiveQuery(todosCollection) + const todos = todosQuery.data ?? [] const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() @@ -40,7 +41,9 @@ function DbDemo() { }) tx.mutate(() => { - todosCollection.update(todo.id, { completed: !todo.completed }) + todosCollection.update(todo.id, (draft) => { + draft.completed = !draft.completed + }) }) await tx.commit() diff --git a/src/routes/index.tsx b/src/routes/index.tsx index a5f4252..4695eae 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -7,7 +7,7 @@ export const Route = createFileRoute('/')({ function Home() { return ( -
+

Crabwalk

diff --git a/src/routes/monitor/index.tsx b/src/routes/monitor/index.tsx index 102916e..06b7857 100644 --- a/src/routes/monitor/index.tsx +++ b/src/routes/monitor/index.tsx @@ -7,10 +7,7 @@ import { sessionsCollection, actionsCollection, upsertSession, - addAction, clearCollections, - type MonitorSession, - type MonitorAction, } from '~/integrations/clawdbot' import { ActionGraph, @@ -31,8 +28,11 @@ function MonitorPage() { const [selectedSession, setSelectedSession] = useState(null) // Live queries from TanStack DB collections - const sessions = useLiveQuery(sessionsCollection) - const actions = useLiveQuery(actionsCollection) + const sessionsQuery = useLiveQuery(sessionsCollection) + const actionsQuery = useLiveQuery(actionsCollection) + + const sessions = sessionsQuery.data ?? [] + const actions = actionsQuery.data ?? [] // Check connection status on mount useEffect(() => { diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..a9331e8 --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1,6 @@ +/// + +declare module '*.css?url' { + const url: string + export default url +}