From c1e5b1bbea81fb1f14f36b915918b471d54fb52e Mon Sep 17 00:00:00 2001 From: Jack Herrington Date: Thu, 16 Apr 2026 10:40:14 -0700 Subject: [PATCH 1/9] chore: update project agent configuration --- .agentsroom/.gitignore | 4 ++++ .agentsroom/agents.json | 1 + .agentsroom/prompts.json | 4 ++++ 3 files changed, 9 insertions(+) create mode 100644 .agentsroom/.gitignore create mode 100644 .agentsroom/agents.json create mode 100644 .agentsroom/prompts.json diff --git a/.agentsroom/.gitignore b/.agentsroom/.gitignore new file mode 100644 index 000000000..1acd1a387 --- /dev/null +++ b/.agentsroom/.gitignore @@ -0,0 +1,4 @@ +# AgentsRoom: personal files (not committed to git) +*-personal.json +agents-local.json +sessions/ diff --git a/.agentsroom/agents.json b/.agentsroom/agents.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/.agentsroom/agents.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/.agentsroom/prompts.json b/.agentsroom/prompts.json new file mode 100644 index 000000000..f4455d843 --- /dev/null +++ b/.agentsroom/prompts.json @@ -0,0 +1,4 @@ +{ + "folders": [], + "prompts": [] +} \ No newline at end of file From 45518bf114e5c9433b65d754522388a81605f9f0 Mon Sep 17 00:00:00 2001 From: Jack Herrington Date: Thu, 16 Apr 2026 10:40:44 -0700 Subject: [PATCH 2/9] chore: update project agent configuration --- .agentsroom/agents.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.agentsroom/agents.json b/.agentsroom/agents.json index 0637a088a..61f6b9421 100644 --- a/.agentsroom/agents.json +++ b/.agentsroom/agents.json @@ -1 +1,9 @@ -[] \ No newline at end of file +[ + { + "role": "fullstack", + "model": "opus", + "customName": "Full-Stack Developer", + "isPersonal": false, + "id": "agent-1776361243376-3sekdc" + } +] \ No newline at end of file From 88f018e20a8513933d35ce1e0f82b048535be42b Mon Sep 17 00:00:00 2001 From: Jack Herrington Date: Thu, 16 Apr 2026 10:40:59 -0700 Subject: [PATCH 3/9] chore: update project agent configuration --- .agentsroom/agents.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.agentsroom/agents.json b/.agentsroom/agents.json index 61f6b9421..e9a83e418 100644 --- a/.agentsroom/agents.json +++ b/.agentsroom/agents.json @@ -4,6 +4,7 @@ "model": "opus", "customName": "Full-Stack Developer", "isPersonal": false, - "id": "agent-1776361243376-3sekdc" + "id": "agent-1776361243376-3sekdc", + "claudeSessionId": "96773a93-be2a-45a9-a732-ceb224d3d0e5" } ] \ No newline at end of file From 7d960022a4cef52107e244f4ef8190b54508873f Mon Sep 17 00:00:00 2001 From: Jack Herrington Date: Sun, 19 Jul 2026 08:02:18 -0700 Subject: [PATCH 4/9] fix(examples): update ts-group-chat for Cap'n Web 0.10 push RPC Upgrade the group chat demo to Cap'n Web 0.10 bidirectional push, modern TanStack Start/Nitro setup, and fix browser RPC by calling stubs via ref instead of React state. Co-authored-by: Cursor --- examples/ts-group-chat/.env.example | 2 +- examples/ts-group-chat/README.md | 251 +- .../ts-group-chat/chat-server/capnweb-rpc.ts | 348 +- .../ts-group-chat/chat-server/chat-api.ts | 58 + .../ts-group-chat/chat-server/chat-logic.ts | 58 +- examples/ts-group-chat/chat-server/index.ts | 3 +- .../ts-group-chat/chat-server/vite-plugin.ts | 19 +- examples/ts-group-chat/package.json | 11 +- examples/ts-group-chat/pnpm-lock.yaml | 3637 ----------------- .../src/components/ChatInterface.tsx | 26 +- .../src/components/UsernameInput.tsx | 74 - .../src/hooks/useChatConnection.ts | 107 +- .../src/hooks/useChatMessages.ts | 200 +- examples/ts-group-chat/src/hooks/useClaude.ts | 49 +- .../ts-group-chat/src/lib/chat-notifier.ts | 15 + examples/ts-group-chat/src/routes/__root.tsx | 2 +- examples/ts-group-chat/src/routes/index.tsx | 10 +- .../{vite.config.mjs => vite.config.ts} | 22 +- pnpm-lock.yaml | 22 +- 19 files changed, 564 insertions(+), 4350 deletions(-) create mode 100644 examples/ts-group-chat/chat-server/chat-api.ts delete mode 100644 examples/ts-group-chat/pnpm-lock.yaml delete mode 100644 examples/ts-group-chat/src/components/UsernameInput.tsx create mode 100644 examples/ts-group-chat/src/lib/chat-notifier.ts rename examples/ts-group-chat/{vite.config.mjs => vite.config.ts} (79%) diff --git a/examples/ts-group-chat/.env.example b/examples/ts-group-chat/.env.example index cd7f89812..1a76f6e2f 100644 --- a/examples/ts-group-chat/.env.example +++ b/examples/ts-group-chat/.env.example @@ -1,3 +1,3 @@ -# Anthropic API Key for Murf AI assistant +# Anthropic API Key for @Claude mentions in group chat # Get your API key from: https://console.anthropic.com/ ANTHROPIC_API_KEY=sk-ant-your-api-key-here diff --git a/examples/ts-group-chat/README.md b/examples/ts-group-chat/README.md index da68d7723..a81e89047 100644 --- a/examples/ts-group-chat/README.md +++ b/examples/ts-group-chat/README.md @@ -1,229 +1,86 @@ -# Cap'n Web Chat Demo +# Cap'n Web Group Chat -A real-time multi-user chat application demonstrating the integration of **Cap'n Web RPC** with **TanStack Start**. This project showcases bidirectional WebSocket communication, server-side rendering, and modern React patterns. +A real-time multi-user chat demonstrating **Cap'n Web RPC** bidirectional push with **TanStack Start**. Mention `@Claude` in a message to queue an Anthropic response (requires `ANTHROPIC_API_KEY`). -## πŸŽ₯ Demo +## Features -![Demo video](./assets/demo.gif) +- Real-time messaging via Cap'n Web serverβ†’client push (no polling) +- Online presence updates when users join or leave +- Auto-connect on page load +- Optional `@Claude` AI responses via `@tanstack/ai` +- TanStack Start SSR shell + file-based routing +- Tailwind CSS chat UI -## ✨ Features +## Architecture -- **Real-time messaging** - Instant message delivery across all connected users -- **Online presence** - See who's currently in the chat -- **Auto-connection** - Seamlessly connects when the page loads -- **Responsive design** - Works on desktop and mobile devices -- **Modern chat UI** - Messages appear like iMessage/WhatsApp (yours on right, others on left) -- **Username-based** - Simple username entry, no registration required - -## πŸ—οΈ Architecture - -This demo combines several powerful technologies: - -- **[Cap'n Web RPC](https://github.com/cloudflare/capnweb)** - Bidirectional RPC over WebSockets -- **[TanStack Start](https://tanstack.com/start)** - Full-stack React framework -- **[TanStack Router](https://tanstack.com/router)** - Type-safe routing -- **[Tailwind CSS](https://tailwindcss.com/)** - Utility-first styling -- **WebSocket Server** - Real-time communication layer - -### Key Components +- **[Cap'n Web](https://github.com/cloudflare/capnweb)** β€” `RpcTarget` server + typed `RpcStub` client over WebSocket +- **[TanStack Start](https://tanstack.com/start)** β€” Vite + Nitro 3 full-stack React +- **Vite dev plugin** β€” WebSocket upgrade at `/api/websocket` (Node `ws` pattern from Cap'n Web docs) ``` chat-server/ -β”œβ”€β”€ chat-logic.ts # Core chat business logic -β”œβ”€β”€ capnweb-rpc.ts # Cap'n Web RPC server implementation -└── vite-plugin.ts # WebSocket integration with Vite +β”œβ”€β”€ chat-api.ts # Shared ChatApi interface and types +β”œβ”€β”€ chat-logic.ts # In-memory chat state +β”œβ”€β”€ capnweb-rpc.ts # ChatServer RpcTarget + push broadcasts +└── vite-plugin.ts # Dev-server WebSocket upgrade handler src/ -β”œβ”€β”€ components/ -β”‚ β”œβ”€β”€ ChatInterface.tsx # Main chat message display -β”‚ β”œβ”€β”€ OnlineUsers.tsx # Online user list -β”‚ └── UsernameInput.tsx # Username entry β”œβ”€β”€ hooks/ -β”‚ β”œβ”€β”€ useChatConnection.ts # WebSocket connection management -β”‚ └── useChatMessages.ts # Message state and polling -└── routes/ - └── index.tsx # Main chat page +β”‚ β”œβ”€β”€ useChatConnection.ts # RpcStub session + Symbol.dispose +β”‚ β”œβ”€β”€ useChatMessages.ts # joinChat onNotify push handler +β”‚ └── useClaude.ts # Claude queue status +└── routes/index.tsx # Main chat page ``` -## πŸš€ Getting Started +> **Note:** The Cap'n Web WebSocket room runs on the Vite dev server (`pnpm dev`). Production `pnpm build` / `pnpm serve` builds the Start app but does not host the live chat room β€” use `pnpm dev` to exercise real-time RPC. -### Prerequisites +## Getting Started -- Node.js 18+ -- pnpm (recommended) or npm - -### Installation +From the monorepo root: ```bash -# Clone the repository -git clone -cd capnweb-chat-demo - -# Install dependencies pnpm install -``` - -### Development - -```bash -# Start the development server +cd examples/ts-group-chat +cp .env.example .env # optional, for @Claude pnpm dev ``` -Open [http://localhost:3000](http://localhost:3000) in multiple browser tabs to test the chat functionality. - -### Building for Production - -```bash -# Build the application -pnpm build - -# Start production server -pnpm start -``` - -## πŸ’¬ How It Works - -### 1. **Connection Flow** - -- User opens the chat page -- Application auto-connects to WebSocket server via Cap'n Web RPC -- Connection status is managed transparently - -### 2. **Chat Flow** - -- User enters a username -- Application automatically joins the chat room -- Messages are sent via RPC calls to the server -- Server broadcasts messages to all connected users -- Client polls for new messages every second - -### 3. **Message Broadcasting** - -```typescript -// Server-side message broadcasting -await ChatServer.broadcastToAll({ - type: 'message', - message: message.message, - username: message.username, - timestamp: message.timestamp, - id: message.id, -}) -``` - -### 4. **Real-time Updates** - -- **Messages**: Polled every 1 second -- **Online users**: Updated every 5 seconds -- **Connection status**: Real-time via WebSocket events - -## πŸ”§ Configuration +Open [http://localhost:3000](http://localhost:3000) in multiple browser tabs, pick different usernames, and send messages. -### WebSocket Setup +## How It Works -The WebSocket server is configured in `chat-server/vite-plugin.ts`: +1. Client opens `newWebSocketRpcSession(url, chatNotifier)` β€” the notifier is exported as Cap'n Web `localMain`. +2. Server receives the client's notifier stub when the WebSocket session starts (`vite-plugin.ts`). +3. Client calls `joinChat(username)`; the server registers that connection's notifier for push. +4. When any user sends a message, `broadcastToAll` invokes each client's `notify()` stub β€” instant push, no polling. ```typescript -// WebSocket endpoint: /api/websocket -wss.handleUpgrade(request, socket, head, (ws) => { - const chatServer = new ChatServer() - chatServer.setWebSocket(ws) - newWebSocketRpcSession(ws, chatServer) -}) +// Client: export ChatNotifier as localMain, call typed ChatApi methods +const notifier = new ChatNotifier() +notifier.onNotification = (n) => { /* update UI */ } +const api = newWebSocketRpcSession(wsUrl, notifier) +await api.joinChat(username) + +// Server (vite plugin): receive client notifier stub from session setup +const clientNotifier = newWebSocketRpcSession(ws, chatServer) +chatServer.setClientNotifier(clientNotifier) ``` -### Message Queue System - -Messages are queued per user to ensure delivery: - -```typescript -// Each user gets their own message queue -export const userMessageQueues = new Map>() - -// Messages are polled and cleared -const messages = userMessageQueues.get(username) || [] -userMessageQueues.set(username, []) // Clear after reading -``` - -## πŸ§ͺ Testing - -```bash -# Run tests -pnpm test -``` - -To test the chat functionality: - -1. Open multiple browser tabs to [http://localhost:3000](http://localhost:3000) -2. Enter different usernames in each tab -3. Send messages and observe real-time delivery -4. Check the online users list updates - -## πŸ“š Key Technologies Explained - -### Cap'n Web RPC - -- **Bidirectional**: Both client and server can call methods on each other -- **Type-safe**: Full TypeScript support for RPC calls -- **WebSocket-based**: Built on standard WebSocket protocol -- **Efficient**: Binary protocol with message queuing - -### TanStack Start - -- **Full-stack**: Single framework for client and server -- **Server-side rendering**: Pages render on the server first -- **File-based routing**: Routes defined by file structure -- **Type-safe**: End-to-end TypeScript support - -### Message Flow - -``` -User types message - ↓ -useChatMessages.sendMessage() - ↓ -Cap'n Web RPC call - ↓ -ChatServer.sendMessage() - ↓ -ChatLogic.sendMessage() - ↓ -Broadcast to all users - ↓ -Message queues updated - ↓ -Clients poll for updates - ↓ -UI updates with new message -``` - -## 🎯 Why This Demo? - -This application demonstrates: - -1. **Real-world WebSocket usage** - Not just a simple echo server -2. **RPC over WebSockets** - More structured than plain message passing -3. **Modern React patterns** - Hooks, effects, and clean component architecture -4. **Full-stack TypeScript** - Shared types between client and server -5. **Production-ready patterns** - Error handling, reconnection, message queuing - -Perfect for learning how to build real-time applications with modern web technologies! - -## 🀝 Contributing - -This is a demonstration project, but improvements are welcome: +## Scripts -- Enhanced error handling -- Message persistence -- User authentication -- Private messaging -- File sharing -- Emoji reactions +| Script | Description | +|--------|-------------| +| `pnpm dev` | Start dev server with Cap'n Web WebSocket (port 3000) | +| `pnpm build` | Build TanStack Start app | +| `pnpm serve` | Preview production build | +| `pnpm test:types` | Typecheck | -## πŸ“„ License +## @Claude Integration -This project is open source and available under the [MIT License](LICENSE). +Set `ANTHROPIC_API_KEY` in `.env`. Messages containing `@Claude` (or starting with `Claude`) queue a response from `claude-sonnet-4-5`. Only one Claude request runs at a time; others wait in queue. ---- +## Key Technologies -**Built with ❀️ using Cap'n Web RPC and TanStack Start** +- **Cap'n Web 0.10** β€” bidirectional RPC, pass-by-reference callbacks, `RpcStub` + `[Symbol.dispose]()` +- **TanStack Start 1.159** β€” `tanstackStart()` + Nitro 3 +- **TanStack AI** β€” Anthropic adapter for Claude mentions diff --git a/examples/ts-group-chat/chat-server/capnweb-rpc.ts b/examples/ts-group-chat/chat-server/capnweb-rpc.ts index d4f8e36f7..603446734 100644 --- a/examples/ts-group-chat/chat-server/capnweb-rpc.ts +++ b/examples/ts-group-chat/chat-server/capnweb-rpc.ts @@ -1,20 +1,25 @@ // Cap'n Web RPC server implementation for chat import { RpcTarget } from 'capnweb' import { ChatLogic } from './chat-logic.js' - +import type { + ChatApi, + ChatNotification, + ChatNotifierApi, + ClaudeQueueStatus, + JoinResult, + SendResult, +} from './chat-api.js' +import type { ClaudeService } from './claude-service.js' import type { WebSocket } from 'ws' -// Local type definition to avoid importing from @tanstack/ai at module parse time -interface ModelMessage { - role: 'system' | 'user' | 'assistant' | 'tool' - content?: string - toolCallId?: string - toolCalls?: Array -} +type NotifyTarget = ChatNotifierApi + +// Global registry of client notification targets (Cap'n Web RpcTarget stubs) +export const clients = new Map() // Lazy-load claude service to avoid importing AI packages at module parse time -let globalClaudeService: any = null -async function getClaudeService() { +let globalClaudeService: ClaudeService | null = null +async function getClaudeService(): Promise { if (!globalClaudeService) { const { globalClaudeService: service } = await import('./claude-service.js') globalClaudeService = service @@ -22,6 +27,29 @@ async function getClaudeService() { return globalClaudeService } +function pushNotification( + notifier: ChatNotifierApi, + notification: ChatNotification, +) { + // Defer serverβ†’client RPC so we never nest calls inside an in-flight handler + // (Cap'n Web 0.10 throws "' is not a function" in the browser otherwise). + queueMicrotask(() => { + void Promise.resolve(notifier.notify(notification)).catch((error) => { + console.error('Failed to push notification to client:', error) + }) + }) +} + +function normalizeNotification( + notification: ChatNotification, +): ChatNotification { + return { + ...notification, + timestamp: notification.timestamp || new Date().toISOString(), + id: notification.id || Math.random().toString(36).slice(2, 11), + } +} + // Global shared chat instance export const globalChat = new ChatLogic({ async onUserJoined(username) { @@ -29,15 +57,20 @@ export const globalChat = new ChatLogic({ type: 'user_joined', message: `${username} joined the chat`, username, + onlineUsers: globalChat.getOnlineUsers(), }) }, async onUserLeft(username) { - await ChatServer.broadcastToAll({ - type: 'user_left', - message: `${username} left the chat`, + await ChatServer.broadcastToAll( + { + type: 'user_left', + message: `${username} left the chat`, + username, + onlineUsers: globalChat.getOnlineUsers(), + }, username, - }) + ) }, async onMessageSent(message) { @@ -54,26 +87,22 @@ export const globalChat = new ChatLogic({ // Global registry of active RPC server instances export const activeServers = new Set() -// Message queue system for each user -export const userMessageQueues = new Map>() - -// Global registry of client callbacks -export const clients = new Map) => void>() - // Chat Server Implementation (one per connection) -export class ChatServer extends RpcTarget { +export class ChatServer extends RpcTarget implements ChatApi { public currentUsername: string | null = null + private clientNotifier: ChatNotifierApi | null = null constructor() { super() - // Register this server instance activeServers.add(this) console.log(`πŸ“‘ Registered new chat server. Total: ${activeServers.size}`) } - // Set the WebSocket connection for this server instance + setClientNotifier(notifier: ChatNotifierApi) { + this.clientNotifier = notifier + } + setWebSocket(ws: WebSocket) { - // Handle WebSocket disconnection ws.on('close', () => { if (this.currentUsername) { this.leaveChat() @@ -83,50 +112,35 @@ export class ChatServer extends RpcTarget { }) } - // Broadcast to all connected users - static async broadcastToAll(notification: any, excludeUser?: string) { - const msgPreview = notification.message?.substring(0, 50) || '' + static async broadcastToAll( + notification: ChatNotification, + excludeUser?: string, + ) { + const payload = normalizeNotification(notification) + const msgPreview = payload.message?.substring(0, 50) || '' console.log( - `\nπŸ“¬ broadcastToAll() - type: ${notification.type}, from: ${notification.username}, message: "${msgPreview}..."`, + `\nπŸ“¬ broadcastToAll() - type: ${payload.type}, from: ${payload.username}, message: "${msgPreview}..."`, ) console.log(`πŸ“¬ Connected users: ${Array.from(clients.keys()).join(', ')}`) console.log(`πŸ“¬ Exclude user: ${excludeUser || 'none'}`) let successCount = 0 - const successful: Array = [] + const successful: string[] = [] - for (const username of clients.keys()) { + for (const [username, callback] of clients.entries()) { if (excludeUser && username === excludeUser) { console.log(`πŸ“¬ Skipping excluded user: ${username}`) continue } - // Add notification to user's message queue - if (!userMessageQueues.has(username)) { - userMessageQueues.set(username, []) - } - - const queue = userMessageQueues.get(username)! - const messageId = - notification.id || Math.random().toString(36).substr(2, 9) - - queue.push({ - ...notification, - timestamp: notification.timestamp || new Date().toISOString(), - id: messageId, - }) - - console.log( - `πŸ“¬ Added to ${username}'s queue (queue size: ${queue.length}, messageId: ${messageId})`, - ) - - // Keep queue size manageable (last 50 messages) - if (queue.length > 50) { - queue.splice(0, queue.length - 50) + try { + pushNotification(callback, payload) + successCount++ + successful.push(username) + console.log(`πŸ“¬ Queued push to ${username}`) + } catch (error) { + console.error(`πŸ“¬ Failed to notify ${username}:`, error) } - - successCount++ - successful.push(username) } console.log( @@ -137,57 +151,50 @@ export class ChatServer extends RpcTarget { return { successful, successCount } } - // Cleanup when connection closes dispose() { activeServers.delete(this) if (this.currentUsername) { clients.delete(this.currentUsername) - userMessageQueues.delete(this.currentUsername) } console.log(`πŸ“‘ Unregistered chat server. Total: ${activeServers.size}`) } - // Client joins the chat - async joinChat( - username: string, - notificationCallback: (...args: Array) => void, - ) { + joinChat(username: string): JoinResult { console.log(`${username} is joining the chat`) - this.currentUsername = username - // Register in global state - clients.set(username, notificationCallback) + if (!this.clientNotifier) { + throw new Error('Client notifier not available on this connection') + } - // Add user to chat logic - await globalChat.addUser(username) + this.currentUsername = username + clients.set(username, this.clientNotifier) - // Send welcome notification - if (!userMessageQueues.has(username)) { - userMessageQueues.set(username, []) - } + globalChat.addUserSync(username) - const welcomeMessage = { + const welcomeMessage = normalizeNotification({ type: 'welcome', message: `Welcome to the chat, ${username}! πŸ‘‹`, - timestamp: new Date().toISOString(), - id: Math.random().toString(36).substr(2, 9), - } + username: 'System', + }) - userMessageQueues.get(username)!.push(welcomeMessage) + pushNotification(this.clientNotifier, welcomeMessage) return { message: 'Successfully joined the chat', onlineUsers: globalChat.getOnlineUsers(), - recentMessages: globalChat.getMessages().slice(-20), // Last 20 messages + recentMessages: globalChat.getMessages().slice(-20), } } - // Client leaves the chat - async leaveChat() { - if (!this.currentUsername) return + leaveChat() { + if (!this.currentUsername) { + return { message: 'Not in chat' } + } - console.log(`${this.currentUsername} is leaving the chat`) - await globalChat.removeUser(this.currentUsername) + const username = this.currentUsername + console.log(`${username} is leaving the chat`) + globalChat.removeUserSync(username) + clients.delete(username) this.currentUsername = null return { @@ -195,34 +202,11 @@ export class ChatServer extends RpcTarget { } } - // Get current chat state getChatState() { return globalChat.getChatState() } - // Poll for new messages from the queue - async pollMessages() { - if (!this.currentUsername) { - return [] - } - - const queue = userMessageQueues.get(this.currentUsername) || [] - const messages = [...queue] // Return copy - - // Clear the queue after reading - userMessageQueues.set(this.currentUsername, []) - - if (messages.length > 0) { - console.log( - `πŸ“¨ ${this.currentUsername} polling: returning ${messages.length} messages`, - ) - } - - return messages - } - - // Send a chat message - async sendMessage(messageText: string) { + sendMessage(messageText: string): SendResult { console.log( `\nπŸ“¨ [${this.currentUsername}] sendMessage called: "${messageText}"`, ) @@ -235,59 +219,19 @@ export class ChatServer extends RpcTarget { throw new Error('Message cannot be empty') } - // Check for Claude trigger pattern - matches @Claude anywhere in message or Claude at start const trimmedMessage = messageText.trim() const isClaudeMention = - /@Claude/i.test(messageText) || // @Claude anywhere in message - /^Claude/i.test(trimmedMessage) || // Claude at start - /^@Claude/i.test(trimmedMessage) // @Claude at start - console.log( - `πŸ“¨ [${this.currentUsername}] Checking for Claude mention in: "${messageText.substring(0, 50)}..."`, - ) - console.log( - `πŸ“¨ [${this.currentUsername}] isClaudeMention: ${ - isClaudeMention ? 'YES' : 'NO' - }`, - ) + /@Claude/i.test(messageText) || + /^Claude/i.test(trimmedMessage) || + /^@Claude/i.test(trimmedMessage) if (isClaudeMention) { - console.log( - `πŸ“¨ [${this.currentUsername}] Claude mention detected, sending user message first`, - ) - - // First, send the user's message to chat - const message = await globalChat.sendMessage( + const message = globalChat.sendMessageSync( this.currentUsername, messageText.trim(), ) - console.log( - `πŸ“¨ [${this.currentUsername}] User message sent, ID: ${message.id}`, - ) - - // Build conversation history for Claude - const conversationHistory: Array = globalChat - .getMessages() - .map((msg) => ({ - role: 'user' as const, - content: `${msg.username}: ${msg.message}`, - })) - console.log( - `πŸ“¨ [${this.currentUsername}] Built history with ${conversationHistory.length} messages`, - ) - - // Enqueue Claude request - const claudeService = await getClaudeService() - claudeService.enqueue({ - id: Math.random().toString(36).substr(2, 9), - username: this.currentUsername, - message: messageText, - conversationHistory, - }) - console.log(`πŸ“¨ [${this.currentUsername}] Claude request enqueued`) - // Start processing immediately (will check queue internally) - console.log(`πŸ“¨ [${this.currentUsername}] Starting processClaudeQueue()`) - this.processClaudeQueue() + void this.enqueueClaudeRequest(messageText) return { message: 'Claude request queued', @@ -295,13 +239,10 @@ export class ChatServer extends RpcTarget { } } - // Regular message handling - console.log(`πŸ“¨ [${this.currentUsername}] Regular message, sending to chat`) - const message = await globalChat.sendMessage( + const message = globalChat.sendMessageSync( this.currentUsername, messageText.trim(), ) - console.log(`πŸ“¨ [${this.currentUsername}] Message sent, ID: ${message.id}`) return { message: 'Message sent successfully', @@ -309,109 +250,80 @@ export class ChatServer extends RpcTarget { } } - // Process Claude queue and stream response - private async processClaudeQueue() { - console.log(`\n🎯 processClaudeQueue() called`) + private async enqueueClaudeRequest(messageText: string) { + const conversationHistory = globalChat.getMessages().map((msg) => ({ + role: 'user' as const, + content: `${msg.username}: ${msg.message}`, + })) + const claudeService = await getClaudeService() + claudeService.enqueue({ + id: Math.random().toString(36).slice(2, 11), + username: this.currentUsername!, + message: messageText, + conversationHistory, + }) + + void this.processClaudeQueue() + } + + private async processClaudeQueue() { const claudeService = await getClaudeService() const status = claudeService.getQueueStatus() - console.log( - `🎯 Queue status: processing=${status.isProcessing}, queue length=${status.queue.length}, current=${status.current}`, - ) - // If already processing or queue is empty, return if (status.isProcessing || status.queue.length === 0) { - console.log( - `🎯 Skipping: ${ - status.isProcessing ? 'already processing' : 'queue empty' - }`, - ) return } - // Start processing - console.log(`🎯 Starting to process queue`) claudeService.startProcessing() try { const currentStatus = claudeService.getQueueStatus() - console.log(`🎯 Current user: ${currentStatus.current}`) - // Broadcast that Claude is responding - console.log(`🎯 Broadcasting claude_responding...`) await ChatServer.broadcastToAll({ type: 'claude_responding', message: `Claude is responding to ${currentStatus.current}...`, username: 'System', }) - // Get conversation history from the current request - const conversationHistory: Array = globalChat - .getMessages() - .map((msg) => ({ - role: 'user' as const, - content: `${msg.username}: ${msg.message}`, - })) - console.log( - `🎯 Built conversation history: ${conversationHistory.length} messages`, - ) + const conversationHistory = globalChat.getMessages().map((msg) => ({ + role: 'user' as const, + content: `${msg.username}: ${msg.message}`, + })) - // Stream Claude response and accumulate text - console.log(`🎯 Starting to stream Claude response...`) let accumulatedResponse = '' for await (const chunk of claudeService.streamResponse( conversationHistory, )) { - if (chunk.type === 'content' && chunk.delta) { + if (chunk.type === 'TEXT_MESSAGE_CONTENT' && chunk.delta) { accumulatedResponse += chunk.delta } } - console.log( - `🎯 Accumulated response (${ - accumulatedResponse.length - } chars): "${accumulatedResponse.substring(0, 100)}..."`, - ) - // Add Claude's response to chat history - // Note: globalChat.sendMessage will automatically broadcast via onMessageSent callback - console.log( - `🎯 Adding Claude message to globalChat (this will auto-broadcast)...`, - ) - const claudeMessage = await globalChat.sendMessage( - 'Claude', - accumulatedResponse, - ) - console.log( - `🎯 Claude message added to globalChat and broadcast automatically, ID: ${claudeMessage.id}`, - ) + await globalChat.sendMessage('Claude', accumulatedResponse) } catch (error) { - console.error('🎯 ERROR in processClaudeQueue:', error) + console.error('Error in processClaudeQueue:', error) - // Broadcast error await ChatServer.broadcastToAll({ type: 'claude_error', message: 'Claude encountered an error responding', username: 'System', }) } finally { - console.log(`🎯 Finishing processing...`) claudeService.finishProcessing() - - // Process next in queue if any - console.log(`🎯 Checking for next in queue...`) - this.processClaudeQueue() + void this.processClaudeQueue() } } - // Get Claude queue status - async getClaudeQueueStatus() { - const claudeService = await getClaudeService() - return claudeService.getQueueStatus() - } + getClaudeQueueStatus(): ClaudeQueueStatus { + if (!globalClaudeService) { + return { + current: null, + queue: [], + isProcessing: false, + } + } - // Stream Claude response (for future use if needed) - async *streamClaudeResponse(conversationHistory: Array) { - const claudeService = await getClaudeService() - yield* claudeService.streamResponse(conversationHistory) + return globalClaudeService.getQueueStatus() } } diff --git a/examples/ts-group-chat/chat-server/chat-api.ts b/examples/ts-group-chat/chat-server/chat-api.ts new file mode 100644 index 000000000..67968a69a --- /dev/null +++ b/examples/ts-group-chat/chat-server/chat-api.ts @@ -0,0 +1,58 @@ +export type ChatNotificationType = + | 'message' + | 'user_joined' + | 'user_left' + | 'welcome' + | 'claude_responding' + | 'claude_error' + +export interface ChatNotification { + type: ChatNotificationType + message: string + username?: string + timestamp?: string + id?: string + onlineUsers?: string[] +} + +export interface ChatMessage { + id: string + username: string + message: string + timestamp: string + type?: ChatNotificationType +} + +export interface ChatState { + onlineUsers: string[] + messages: ChatMessage[] +} + +export interface JoinResult { + message: string + onlineUsers: string[] + recentMessages: ChatMessage[] +} + +export interface SendResult { + message: string + chatMessage?: ChatMessage +} + +export interface ClaudeQueueStatus { + current: string | null + queue: string[] + isProcessing: boolean +} + +export interface ChatNotifierApi { + notify(notification: ChatNotification): void | Promise +} + +export interface ChatApi { + joinChat(username: string): JoinResult + leaveChat(): { message: string } + getChatState(): ChatState + sendMessage(message: string): SendResult + getClaudeQueueStatus(): ClaudeQueueStatus +} diff --git a/examples/ts-group-chat/chat-server/chat-logic.ts b/examples/ts-group-chat/chat-server/chat-logic.ts index 6133a55b0..5ac24459a 100644 --- a/examples/ts-group-chat/chat-server/chat-logic.ts +++ b/examples/ts-group-chat/chat-server/chat-logic.ts @@ -1,16 +1,7 @@ // Core chat business logic and data structures +import type { ChatMessage, ChatState } from './chat-api.js' -export interface ChatMessage { - id: string - username: string - message: string - timestamp: string -} - -export interface ChatState { - onlineUsers: Array - messages: Array -} +export type { ChatMessage, ChatState } // Core chat business logic class export class ChatLogic { @@ -34,6 +25,51 @@ export class ChatLogic { this.onMessageSent = callbacks?.onMessageSent } + addUserSync(username: string) { + if (!this.chatState.onlineUsers.includes(username)) { + this.chatState.onlineUsers.push(username) + console.log(`βœ… ${username} joined the chat`) + + if (this.onUserJoined) { + void this.onUserJoined(username) + } + } + } + + removeUserSync(username: string) { + const index = this.chatState.onlineUsers.indexOf(username) + if (index > -1) { + this.chatState.onlineUsers.splice(index, 1) + console.log(`πŸ‘‹ ${username} left the chat`) + + if (this.onUserLeft) { + void this.onUserLeft(username) + } + } + } + + sendMessageSync(username: string, messageText: string): ChatMessage { + const message: ChatMessage = { + id: Math.random().toString(36).substr(2, 9), + username, + message: messageText, + timestamp: new Date().toISOString(), + } + + this.chatState.messages.push(message) + if (this.chatState.messages.length > 100) { + this.chatState.messages = this.chatState.messages.slice(-100) + } + + console.log(`πŸ’¬ ${username}: ${messageText}`) + + if (this.onMessageSent) { + void this.onMessageSent(message) + } + + return message + } + async addUser(username: string) { if (!this.chatState.onlineUsers.includes(username)) { this.chatState.onlineUsers.push(username) diff --git a/examples/ts-group-chat/chat-server/index.ts b/examples/ts-group-chat/chat-server/index.ts index b299d4dbd..030c7790f 100644 --- a/examples/ts-group-chat/chat-server/index.ts +++ b/examples/ts-group-chat/chat-server/index.ts @@ -1,5 +1,6 @@ // Main entry point for the chat server modules -export * from './chat-logic.js' +export * from './chat-api.js' +export { ChatLogic } from './chat-logic.js' export * from './capnweb-rpc.js' // Note: claude-service is only used internally by capnweb-rpc, no need to export export * from './vite-plugin.js' diff --git a/examples/ts-group-chat/chat-server/vite-plugin.ts b/examples/ts-group-chat/chat-server/vite-plugin.ts index 59e4b9f10..2a54af29a 100644 --- a/examples/ts-group-chat/chat-server/vite-plugin.ts +++ b/examples/ts-group-chat/chat-server/vite-plugin.ts @@ -1,13 +1,14 @@ // Vite WebSocket plugin for handling Cap'n Web RPC connections -import { Plugin } from 'vite' +import type { Plugin } from 'vite' import { WebSocketServer } from 'ws' import { newWebSocketRpcSession } from 'capnweb' import { ChatServer } from './capnweb-rpc.js' +import type { ChatNotifierApi } from './chat-api.js' -// Custom WebSocket RPC plugin for Vite export function websocketRpcPlugin(): Plugin { return { name: 'websocket-rpc-plugin', + enforce: 'pre', configureServer(server) { if (!server.httpServer) return @@ -19,22 +20,22 @@ export function websocketRpcPlugin(): Plugin { const pathname = new URL(request.url!, `http://${request.headers.host}`) .pathname - // Only handle our specific websocket path if (pathname === '/api/websocket') { wss.handleUpgrade(request, socket, head, (ws) => { console.log( 'WebSocket RPC connection established on /api/websocket', ) - // Create a new RPC server instance for this connection const chatServer = new ChatServer() - - // Store WebSocket reference in server chatServer.setWebSocket(ws) - // Set up Cap'n Web RPC over this websocket - // Cast to any to handle Node.js WebSocket vs Browser WebSocket interface differences - newWebSocketRpcSession(ws as any, chatServer) + // Client exports ChatNotifier as localMain; server receives that stub here. + const clientNotifier = newWebSocketRpcSession( + ws as never, + chatServer, + ) as unknown as ChatNotifierApi + + chatServer.setClientNotifier(clientNotifier) }) } }) diff --git a/examples/ts-group-chat/package.json b/examples/ts-group-chat/package.json index 94979fd17..eb4e60cab 100644 --- a/examples/ts-group-chat/package.json +++ b/examples/ts-group-chat/package.json @@ -3,8 +3,9 @@ "private": true, "type": "module", "scripts": { - "dev": "vite dev --port 4000", - "build": "exit 0", + "dev": "vite dev --port 3000", + "build": "vite build", + "serve": "vite preview", "test": "exit 0", "test:types": "tsc --noEmit" }, @@ -12,15 +13,13 @@ "@tailwindcss/vite": "^4.1.18", "@tanstack/ai": "workspace:*", "@tanstack/ai-anthropic": "workspace:*", - "@tanstack/ai-client": "workspace:*", - "@tanstack/ai-react": "workspace:*", "@tanstack/react-devtools": "^0.9.10", "@tanstack/react-router": "^1.158.4", "@tanstack/react-router-devtools": "^1.158.4", - "@tanstack/react-router-ssr-query": "^1.158.4", "@tanstack/react-start": "^1.159.0", "@tanstack/router-plugin": "^1.158.4", - "capnweb": "^0.1.0", + "capnweb": "^0.10.0", + "nitro": "3.0.1-alpha.2", "react": "^19.2.3", "react-dom": "^19.2.3", "tailwindcss": "^4.1.18", diff --git a/examples/ts-group-chat/pnpm-lock.yaml b/examples/ts-group-chat/pnpm-lock.yaml deleted file mode 100644 index ead975523..000000000 --- a/examples/ts-group-chat/pnpm-lock.yaml +++ /dev/null @@ -1,3637 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@tailwindcss/vite': - specifier: ^4.0.6 - version: 4.1.13(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)) - '@tanstack/react-devtools': - specifier: ^0.2.2 - version: 0.2.2(@types/react-dom@19.1.9(@types/react@19.1.14))(@types/react@19.1.14)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.9) - '@tanstack/react-router': - specifier: ^1.132.0 - version: 1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@tanstack/react-router-devtools': - specifier: ^1.132.0 - version: 1.132.11(@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.132.7)(@types/node@22.18.6)(csstype@3.1.3)(jiti@2.6.0)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.9)(tiny-invariant@1.3.3)(tsx@4.20.6) - '@tanstack/react-router-ssr-query': - specifier: ^1.131.7 - version: 1.132.7(@tanstack/query-core@5.90.2)(@tanstack/react-query@5.90.2(react@19.1.1))(@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.132.7)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@tanstack/react-start': - specifier: ^1.132.0 - version: 1.132.13(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)) - '@tanstack/router-plugin': - specifier: ^1.132.0 - version: 1.132.7(@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)) - '@types/ws': - specifier: ^8.18.1 - version: 8.18.1 - capnweb: - specifier: ^0.1.0 - version: 0.1.0 - react: - specifier: ^19.0.0 - version: 19.1.1 - react-dom: - specifier: ^19.0.0 - version: 19.1.1(react@19.1.1) - tailwindcss: - specifier: ^4.0.6 - version: 4.1.13 - vite-tsconfig-paths: - specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)) - ws: - specifier: ^8.18.3 - version: 8.18.3 - devDependencies: - '@testing-library/dom': - specifier: ^10.4.0 - version: 10.4.1 - '@testing-library/react': - specifier: ^16.2.0 - version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.9(@types/react@19.1.14))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@types/node': - specifier: ^22.10.2 - version: 22.18.6 - '@types/react': - specifier: ^19.0.8 - version: 19.1.14 - '@types/react-dom': - specifier: ^19.0.3 - version: 19.1.9(@types/react@19.1.14) - '@vitejs/plugin-react': - specifier: ^4.3.4 - version: 4.7.0(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)) - jsdom: - specifier: ^26.0.0 - version: 26.1.0 - typescript: - specifier: ^5.7.2 - version: 5.9.2 - vite: - specifier: ^7.1.7 - version: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6) - vitest: - specifier: ^3.0.5 - version: 3.2.4(@types/node@22.18.6)(jiti@2.6.0)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.6) - web-vitals: - specifier: ^4.2.4 - version: 4.2.4 - -packages: - - '@asamuzakjp/css-color@3.2.0': - resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} - - '@babel/code-frame@7.26.2': - resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} - engines: {node: '>=6.9.0'} - - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.28.4': - resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.28.4': - resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.28.3': - resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-annotate-as-pure@7.27.3': - resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-create-class-features-plugin@7.28.3': - resolution: {integrity: sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-member-expression-to-functions@7.27.1': - resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-optimise-call-expression@7.27.1': - resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-plugin-utils@7.27.1': - resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-replace-supers@7.27.1': - resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': - resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.28.4': - resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.28.4': - resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-syntax-jsx@7.27.1': - resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.27.1': - resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-commonjs@7.27.1': - resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-self@7.27.1': - resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.27.1': - resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-typescript@7.28.0': - resolution: {integrity: sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/preset-typescript@7.27.1': - resolution: {integrity: sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/runtime@7.28.4': - resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} - engines: {node: '>=6.9.0'} - - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.28.4': - resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.28.4': - resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} - engines: {node: '>=6.9.0'} - - '@csstools/color-helpers@5.1.0': - resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} - engines: {node: '>=18'} - - '@csstools/css-calc@2.1.4': - resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} - engines: {node: '>=18'} - peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 - - '@csstools/css-color-parser@3.1.0': - resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} - engines: {node: '>=18'} - peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 - - '@csstools/css-parser-algorithms@3.0.5': - resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} - engines: {node: '>=18'} - peerDependencies: - '@csstools/css-tokenizer': ^3.0.4 - - '@csstools/css-tokenizer@3.0.4': - resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} - engines: {node: '>=18'} - - '@esbuild/aix-ppc64@0.25.10': - resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.25.10': - resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.25.10': - resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.25.10': - resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.25.10': - resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.10': - resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.25.10': - resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.10': - resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.25.10': - resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.25.10': - resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.25.10': - resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.25.10': - resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.25.10': - resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.25.10': - resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.10': - resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.25.10': - resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.25.10': - resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.25.10': - resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.25.10': - resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.25.10': - resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.25.10': - resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.25.10': - resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.25.10': - resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.25.10': - resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.25.10': - resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.25.10': - resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@isaacs/fs-minipass@4.0.1': - resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} - engines: {node: '>=18.0.0'} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@oozcitak/dom@1.15.10': - resolution: {integrity: sha512-0JT29/LaxVgRcGKvHmSrUTEvZ8BXvZhGl2LASRUgHqDTC1M5g1pLmVv56IYNyt3bG2CUjDkc67wnyZC14pbQrQ==} - engines: {node: '>=8.0'} - - '@oozcitak/infra@1.0.8': - resolution: {integrity: sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==} - engines: {node: '>=6.0'} - - '@oozcitak/url@1.0.4': - resolution: {integrity: sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==} - engines: {node: '>=8.0'} - - '@oozcitak/util@8.3.8': - resolution: {integrity: sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ==} - engines: {node: '>=8.0'} - - '@rolldown/pluginutils@1.0.0-beta.27': - resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} - - '@rolldown/pluginutils@1.0.0-beta.40': - resolution: {integrity: sha512-s3GeJKSQOwBlzdUrj4ISjJj5SfSh+aqn0wjOar4Bx95iV1ETI7F6S/5hLcfAxZ9kXDcyrAkxPlqmd1ZITttf+w==} - - '@rollup/rollup-android-arm-eabi@4.52.2': - resolution: {integrity: sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.52.2': - resolution: {integrity: sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.52.2': - resolution: {integrity: sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.52.2': - resolution: {integrity: sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.52.2': - resolution: {integrity: sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.52.2': - resolution: {integrity: sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.52.2': - resolution: {integrity: sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.52.2': - resolution: {integrity: sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.52.2': - resolution: {integrity: sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.52.2': - resolution: {integrity: sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loong64-gnu@4.52.2': - resolution: {integrity: sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-ppc64-gnu@4.52.2': - resolution: {integrity: sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.52.2': - resolution: {integrity: sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.52.2': - resolution: {integrity: sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.52.2': - resolution: {integrity: sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.52.2': - resolution: {integrity: sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.52.2': - resolution: {integrity: sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-openharmony-arm64@4.52.2': - resolution: {integrity: sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-win32-arm64-msvc@4.52.2': - resolution: {integrity: sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.52.2': - resolution: {integrity: sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.52.2': - resolution: {integrity: sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.52.2': - resolution: {integrity: sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==} - cpu: [x64] - os: [win32] - - '@solid-primitives/event-listener@2.4.3': - resolution: {integrity: sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg==} - peerDependencies: - solid-js: ^1.6.12 - - '@solid-primitives/keyboard@1.3.3': - resolution: {integrity: sha512-9dQHTTgLBqyAI7aavtO+HnpTVJgWQA1ghBSrmLtMu1SMxLPDuLfuNr+Tk5udb4AL4Ojg7h9JrKOGEEDqsJXWJA==} - peerDependencies: - solid-js: ^1.6.12 - - '@solid-primitives/rootless@1.5.2': - resolution: {integrity: sha512-9HULb0QAzL2r47CCad0M+NKFtQ+LrGGNHZfteX/ThdGvKIg2o2GYhBooZubTCd/RTu2l2+Nw4s+dEfiDGvdrrQ==} - peerDependencies: - solid-js: ^1.6.12 - - '@solid-primitives/utils@6.3.2': - resolution: {integrity: sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ==} - peerDependencies: - solid-js: ^1.6.12 - - '@tailwindcss/node@4.1.13': - resolution: {integrity: sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==} - - '@tailwindcss/oxide-android-arm64@4.1.13': - resolution: {integrity: sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.1.13': - resolution: {integrity: sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.1.13': - resolution: {integrity: sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.1.13': - resolution: {integrity: sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13': - resolution: {integrity: sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.1.13': - resolution: {integrity: sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-musl@4.1.13': - resolution: {integrity: sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@tailwindcss/oxide-linux-x64-gnu@4.1.13': - resolution: {integrity: sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@tailwindcss/oxide-linux-x64-musl@4.1.13': - resolution: {integrity: sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@tailwindcss/oxide-wasm32-wasi@4.1.13': - resolution: {integrity: sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.1.13': - resolution: {integrity: sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.1.13': - resolution: {integrity: sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.1.13': - resolution: {integrity: sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==} - engines: {node: '>= 10'} - - '@tailwindcss/vite@4.1.13': - resolution: {integrity: sha512-0PmqLQ010N58SbMTJ7BVJ4I2xopiQn/5i6nlb4JmxzQf8zcS5+m2Cv6tqh+sfDwtIdjoEnOvwsGQ1hkUi8QEHQ==} - peerDependencies: - vite: ^5.2.0 || ^6 || ^7 - - '@tanstack/devtools-event-bus@0.2.1': - resolution: {integrity: sha512-JMq3AmrQR2LH9P8Rcj1MTq8Iq/mPk/PyuqSw1L0hO2Wl8G1oz5ue31fS8u8lIgOCVR/mGdJah18p+Pj5OosRJA==} - engines: {node: '>=18'} - - '@tanstack/devtools-ui@0.2.2': - resolution: {integrity: sha512-G2gRyoGpjtr25w9BqQzPbefiJ9WALWYLOVxVr+NoqOLDA680nUO5KJjp7oLZMfnKssWxACgulaQ3DUmGGJuysQ==} - engines: {node: '>=18'} - peerDependencies: - solid-js: '>=1.9.7' - - '@tanstack/devtools@0.3.0': - resolution: {integrity: sha512-uxj3MM2/ZlQDlhnBoUSlJC8oneJ+sPh6hK0kgiKZAEgrlUvZfUNgKGhrmMHZaAGl/WEglhZoxXwvvo9HAFT8yw==} - engines: {node: '>=18'} - peerDependencies: - solid-js: '>=1.9.7' - - '@tanstack/directive-functions-plugin@1.132.11': - resolution: {integrity: sha512-ObfCBFd0iTfi8a8EJnDk9gxFBntB+nyCboQHdkM8RJhmSd3kGmMQgZEcioltIoNX+VxfvX1djlA1ZZ5HYArsMg==} - engines: {node: '>=12'} - peerDependencies: - vite: '>=6.0.0 || >=7.0.0' - - '@tanstack/history@1.132.0': - resolution: {integrity: sha512-GG2R9I6QSlbNR9fEuX2sQCigY6K28w51h2634TWmkaHXlzQw+rWuIWr4nAGM9doA+kWRi1LFSFMvAiG3cOqjXQ==} - engines: {node: '>=12'} - - '@tanstack/query-core@5.90.2': - resolution: {integrity: sha512-k/TcR3YalnzibscALLwxeiLUub6jN5EDLwKDiO7q5f4ICEoptJ+n9+7vcEFy5/x/i6Q+Lb/tXrsKCggf5uQJXQ==} - - '@tanstack/react-devtools@0.2.2': - resolution: {integrity: sha512-Ig8ZYqUPJ+nwRvF/RpkQHPbgEkrL3b2PjeYBgXgT5OemyRUlmG12UutvMBV+bJuBsSOKHrNf29IvzC0Vw9Bt1A==} - engines: {node: '>=18'} - peerDependencies: - '@types/react': '>=16.8' - '@types/react-dom': '>=16.8' - react: '>=16.8' - react-dom: '>=16.8' - - '@tanstack/react-query@5.90.2': - resolution: {integrity: sha512-CLABiR+h5PYfOWr/z+vWFt5VsOA2ekQeRQBFSKlcoW6Ndx/f8rfyVmq4LbgOM4GG2qtxAxjLYLOpCNTYm4uKzw==} - peerDependencies: - react: ^18 || ^19 - - '@tanstack/react-router-devtools@1.132.11': - resolution: {integrity: sha512-5UxJjyHxDBX2Sh8E5oN+YagfHiQ0bnhDx6Gguff2sCcMw/MgxaAcQ51EoQcAOwt36KAvh9CIp6VJiM9X6uhVRw==} - engines: {node: '>=12'} - peerDependencies: - '@tanstack/react-router': ^1.132.7 - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' - - '@tanstack/react-router-ssr-query@1.132.7': - resolution: {integrity: sha512-PH2C4BqvI+y0fgDRnJbr863dlh3Gf6R64o1seP/FPR4WMQn9UfdfDWADMVriGs+R93y1DKnIU6gnYdCM2n39OA==} - engines: {node: '>=12'} - peerDependencies: - '@tanstack/query-core': '>=5.66.0' - '@tanstack/react-query': '>=5.66.2' - '@tanstack/react-router': '>=1.127.0' - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' - - '@tanstack/react-router@1.132.7': - resolution: {integrity: sha512-NuZHVFA2Mh1MIPpSet8g6dU6bbLM6AMonNFmhU4tCgQgjOW8oeqnnfWZ4hf4V++mD7CM/E53iNHUOLY2CEjc7Q==} - engines: {node: '>=12'} - peerDependencies: - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' - - '@tanstack/react-start-client@1.132.12': - resolution: {integrity: sha512-s6tjAB/I5zhHQ8M8icA9aVxHR6XEBkE0qcMWuDZfMZqpRH4NV00JjDADxwNn3C5MijyFHbO64x6Hx6i4UhcCIA==} - engines: {node: '>=22.12.0'} - peerDependencies: - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' - - '@tanstack/react-start-server@1.132.13': - resolution: {integrity: sha512-0hTj/mdhOwpvksFrtSvb5oIXQN0bJUnICauAdRP8kys+KHOkYGf/n9AemfPj6fIwlyxvjichJxcRtHLykLWymg==} - engines: {node: '>=22.12.0'} - peerDependencies: - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' - - '@tanstack/react-start@1.132.13': - resolution: {integrity: sha512-H+m1E5mqQDeTERvWBfbJCdNdJwSqBoEGmcNqlFqoV8sGx/Ogj0w64b8z+vNsrYEShgeXnHnn+ZjVlDUtelEvQw==} - engines: {node: '>=22.12.0'} - peerDependencies: - react: '>=18.0.0 || >=19.0.0' - react-dom: '>=18.0.0 || >=19.0.0' - vite: '>=7.0.0' - - '@tanstack/react-store@0.7.7': - resolution: {integrity: sha512-qqT0ufegFRDGSof9D/VqaZgjNgp4tRPHZIJq2+QIHkMUtHjaJ0lYrrXjeIUJvjnTbgPfSD1XgOMEt0lmANn6Zg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - '@tanstack/router-core@1.132.7': - resolution: {integrity: sha512-HgarEo427V1rlpQtA1jIoK4/S/JYgM7/mU3TKO2d7/tyx0FRE9LP8hvUmlxz/Zi71lD6yaICHoCSVcOh0dGLbA==} - engines: {node: '>=12'} - - '@tanstack/router-devtools-core@1.132.11': - resolution: {integrity: sha512-77cMrmVzLXVuSxTr6HTQSTVqzRtY4ITyDk4pZs2QI84dHujt0aMjHj9WnvqBe/0QW0nqgJBi4e8PmsVNXgUv7Q==} - engines: {node: '>=12'} - peerDependencies: - '@tanstack/router-core': ^1.132.7 - csstype: ^3.0.10 - solid-js: '>=1.9.5' - tiny-invariant: ^1.3.3 - peerDependenciesMeta: - csstype: - optional: true - - '@tanstack/router-generator@1.132.7': - resolution: {integrity: sha512-DdBpKBH9itPJXiZIHB9aomVo5/lmFRAPOq/Dk//dXA/cD66ioBrcHqraenM6SqhbjQE8h/6tkgzrpIqyKdjBtQ==} - engines: {node: '>=12'} - - '@tanstack/router-plugin@1.132.7': - resolution: {integrity: sha512-uyx7kosj/y1nfj2L5vARu5aUUVGcFlh52Rm8dcqR64X/6k/5MRP/uFk2icf693BsZwmwisbU7CAihBbvIhWT6w==} - engines: {node: '>=12'} - peerDependencies: - '@rsbuild/core': '>=1.0.2' - '@tanstack/react-router': ^1.132.7 - vite: '>=5.0.0 || >=6.0.0 || >=7.0.0' - vite-plugin-solid: ^2.11.8 - webpack: '>=5.92.0' - peerDependenciesMeta: - '@rsbuild/core': - optional: true - '@tanstack/react-router': - optional: true - vite: - optional: true - vite-plugin-solid: - optional: true - webpack: - optional: true - - '@tanstack/router-ssr-query-core@1.132.7': - resolution: {integrity: sha512-6ykbJZKGRDm3o3IBq3Uahl1TYSkvmpPuJhRrPNIYK/HmBrSrmrP4JeAZkDfJ9GClpqp3WwOcxDkuU6rkx1Napg==} - engines: {node: '>=12'} - peerDependencies: - '@tanstack/query-core': '>=5.66.0' - '@tanstack/router-core': '>=1.127.0' - - '@tanstack/router-utils@1.132.0': - resolution: {integrity: sha512-WDnvAi9kO20joLDzlsTvfgXNv+FgQ4G98xAD8r4jKWoTdTTG05DU2sRYimtbdq4Q7E3uVdvyvPdhRy45wan7bw==} - engines: {node: '>=12'} - - '@tanstack/server-functions-plugin@1.132.11': - resolution: {integrity: sha512-g7NNREKMFyn0/rClY5xfGbDcwBGS0lMxrrjM+jiqruX07Du32X0f9D9BbYAvXZJn4H7GPKv18LFnzbcs2kSsPQ==} - engines: {node: '>=12'} - - '@tanstack/start-client-core@1.132.12': - resolution: {integrity: sha512-8I02Ux/3N/gUExg0Gz/JwNIxq4n0vLkgYw2vBbSzqwDCUKPNKeCk7ic5wI+rd8kwOZSwBR++nM6vrJhAVMh6Og==} - engines: {node: '>=22.12.0'} - - '@tanstack/start-plugin-core@1.132.13': - resolution: {integrity: sha512-gDYLp2NKDgLZll/QeQ+dcaMOowKser0nSzJ4LIeVgvqaZQVeEPMvmQyYlnX2MnAoFy/LWuN5Wb9DoYlB8uNXMg==} - engines: {node: '>=22.12.0'} - peerDependencies: - vite: '>=7.0.0' - - '@tanstack/start-server-core@1.132.13': - resolution: {integrity: sha512-hG5ZvFws0iYVbuShe0Un0KrOyW13+u5VI970g6l5/CXgF/CwbJdayVnkwThnpUcYpBjSguDgy8PHNBoVCRj/Sw==} - engines: {node: '>=22.12.0'} - - '@tanstack/start-storage-context@1.132.7': - resolution: {integrity: sha512-b+9dAKhpr7ZcNmfm9fmTkdh2yX7n787SEeMjrK+LSeIU82r+0lAmEbFNSCybDpnuHrnmQc11rdjVT+xqSQxp7w==} - engines: {node: '>=22.12.0'} - - '@tanstack/store@0.7.7': - resolution: {integrity: sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ==} - - '@tanstack/virtual-file-routes@1.132.0': - resolution: {integrity: sha512-d3do4ih9IdLPBVY4Gb8x7Ho7z0oFDLpxoao7uNVkfWtYU7nc3B+rnnVejXIgprmI5gt1hNzyNDJFr8G/W926GA==} - engines: {node: '>=12'} - - '@testing-library/dom@10.4.1': - resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} - engines: {node: '>=18'} - - '@testing-library/react@16.3.0': - resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==} - engines: {node: '>=18'} - peerDependencies: - '@testing-library/dom': ^10.0.0 - '@types/react': ^18.0.0 || ^19.0.0 - '@types/react-dom': ^18.0.0 || ^19.0.0 - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@types/aria-query@5.0.4': - resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.27.0': - resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.28.0': - resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} - - '@types/chai@5.2.2': - resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/node@22.18.6': - resolution: {integrity: sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==} - - '@types/react-dom@19.1.9': - resolution: {integrity: sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==} - peerDependencies: - '@types/react': ^19.0.0 - - '@types/react@19.1.14': - resolution: {integrity: sha512-ukd93VGzaNPMAUPy0gRDSC57UuQbnH9Kussp7HBjM06YFi9uZTFhOvMSO2OKqXm1rSgzOE+pVx1k1PYHGwlc8Q==} - - '@types/ws@8.18.1': - resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - - '@vitejs/plugin-react@4.7.0': - resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - - '@vitest/expect@3.2.4': - resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - - '@vitest/mocker@3.2.4': - resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} - peerDependencies: - msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@3.2.4': - resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - - '@vitest/runner@3.2.4': - resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - - '@vitest/snapshot@3.2.4': - resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - - '@vitest/spy@3.2.4': - resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - - '@vitest/utils@3.2.4': - resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} - engines: {node: '>=0.4.0'} - hasBin: true - - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - ansis@4.1.0: - resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==} - engines: {node: '>=14'} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - - aria-query@5.3.0: - resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - ast-types@0.16.1: - resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} - engines: {node: '>=4'} - - babel-dead-code-elimination@1.0.10: - resolution: {integrity: sha512-DV5bdJZTzZ0zn0DC24v3jD7Mnidh6xhKa4GfKCbq3sfW8kaWhDdZjP3i81geA8T33tdYqWKw4D3fVv0CwEgKVA==} - - baseline-browser-mapping@2.8.7: - resolution: {integrity: sha512-bxxN2M3a4d1CRoQC//IqsR5XrLh0IJ8TCv2x6Y9N0nckNz/rTjZB3//GGscZziZOxmjP55rzxg/ze7usFI9FqQ==} - hasBin: true - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - browserslist@4.26.2: - resolution: {integrity: sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - caniuse-lite@1.0.30001745: - resolution: {integrity: sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==} - - capnweb@0.1.0: - resolution: {integrity: sha512-+pygKx1JFTZTRdd1hHgaBRg5BwULEDZq8ZoHXkYP2GXNV3lrjXLj5qzlGz+SgBCJjWUmNBtlh7JPWdr0wIbY8w==} - - chai@5.3.3: - resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} - engines: {node: '>=18'} - - check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} - engines: {node: '>= 16'} - - cheerio-select@2.1.0: - resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} - - cheerio@1.1.2: - resolution: {integrity: sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==} - engines: {node: '>=20.18.1'} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - chownr@3.0.0: - resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} - engines: {node: '>=18'} - - clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - cookie-es@2.0.0: - resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} - - css-select@5.2.2: - resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} - - css-what@6.2.2: - resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} - engines: {node: '>= 6'} - - cssstyle@4.6.0: - resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} - engines: {node: '>=18'} - - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - - data-urls@5.0.0: - resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} - engines: {node: '>=18'} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decimal.js@10.6.0: - resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - detect-libc@2.1.1: - resolution: {integrity: sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==} - engines: {node: '>=8'} - - diff@8.0.2: - resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} - engines: {node: '>=0.3.1'} - - dom-accessibility-api@0.5.16: - resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} - - dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} - - domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - - domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} - - domutils@3.2.2: - resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} - - electron-to-chromium@1.5.224: - resolution: {integrity: sha512-kWAoUu/bwzvnhpdZSIc6KUyvkI1rbRXMT0Eq8pKReyOyaPZcctMli+EgvcN1PAvwVc7Tdo4Fxi2PsLNDU05mdg==} - - encoding-sniffer@0.2.1: - resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==} - - enhanced-resolve@5.18.3: - resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} - engines: {node: '>=10.13.0'} - - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - - entities@6.0.1: - resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} - engines: {node: '>=0.12'} - - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - - esbuild@0.25.10: - resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} - engines: {node: '>=18'} - hasBin: true - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - expect-type@1.2.2: - resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} - engines: {node: '>=12.0.0'} - - exsolve@1.0.7: - resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} - - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} - - fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fetchdts@0.1.7: - resolution: {integrity: sha512-YoZjBdafyLIop9lSxXVI33oLD5kN31q4Td+CasofLLYeLXRFeOsuOw0Uo+XNRi9PZlbfdlN2GmRtm4tCEQ9/KA==} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-tsconfig@4.10.1: - resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - - goober@2.1.16: - resolution: {integrity: sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==} - peerDependencies: - csstype: ^3.0.10 - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - h3@2.0.0-beta.4: - resolution: {integrity: sha512-/JdwHUGuHjbBXAVxQN7T7QeI9cVlhsqMKVNFHebZVs9RoEYH85Ogh9O1DEy/1ZiJkmMwa1gNg6bBcGhc1Itjdg==} - engines: {node: '>=20.11.1'} - peerDependencies: - crossws: ^0.4.1 - peerDependenciesMeta: - crossws: - optional: true - - html-encoding-sniffer@4.0.0: - resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} - engines: {node: '>=18'} - - htmlparser2@10.0.0: - resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==} - - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} - - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-potential-custom-element-name@1.0.1: - resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - - isbot@5.1.31: - resolution: {integrity: sha512-DPgQshehErHAqSCKDb3rNW03pa2wS/v5evvUqtxt6TTnHRqAG8FdzcSSJs9656pK6Y+NT7K9R4acEYXLHYfpUQ==} - engines: {node: '>=18'} - - jiti@2.6.0: - resolution: {integrity: sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==} - hasBin: true - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - - js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true - - jsdom@26.1.0: - resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} - engines: {node: '>=18'} - peerDependencies: - canvas: ^3.0.0 - peerDependenciesMeta: - canvas: - optional: true - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - lightningcss-darwin-arm64@1.30.1: - resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.30.1: - resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.30.1: - resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.30.1: - resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.30.1: - resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-arm64-musl@1.30.1: - resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-x64-gnu@1.30.1: - resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-linux-x64-musl@1.30.1: - resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-win32-arm64-msvc@1.30.1: - resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.30.1: - resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.30.1: - resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} - engines: {node: '>= 12.0.0'} - - loupe@3.2.1: - resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - lz-string@1.5.0: - resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} - hasBin: true - - magic-string@0.30.19: - resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - - minizlib@3.1.0: - resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} - engines: {node: '>= 18'} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - node-releases@2.0.21: - resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - - nwsapi@2.2.22: - resolution: {integrity: sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==} - - parse5-htmlparser2-tree-adapter@7.1.0: - resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} - - parse5-parser-stream@7.1.2: - resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==} - - parse5@7.3.0: - resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} - engines: {node: ^10 || ^12 || >=14} - - prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} - engines: {node: '>=14'} - hasBin: true - - pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - react-dom@19.1.1: - resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==} - peerDependencies: - react: ^19.1.1 - - react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - - react-refresh@0.17.0: - resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} - engines: {node: '>=0.10.0'} - - react@19.1.1: - resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} - engines: {node: '>=0.10.0'} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - recast@0.23.11: - resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} - engines: {node: '>= 4'} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rollup@4.52.2: - resolution: {integrity: sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - rou3@0.7.5: - resolution: {integrity: sha512-bwUHDHw1HSARty7TWNV71R0NZs5fOt74OM+hcMdJyPfchfRktEmxLoMSNa7PwEp6WqJ0a3feKztsIfTUEYhskw==} - - rrweb-cssom@0.8.0: - resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - saxes@6.0.0: - resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} - engines: {node: '>=v12.22.7'} - - scheduler@0.26.0: - resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - seroval-plugins@1.3.3: - resolution: {integrity: sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==} - engines: {node: '>=10'} - peerDependencies: - seroval: ^1.0 - - seroval@1.3.2: - resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==} - engines: {node: '>=10'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - solid-js@1.9.9: - resolution: {integrity: sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - source-map@0.7.6: - resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} - engines: {node: '>= 12'} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - srvx@0.8.7: - resolution: {integrity: sha512-g3+15LlwVOGL2QpoTPZlvRjg+9a5Tx/69CatXjFP6txvhIaW2FmGyzJfb8yft5wyfGddvJmP/Yx+e/uNDMRSLQ==} - engines: {node: '>=20.16.0'} - hasBin: true - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - std-env@3.9.0: - resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} - - strip-literal@3.0.0: - resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} - - symbol-tree@3.2.4: - resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - - tailwindcss@4.1.13: - resolution: {integrity: sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==} - - tapable@2.2.3: - resolution: {integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==} - engines: {node: '>=6'} - - tar@7.5.1: - resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==} - engines: {node: '>=18'} - - tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - - tiny-warning@1.0.3: - resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} - engines: {node: '>=12.0.0'} - - tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} - - tinyspy@4.0.4: - resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} - engines: {node: '>=14.0.0'} - - tldts-core@6.1.86: - resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} - - tldts@6.1.86: - resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} - hasBin: true - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - tough-cookie@5.1.2: - resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} - engines: {node: '>=16'} - - tr46@5.1.1: - resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} - engines: {node: '>=18'} - - tsconfck@3.1.6: - resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} - engines: {node: ^18 || >=20} - hasBin: true - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsx@4.20.6: - resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==} - engines: {node: '>=18.0.0'} - hasBin: true - - typescript@5.9.2: - resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} - engines: {node: '>=14.17'} - hasBin: true - - ufo@1.6.1: - resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - undici@7.16.0: - resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} - engines: {node: '>=20.18.1'} - - unplugin@2.3.10: - resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==} - engines: {node: '>=18.12.0'} - - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - use-sync-external-store@1.5.0: - resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - vite-node@3.2.4: - resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - - vite-tsconfig-paths@5.1.4: - resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} - peerDependencies: - vite: '*' - peerDependenciesMeta: - vite: - optional: true - - vite@7.1.7: - resolution: {integrity: sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vitefu@1.1.1: - resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} - peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 - peerDependenciesMeta: - vite: - optional: true - - vitest@3.2.4: - resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.4 - '@vitest/ui': 3.2.4 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/debug': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - w3c-xmlserializer@5.0.0: - resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} - engines: {node: '>=18'} - - web-vitals@4.2.4: - resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==} - - webidl-conversions@7.0.0: - resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} - engines: {node: '>=12'} - - webpack-virtual-modules@0.6.2: - resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - - whatwg-encoding@3.1.1: - resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} - engines: {node: '>=18'} - - whatwg-mimetype@4.0.0: - resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} - engines: {node: '>=18'} - - whatwg-url@14.2.0: - resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} - engines: {node: '>=18'} - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - xml-name-validator@5.0.0: - resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} - engines: {node: '>=18'} - - xmlbuilder2@3.1.1: - resolution: {integrity: sha512-WCSfbfZnQDdLQLiMdGUQpMxxckeQ4oZNMNhLVkcekTu7xhD4tuUDyAPoY8CwXvBYE6LwBHd6QW2WZXlOWr1vCw==} - engines: {node: '>=12.0'} - - xmlchars@2.2.0: - resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - yallist@5.0.0: - resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} - engines: {node: '>=18'} - - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - -snapshots: - - '@asamuzakjp/css-color@3.2.0': - dependencies: - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - lru-cache: 10.4.3 - - '@babel/code-frame@7.26.2': - dependencies: - '@babel/helper-validator-identifier': 7.27.1 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/code-frame@7.27.1': - dependencies: - '@babel/helper-validator-identifier': 7.27.1 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.28.4': {} - - '@babel/core@7.28.4': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) - '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.4 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.28.3': - dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/helper-annotate-as-pure@7.27.3': - dependencies: - '@babel/types': 7.28.4 - - '@babel/helper-compilation-targets@7.27.2': - dependencies: - '@babel/compat-data': 7.28.4 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.26.2 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-create-class-features-plugin@7.28.3(@babel/core@7.28.4)': - dependencies: - '@babel/core': 7.28.4 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-member-expression-to-functions': 7.27.1 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.28.4 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/helper-globals@7.28.0': {} - - '@babel/helper-member-expression-to-functions@7.27.1': - dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-imports@7.27.1': - dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': - dependencies: - '@babel/core': 7.28.4 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4 - transitivePeerDependencies: - - supports-color - - '@babel/helper-optimise-call-expression@7.27.1': - dependencies: - '@babel/types': 7.28.4 - - '@babel/helper-plugin-utils@7.27.1': {} - - '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.4)': - dependencies: - '@babel/core': 7.28.4 - '@babel/helper-member-expression-to-functions': 7.27.1 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.4 - transitivePeerDependencies: - - supports-color - - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': - dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 - transitivePeerDependencies: - - supports-color - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.27.1': {} - - '@babel/helper-validator-option@7.27.1': {} - - '@babel/helpers@7.28.4': - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.4 - - '@babel/parser@7.28.4': - dependencies: - '@babel/types': 7.28.4 - - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.4)': - dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.4)': - dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.4)': - dependencies: - '@babel/core': 7.28.4 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.4)': - dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.4)': - dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.28.4)': - dependencies: - '@babel/core': 7.28.4 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4) - transitivePeerDependencies: - - supports-color - - '@babel/preset-typescript@7.27.1(@babel/core@7.28.4)': - dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.4) - transitivePeerDependencies: - - supports-color - - '@babel/runtime@7.28.4': {} - - '@babel/template@7.27.2': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 - - '@babel/traverse@7.28.4': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.4 - '@babel/template': 7.27.2 - '@babel/types': 7.28.4 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.28.4': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - - '@csstools/color-helpers@5.1.0': {} - - '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': - dependencies: - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - - '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': - dependencies: - '@csstools/color-helpers': 5.1.0 - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - - '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': - dependencies: - '@csstools/css-tokenizer': 3.0.4 - - '@csstools/css-tokenizer@3.0.4': {} - - '@esbuild/aix-ppc64@0.25.10': - optional: true - - '@esbuild/android-arm64@0.25.10': - optional: true - - '@esbuild/android-arm@0.25.10': - optional: true - - '@esbuild/android-x64@0.25.10': - optional: true - - '@esbuild/darwin-arm64@0.25.10': - optional: true - - '@esbuild/darwin-x64@0.25.10': - optional: true - - '@esbuild/freebsd-arm64@0.25.10': - optional: true - - '@esbuild/freebsd-x64@0.25.10': - optional: true - - '@esbuild/linux-arm64@0.25.10': - optional: true - - '@esbuild/linux-arm@0.25.10': - optional: true - - '@esbuild/linux-ia32@0.25.10': - optional: true - - '@esbuild/linux-loong64@0.25.10': - optional: true - - '@esbuild/linux-mips64el@0.25.10': - optional: true - - '@esbuild/linux-ppc64@0.25.10': - optional: true - - '@esbuild/linux-riscv64@0.25.10': - optional: true - - '@esbuild/linux-s390x@0.25.10': - optional: true - - '@esbuild/linux-x64@0.25.10': - optional: true - - '@esbuild/netbsd-arm64@0.25.10': - optional: true - - '@esbuild/netbsd-x64@0.25.10': - optional: true - - '@esbuild/openbsd-arm64@0.25.10': - optional: true - - '@esbuild/openbsd-x64@0.25.10': - optional: true - - '@esbuild/openharmony-arm64@0.25.10': - optional: true - - '@esbuild/sunos-x64@0.25.10': - optional: true - - '@esbuild/win32-arm64@0.25.10': - optional: true - - '@esbuild/win32-ia32@0.25.10': - optional: true - - '@esbuild/win32-x64@0.25.10': - optional: true - - '@isaacs/fs-minipass@4.0.1': - dependencies: - minipass: 7.1.2 - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.1 - - '@oozcitak/dom@1.15.10': - dependencies: - '@oozcitak/infra': 1.0.8 - '@oozcitak/url': 1.0.4 - '@oozcitak/util': 8.3.8 - - '@oozcitak/infra@1.0.8': - dependencies: - '@oozcitak/util': 8.3.8 - - '@oozcitak/url@1.0.4': - dependencies: - '@oozcitak/infra': 1.0.8 - '@oozcitak/util': 8.3.8 - - '@oozcitak/util@8.3.8': {} - - '@rolldown/pluginutils@1.0.0-beta.27': {} - - '@rolldown/pluginutils@1.0.0-beta.40': {} - - '@rollup/rollup-android-arm-eabi@4.52.2': - optional: true - - '@rollup/rollup-android-arm64@4.52.2': - optional: true - - '@rollup/rollup-darwin-arm64@4.52.2': - optional: true - - '@rollup/rollup-darwin-x64@4.52.2': - optional: true - - '@rollup/rollup-freebsd-arm64@4.52.2': - optional: true - - '@rollup/rollup-freebsd-x64@4.52.2': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.52.2': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.52.2': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.52.2': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.52.2': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.52.2': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.52.2': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.52.2': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.52.2': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.52.2': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.52.2': - optional: true - - '@rollup/rollup-linux-x64-musl@4.52.2': - optional: true - - '@rollup/rollup-openharmony-arm64@4.52.2': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.52.2': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.52.2': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.52.2': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.52.2': - optional: true - - '@solid-primitives/event-listener@2.4.3(solid-js@1.9.9)': - dependencies: - '@solid-primitives/utils': 6.3.2(solid-js@1.9.9) - solid-js: 1.9.9 - - '@solid-primitives/keyboard@1.3.3(solid-js@1.9.9)': - dependencies: - '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.9) - '@solid-primitives/rootless': 1.5.2(solid-js@1.9.9) - '@solid-primitives/utils': 6.3.2(solid-js@1.9.9) - solid-js: 1.9.9 - - '@solid-primitives/rootless@1.5.2(solid-js@1.9.9)': - dependencies: - '@solid-primitives/utils': 6.3.2(solid-js@1.9.9) - solid-js: 1.9.9 - - '@solid-primitives/utils@6.3.2(solid-js@1.9.9)': - dependencies: - solid-js: 1.9.9 - - '@tailwindcss/node@4.1.13': - dependencies: - '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.18.3 - jiti: 2.6.0 - lightningcss: 1.30.1 - magic-string: 0.30.19 - source-map-js: 1.2.1 - tailwindcss: 4.1.13 - - '@tailwindcss/oxide-android-arm64@4.1.13': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.1.13': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.1.13': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.1.13': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.1.13': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.1.13': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.1.13': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.1.13': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.1.13': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.1.13': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.1.13': - optional: true - - '@tailwindcss/oxide@4.1.13': - dependencies: - detect-libc: 2.1.1 - tar: 7.5.1 - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.13 - '@tailwindcss/oxide-darwin-arm64': 4.1.13 - '@tailwindcss/oxide-darwin-x64': 4.1.13 - '@tailwindcss/oxide-freebsd-x64': 4.1.13 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.13 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.13 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.13 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.13 - '@tailwindcss/oxide-linux-x64-musl': 4.1.13 - '@tailwindcss/oxide-wasm32-wasi': 4.1.13 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.13 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.13 - - '@tailwindcss/vite@4.1.13(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6))': - dependencies: - '@tailwindcss/node': 4.1.13 - '@tailwindcss/oxide': 4.1.13 - tailwindcss: 4.1.13 - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6) - - '@tanstack/devtools-event-bus@0.2.1': - dependencies: - ws: 8.18.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - '@tanstack/devtools-ui@0.2.2(csstype@3.1.3)(solid-js@1.9.9)': - dependencies: - goober: 2.1.16(csstype@3.1.3) - solid-js: 1.9.9 - transitivePeerDependencies: - - csstype - - '@tanstack/devtools@0.3.0(csstype@3.1.3)(solid-js@1.9.9)': - dependencies: - '@solid-primitives/keyboard': 1.3.3(solid-js@1.9.9) - '@tanstack/devtools-event-bus': 0.2.1 - '@tanstack/devtools-ui': 0.2.2(csstype@3.1.3)(solid-js@1.9.9) - clsx: 2.1.1 - goober: 2.1.16(csstype@3.1.3) - solid-js: 1.9.9 - transitivePeerDependencies: - - bufferutil - - csstype - - utf-8-validate - - '@tanstack/directive-functions-plugin@1.132.11(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6))': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/core': 7.28.4 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 - '@tanstack/router-utils': 1.132.0 - babel-dead-code-elimination: 1.0.10 - tiny-invariant: 1.3.3 - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6) - transitivePeerDependencies: - - supports-color - - '@tanstack/history@1.132.0': {} - - '@tanstack/query-core@5.90.2': {} - - '@tanstack/react-devtools@0.2.2(@types/react-dom@19.1.9(@types/react@19.1.14))(@types/react@19.1.14)(csstype@3.1.3)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.9)': - dependencies: - '@tanstack/devtools': 0.3.0(csstype@3.1.3)(solid-js@1.9.9) - '@types/react': 19.1.14 - '@types/react-dom': 19.1.9(@types/react@19.1.14) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - transitivePeerDependencies: - - bufferutil - - csstype - - solid-js - - utf-8-validate - - '@tanstack/react-query@5.90.2(react@19.1.1)': - dependencies: - '@tanstack/query-core': 5.90.2 - react: 19.1.1 - - '@tanstack/react-router-devtools@1.132.11(@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.132.7)(@types/node@22.18.6)(csstype@3.1.3)(jiti@2.6.0)(lightningcss@1.30.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(solid-js@1.9.9)(tiny-invariant@1.3.3)(tsx@4.20.6)': - dependencies: - '@tanstack/react-router': 1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@tanstack/router-devtools-core': 1.132.11(@tanstack/router-core@1.132.7)(@types/node@22.18.6)(csstype@3.1.3)(jiti@2.6.0)(lightningcss@1.30.1)(solid-js@1.9.9)(tiny-invariant@1.3.3)(tsx@4.20.6) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6) - transitivePeerDependencies: - - '@tanstack/router-core' - - '@types/node' - - csstype - - jiti - - less - - lightningcss - - sass - - sass-embedded - - solid-js - - stylus - - sugarss - - terser - - tiny-invariant - - tsx - - yaml - - '@tanstack/react-router-ssr-query@1.132.7(@tanstack/query-core@5.90.2)(@tanstack/react-query@5.90.2(react@19.1.1))(@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(@tanstack/router-core@1.132.7)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@tanstack/query-core': 5.90.2 - '@tanstack/react-query': 5.90.2(react@19.1.1) - '@tanstack/react-router': 1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@tanstack/router-ssr-query-core': 1.132.7(@tanstack/query-core@5.90.2)(@tanstack/router-core@1.132.7) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - transitivePeerDependencies: - - '@tanstack/router-core' - - '@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@tanstack/history': 1.132.0 - '@tanstack/react-store': 0.7.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@tanstack/router-core': 1.132.7 - isbot: 5.1.31 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - - '@tanstack/react-start-client@1.132.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@tanstack/react-router': 1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@tanstack/router-core': 1.132.7 - '@tanstack/start-client-core': 1.132.12 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - - '@tanstack/react-start-server@1.132.13(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@tanstack/history': 1.132.0 - '@tanstack/react-router': 1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@tanstack/router-core': 1.132.7 - '@tanstack/start-client-core': 1.132.12 - '@tanstack/start-server-core': 1.132.13 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - transitivePeerDependencies: - - crossws - - '@tanstack/react-start@1.132.13(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6))': - dependencies: - '@tanstack/react-router': 1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@tanstack/react-start-client': 1.132.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@tanstack/react-start-server': 1.132.13(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@tanstack/router-utils': 1.132.0 - '@tanstack/start-client-core': 1.132.12 - '@tanstack/start-plugin-core': 1.132.13(@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)) - '@tanstack/start-server-core': 1.132.13 - pathe: 2.0.3 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6) - transitivePeerDependencies: - - '@rsbuild/core' - - crossws - - supports-color - - vite-plugin-solid - - webpack - - '@tanstack/react-store@0.7.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@tanstack/store': 0.7.7 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - use-sync-external-store: 1.5.0(react@19.1.1) - - '@tanstack/router-core@1.132.7': - dependencies: - '@tanstack/history': 1.132.0 - '@tanstack/store': 0.7.7 - cookie-es: 2.0.0 - seroval: 1.3.2 - seroval-plugins: 1.3.3(seroval@1.3.2) - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - - '@tanstack/router-devtools-core@1.132.11(@tanstack/router-core@1.132.7)(@types/node@22.18.6)(csstype@3.1.3)(jiti@2.6.0)(lightningcss@1.30.1)(solid-js@1.9.9)(tiny-invariant@1.3.3)(tsx@4.20.6)': - dependencies: - '@tanstack/router-core': 1.132.7 - clsx: 2.1.1 - goober: 2.1.16(csstype@3.1.3) - solid-js: 1.9.9 - tiny-invariant: 1.3.3 - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6) - optionalDependencies: - csstype: 3.1.3 - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml - - '@tanstack/router-generator@1.132.7': - dependencies: - '@tanstack/router-core': 1.132.7 - '@tanstack/router-utils': 1.132.0 - '@tanstack/virtual-file-routes': 1.132.0 - prettier: 3.6.2 - recast: 0.23.11 - source-map: 0.7.6 - tsx: 4.20.6 - zod: 3.25.76 - transitivePeerDependencies: - - supports-color - - '@tanstack/router-plugin@1.132.7(@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6))': - dependencies: - '@babel/core': 7.28.4 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4) - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 - '@tanstack/router-core': 1.132.7 - '@tanstack/router-generator': 1.132.7 - '@tanstack/router-utils': 1.132.0 - '@tanstack/virtual-file-routes': 1.132.0 - babel-dead-code-elimination: 1.0.10 - chokidar: 3.6.0 - unplugin: 2.3.10 - zod: 3.25.76 - optionalDependencies: - '@tanstack/react-router': 1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6) - transitivePeerDependencies: - - supports-color - - '@tanstack/router-ssr-query-core@1.132.7(@tanstack/query-core@5.90.2)(@tanstack/router-core@1.132.7)': - dependencies: - '@tanstack/query-core': 5.90.2 - '@tanstack/router-core': 1.132.7 - - '@tanstack/router-utils@1.132.0': - dependencies: - '@babel/core': 7.28.4 - '@babel/generator': 7.28.3 - '@babel/parser': 7.28.4 - '@babel/preset-typescript': 7.27.1(@babel/core@7.28.4) - ansis: 4.1.0 - diff: 8.0.2 - fast-glob: 3.3.3 - pathe: 2.0.3 - transitivePeerDependencies: - - supports-color - - '@tanstack/server-functions-plugin@1.132.11(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6))': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/core': 7.28.4 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4) - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 - '@tanstack/directive-functions-plugin': 1.132.11(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)) - babel-dead-code-elimination: 1.0.10 - tiny-invariant: 1.3.3 - transitivePeerDependencies: - - supports-color - - vite - - '@tanstack/start-client-core@1.132.12': - dependencies: - '@tanstack/router-core': 1.132.7 - '@tanstack/start-storage-context': 1.132.7 - seroval: 1.3.2 - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - - '@tanstack/start-plugin-core@1.132.13(@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6))': - dependencies: - '@babel/code-frame': 7.26.2 - '@babel/core': 7.28.4 - '@babel/types': 7.28.4 - '@rolldown/pluginutils': 1.0.0-beta.40 - '@tanstack/router-core': 1.132.7 - '@tanstack/router-generator': 1.132.7 - '@tanstack/router-plugin': 1.132.7(@tanstack/react-router@1.132.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)) - '@tanstack/router-utils': 1.132.0 - '@tanstack/server-functions-plugin': 1.132.11(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)) - '@tanstack/start-server-core': 1.132.13 - babel-dead-code-elimination: 1.0.10 - cheerio: 1.1.2 - exsolve: 1.0.7 - pathe: 2.0.3 - srvx: 0.8.7 - tinyglobby: 0.2.15 - ufo: 1.6.1 - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6) - vitefu: 1.1.1(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)) - xmlbuilder2: 3.1.1 - zod: 3.25.76 - transitivePeerDependencies: - - '@rsbuild/core' - - '@tanstack/react-router' - - crossws - - supports-color - - vite-plugin-solid - - webpack - - '@tanstack/start-server-core@1.132.13': - dependencies: - '@tanstack/history': 1.132.0 - '@tanstack/router-core': 1.132.7 - '@tanstack/start-client-core': 1.132.12 - '@tanstack/start-storage-context': 1.132.7 - h3: 2.0.0-beta.4 - seroval: 1.3.2 - tiny-invariant: 1.3.3 - transitivePeerDependencies: - - crossws - - '@tanstack/start-storage-context@1.132.7': - dependencies: - '@tanstack/router-core': 1.132.7 - - '@tanstack/store@0.7.7': {} - - '@tanstack/virtual-file-routes@1.132.0': {} - - '@testing-library/dom@10.4.1': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/runtime': 7.28.4 - '@types/aria-query': 5.0.4 - aria-query: 5.3.0 - dom-accessibility-api: 0.5.16 - lz-string: 1.5.0 - picocolors: 1.1.1 - pretty-format: 27.5.1 - - '@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.9(@types/react@19.1.14))(@types/react@19.1.14)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@babel/runtime': 7.28.4 - '@testing-library/dom': 10.4.1 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.14 - '@types/react-dom': 19.1.9(@types/react@19.1.14) - - '@types/aria-query@5.0.4': {} - - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 - '@types/babel__generator': 7.27.0 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.28.0 - - '@types/babel__generator@7.27.0': - dependencies: - '@babel/types': 7.28.4 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 - - '@types/babel__traverse@7.28.0': - dependencies: - '@babel/types': 7.28.4 - - '@types/chai@5.2.2': - dependencies: - '@types/deep-eql': 4.0.2 - - '@types/deep-eql@4.0.2': {} - - '@types/estree@1.0.8': {} - - '@types/node@22.18.6': - dependencies: - undici-types: 6.21.0 - - '@types/react-dom@19.1.9(@types/react@19.1.14)': - dependencies: - '@types/react': 19.1.14 - - '@types/react@19.1.14': - dependencies: - csstype: 3.1.3 - - '@types/ws@8.18.1': - dependencies: - '@types/node': 22.18.6 - - '@vitejs/plugin-react@4.7.0(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6))': - dependencies: - '@babel/core': 7.28.4 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4) - '@rolldown/pluginutils': 1.0.0-beta.27 - '@types/babel__core': 7.20.5 - react-refresh: 0.17.0 - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6) - transitivePeerDependencies: - - supports-color - - '@vitest/expect@3.2.4': - dependencies: - '@types/chai': 5.2.2 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - tinyrainbow: 2.0.0 - - '@vitest/mocker@3.2.4(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.19 - optionalDependencies: - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6) - - '@vitest/pretty-format@3.2.4': - dependencies: - tinyrainbow: 2.0.0 - - '@vitest/runner@3.2.4': - dependencies: - '@vitest/utils': 3.2.4 - pathe: 2.0.3 - strip-literal: 3.0.0 - - '@vitest/snapshot@3.2.4': - dependencies: - '@vitest/pretty-format': 3.2.4 - magic-string: 0.30.19 - pathe: 2.0.3 - - '@vitest/spy@3.2.4': - dependencies: - tinyspy: 4.0.4 - - '@vitest/utils@3.2.4': - dependencies: - '@vitest/pretty-format': 3.2.4 - loupe: 3.2.1 - tinyrainbow: 2.0.0 - - acorn@8.15.0: {} - - agent-base@7.1.4: {} - - ansi-regex@5.0.1: {} - - ansi-styles@5.2.0: {} - - ansis@4.1.0: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 - - aria-query@5.3.0: - dependencies: - dequal: 2.0.3 - - assertion-error@2.0.1: {} - - ast-types@0.16.1: - dependencies: - tslib: 2.8.1 - - babel-dead-code-elimination@1.0.10: - dependencies: - '@babel/core': 7.28.4 - '@babel/parser': 7.28.4 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 - transitivePeerDependencies: - - supports-color - - baseline-browser-mapping@2.8.7: {} - - binary-extensions@2.3.0: {} - - boolbase@1.0.0: {} - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - browserslist@4.26.2: - dependencies: - baseline-browser-mapping: 2.8.7 - caniuse-lite: 1.0.30001745 - electron-to-chromium: 1.5.224 - node-releases: 2.0.21 - update-browserslist-db: 1.1.3(browserslist@4.26.2) - - cac@6.7.14: {} - - caniuse-lite@1.0.30001745: {} - - capnweb@0.1.0: {} - - chai@5.3.3: - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.1 - deep-eql: 5.0.2 - loupe: 3.2.1 - pathval: 2.0.1 - - check-error@2.1.1: {} - - cheerio-select@2.1.0: - dependencies: - boolbase: 1.0.0 - css-select: 5.2.2 - css-what: 6.2.2 - domelementtype: 2.3.0 - domhandler: 5.0.3 - domutils: 3.2.2 - - cheerio@1.1.2: - dependencies: - cheerio-select: 2.1.0 - dom-serializer: 2.0.0 - domhandler: 5.0.3 - domutils: 3.2.2 - encoding-sniffer: 0.2.1 - htmlparser2: 10.0.0 - parse5: 7.3.0 - parse5-htmlparser2-tree-adapter: 7.1.0 - parse5-parser-stream: 7.1.2 - undici: 7.16.0 - whatwg-mimetype: 4.0.0 - - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - chownr@3.0.0: {} - - clsx@2.1.1: {} - - convert-source-map@2.0.0: {} - - cookie-es@2.0.0: {} - - css-select@5.2.2: - dependencies: - boolbase: 1.0.0 - css-what: 6.2.2 - domhandler: 5.0.3 - domutils: 3.2.2 - nth-check: 2.1.1 - - css-what@6.2.2: {} - - cssstyle@4.6.0: - dependencies: - '@asamuzakjp/css-color': 3.2.0 - rrweb-cssom: 0.8.0 - - csstype@3.1.3: {} - - data-urls@5.0.0: - dependencies: - whatwg-mimetype: 4.0.0 - whatwg-url: 14.2.0 - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - decimal.js@10.6.0: {} - - deep-eql@5.0.2: {} - - dequal@2.0.3: {} - - detect-libc@2.1.1: {} - - diff@8.0.2: {} - - dom-accessibility-api@0.5.16: {} - - dom-serializer@2.0.0: - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - entities: 4.5.0 - - domelementtype@2.3.0: {} - - domhandler@5.0.3: - dependencies: - domelementtype: 2.3.0 - - domutils@3.2.2: - dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 - - electron-to-chromium@1.5.224: {} - - encoding-sniffer@0.2.1: - dependencies: - iconv-lite: 0.6.3 - whatwg-encoding: 3.1.1 - - enhanced-resolve@5.18.3: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.2.3 - - entities@4.5.0: {} - - entities@6.0.1: {} - - es-module-lexer@1.7.0: {} - - esbuild@0.25.10: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.10 - '@esbuild/android-arm': 0.25.10 - '@esbuild/android-arm64': 0.25.10 - '@esbuild/android-x64': 0.25.10 - '@esbuild/darwin-arm64': 0.25.10 - '@esbuild/darwin-x64': 0.25.10 - '@esbuild/freebsd-arm64': 0.25.10 - '@esbuild/freebsd-x64': 0.25.10 - '@esbuild/linux-arm': 0.25.10 - '@esbuild/linux-arm64': 0.25.10 - '@esbuild/linux-ia32': 0.25.10 - '@esbuild/linux-loong64': 0.25.10 - '@esbuild/linux-mips64el': 0.25.10 - '@esbuild/linux-ppc64': 0.25.10 - '@esbuild/linux-riscv64': 0.25.10 - '@esbuild/linux-s390x': 0.25.10 - '@esbuild/linux-x64': 0.25.10 - '@esbuild/netbsd-arm64': 0.25.10 - '@esbuild/netbsd-x64': 0.25.10 - '@esbuild/openbsd-arm64': 0.25.10 - '@esbuild/openbsd-x64': 0.25.10 - '@esbuild/openharmony-arm64': 0.25.10 - '@esbuild/sunos-x64': 0.25.10 - '@esbuild/win32-arm64': 0.25.10 - '@esbuild/win32-ia32': 0.25.10 - '@esbuild/win32-x64': 0.25.10 - - escalade@3.2.0: {} - - esprima@4.0.1: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.8 - - expect-type@1.2.2: {} - - exsolve@1.0.7: {} - - fast-glob@3.3.3: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - - fastq@1.19.1: - dependencies: - reusify: 1.1.0 - - fdir@6.5.0(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 - - fetchdts@0.1.7: {} - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - fsevents@2.3.3: - optional: true - - gensync@1.0.0-beta.2: {} - - get-tsconfig@4.10.1: - dependencies: - resolve-pkg-maps: 1.0.0 - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - globrex@0.1.2: {} - - goober@2.1.16(csstype@3.1.3): - dependencies: - csstype: 3.1.3 - - graceful-fs@4.2.11: {} - - h3@2.0.0-beta.4: - dependencies: - cookie-es: 2.0.0 - fetchdts: 0.1.7 - rou3: 0.7.5 - srvx: 0.8.7 - - html-encoding-sniffer@4.0.0: - dependencies: - whatwg-encoding: 3.1.1 - - htmlparser2@10.0.0: - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - domutils: 3.2.2 - entities: 6.0.1 - - http-proxy-agent@7.0.2: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - iconv-lite@0.6.3: - dependencies: - safer-buffer: 2.1.2 - - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-extglob@2.1.1: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-number@7.0.0: {} - - is-potential-custom-element-name@1.0.1: {} - - isbot@5.1.31: {} - - jiti@2.6.0: {} - - js-tokens@4.0.0: {} - - js-tokens@9.0.1: {} - - js-yaml@3.14.1: - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - - jsdom@26.1.0: - dependencies: - cssstyle: 4.6.0 - data-urls: 5.0.0 - decimal.js: 10.6.0 - html-encoding-sniffer: 4.0.0 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.22 - parse5: 7.3.0 - rrweb-cssom: 0.8.0 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 5.1.2 - w3c-xmlserializer: 5.0.0 - webidl-conversions: 7.0.0 - whatwg-encoding: 3.1.1 - whatwg-mimetype: 4.0.0 - whatwg-url: 14.2.0 - ws: 8.18.3 - xml-name-validator: 5.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - jsesc@3.1.0: {} - - json5@2.2.3: {} - - lightningcss-darwin-arm64@1.30.1: - optional: true - - lightningcss-darwin-x64@1.30.1: - optional: true - - lightningcss-freebsd-x64@1.30.1: - optional: true - - lightningcss-linux-arm-gnueabihf@1.30.1: - optional: true - - lightningcss-linux-arm64-gnu@1.30.1: - optional: true - - lightningcss-linux-arm64-musl@1.30.1: - optional: true - - lightningcss-linux-x64-gnu@1.30.1: - optional: true - - lightningcss-linux-x64-musl@1.30.1: - optional: true - - lightningcss-win32-arm64-msvc@1.30.1: - optional: true - - lightningcss-win32-x64-msvc@1.30.1: - optional: true - - lightningcss@1.30.1: - dependencies: - detect-libc: 2.1.1 - optionalDependencies: - lightningcss-darwin-arm64: 1.30.1 - lightningcss-darwin-x64: 1.30.1 - lightningcss-freebsd-x64: 1.30.1 - lightningcss-linux-arm-gnueabihf: 1.30.1 - lightningcss-linux-arm64-gnu: 1.30.1 - lightningcss-linux-arm64-musl: 1.30.1 - lightningcss-linux-x64-gnu: 1.30.1 - lightningcss-linux-x64-musl: 1.30.1 - lightningcss-win32-arm64-msvc: 1.30.1 - lightningcss-win32-x64-msvc: 1.30.1 - - loupe@3.2.1: {} - - lru-cache@10.4.3: {} - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - lz-string@1.5.0: {} - - magic-string@0.30.19: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - merge2@1.4.1: {} - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - - minipass@7.1.2: {} - - minizlib@3.1.0: - dependencies: - minipass: 7.1.2 - - ms@2.1.3: {} - - nanoid@3.3.11: {} - - node-releases@2.0.21: {} - - normalize-path@3.0.0: {} - - nth-check@2.1.1: - dependencies: - boolbase: 1.0.0 - - nwsapi@2.2.22: {} - - parse5-htmlparser2-tree-adapter@7.1.0: - dependencies: - domhandler: 5.0.3 - parse5: 7.3.0 - - parse5-parser-stream@7.1.2: - dependencies: - parse5: 7.3.0 - - parse5@7.3.0: - dependencies: - entities: 6.0.1 - - pathe@2.0.3: {} - - pathval@2.0.1: {} - - picocolors@1.1.1: {} - - picomatch@2.3.1: {} - - picomatch@4.0.3: {} - - postcss@8.5.6: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - prettier@3.6.2: {} - - pretty-format@27.5.1: - dependencies: - ansi-regex: 5.0.1 - ansi-styles: 5.2.0 - react-is: 17.0.2 - - punycode@2.3.1: {} - - queue-microtask@1.2.3: {} - - react-dom@19.1.1(react@19.1.1): - dependencies: - react: 19.1.1 - scheduler: 0.26.0 - - react-is@17.0.2: {} - - react-refresh@0.17.0: {} - - react@19.1.1: {} - - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - - recast@0.23.11: - dependencies: - ast-types: 0.16.1 - esprima: 4.0.1 - source-map: 0.6.1 - tiny-invariant: 1.3.3 - tslib: 2.8.1 - - resolve-pkg-maps@1.0.0: {} - - reusify@1.1.0: {} - - rollup@4.52.2: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.52.2 - '@rollup/rollup-android-arm64': 4.52.2 - '@rollup/rollup-darwin-arm64': 4.52.2 - '@rollup/rollup-darwin-x64': 4.52.2 - '@rollup/rollup-freebsd-arm64': 4.52.2 - '@rollup/rollup-freebsd-x64': 4.52.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.52.2 - '@rollup/rollup-linux-arm-musleabihf': 4.52.2 - '@rollup/rollup-linux-arm64-gnu': 4.52.2 - '@rollup/rollup-linux-arm64-musl': 4.52.2 - '@rollup/rollup-linux-loong64-gnu': 4.52.2 - '@rollup/rollup-linux-ppc64-gnu': 4.52.2 - '@rollup/rollup-linux-riscv64-gnu': 4.52.2 - '@rollup/rollup-linux-riscv64-musl': 4.52.2 - '@rollup/rollup-linux-s390x-gnu': 4.52.2 - '@rollup/rollup-linux-x64-gnu': 4.52.2 - '@rollup/rollup-linux-x64-musl': 4.52.2 - '@rollup/rollup-openharmony-arm64': 4.52.2 - '@rollup/rollup-win32-arm64-msvc': 4.52.2 - '@rollup/rollup-win32-ia32-msvc': 4.52.2 - '@rollup/rollup-win32-x64-gnu': 4.52.2 - '@rollup/rollup-win32-x64-msvc': 4.52.2 - fsevents: 2.3.3 - - rou3@0.7.5: {} - - rrweb-cssom@0.8.0: {} - - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - - safer-buffer@2.1.2: {} - - saxes@6.0.0: - dependencies: - xmlchars: 2.2.0 - - scheduler@0.26.0: {} - - semver@6.3.1: {} - - seroval-plugins@1.3.3(seroval@1.3.2): - dependencies: - seroval: 1.3.2 - - seroval@1.3.2: {} - - siginfo@2.0.0: {} - - solid-js@1.9.9: - dependencies: - csstype: 3.1.3 - seroval: 1.3.2 - seroval-plugins: 1.3.3(seroval@1.3.2) - - source-map-js@1.2.1: {} - - source-map@0.6.1: {} - - source-map@0.7.6: {} - - sprintf-js@1.0.3: {} - - srvx@0.8.7: - dependencies: - cookie-es: 2.0.0 - - stackback@0.0.2: {} - - std-env@3.9.0: {} - - strip-literal@3.0.0: - dependencies: - js-tokens: 9.0.1 - - symbol-tree@3.2.4: {} - - tailwindcss@4.1.13: {} - - tapable@2.2.3: {} - - tar@7.5.1: - dependencies: - '@isaacs/fs-minipass': 4.0.1 - chownr: 3.0.0 - minipass: 7.1.2 - minizlib: 3.1.0 - yallist: 5.0.0 - - tiny-invariant@1.3.3: {} - - tiny-warning@1.0.3: {} - - tinybench@2.9.0: {} - - tinyexec@0.3.2: {} - - tinyglobby@0.2.15: - dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - - tinypool@1.1.1: {} - - tinyrainbow@2.0.0: {} - - tinyspy@4.0.4: {} - - tldts-core@6.1.86: {} - - tldts@6.1.86: - dependencies: - tldts-core: 6.1.86 - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - tough-cookie@5.1.2: - dependencies: - tldts: 6.1.86 - - tr46@5.1.1: - dependencies: - punycode: 2.3.1 - - tsconfck@3.1.6(typescript@5.9.2): - optionalDependencies: - typescript: 5.9.2 - - tslib@2.8.1: {} - - tsx@4.20.6: - dependencies: - esbuild: 0.25.10 - get-tsconfig: 4.10.1 - optionalDependencies: - fsevents: 2.3.3 - - typescript@5.9.2: {} - - ufo@1.6.1: {} - - undici-types@6.21.0: {} - - undici@7.16.0: {} - - unplugin@2.3.10: - dependencies: - '@jridgewell/remapping': 2.3.5 - acorn: 8.15.0 - picomatch: 4.0.3 - webpack-virtual-modules: 0.6.2 - - update-browserslist-db@1.1.3(browserslist@4.26.2): - dependencies: - browserslist: 4.26.2 - escalade: 3.2.0 - picocolors: 1.1.1 - - use-sync-external-store@1.5.0(react@19.1.1): - dependencies: - react: 19.1.1 - - vite-node@3.2.4(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)): - dependencies: - debug: 4.4.3 - globrex: 0.1.2 - tsconfck: 3.1.6(typescript@5.9.2) - optionalDependencies: - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6) - transitivePeerDependencies: - - supports-color - - typescript - - vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6): - dependencies: - esbuild: 0.25.10 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.52.2 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 22.18.6 - fsevents: 2.3.3 - jiti: 2.6.0 - lightningcss: 1.30.1 - tsx: 4.20.6 - - vitefu@1.1.1(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)): - optionalDependencies: - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6) - - vitest@3.2.4(@types/node@22.18.6)(jiti@2.6.0)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.6): - dependencies: - '@types/chai': 5.2.2 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - debug: 4.4.3 - expect-type: 1.2.2 - magic-string: 0.30.19 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.9.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.15 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 7.1.7(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6) - vite-node: 3.2.4(@types/node@22.18.6)(jiti@2.6.0)(lightningcss@1.30.1)(tsx@4.20.6) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 22.18.6 - jsdom: 26.1.0 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - w3c-xmlserializer@5.0.0: - dependencies: - xml-name-validator: 5.0.0 - - web-vitals@4.2.4: {} - - webidl-conversions@7.0.0: {} - - webpack-virtual-modules@0.6.2: {} - - whatwg-encoding@3.1.1: - dependencies: - iconv-lite: 0.6.3 - - whatwg-mimetype@4.0.0: {} - - whatwg-url@14.2.0: - dependencies: - tr46: 5.1.1 - webidl-conversions: 7.0.0 - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - ws@8.18.3: {} - - xml-name-validator@5.0.0: {} - - xmlbuilder2@3.1.1: - dependencies: - '@oozcitak/dom': 1.15.10 - '@oozcitak/infra': 1.0.8 - '@oozcitak/util': 8.3.8 - js-yaml: 3.14.1 - - xmlchars@2.2.0: {} - - yallist@3.1.1: {} - - yallist@5.0.0: {} - - zod@3.25.76: {} diff --git a/examples/ts-group-chat/src/components/ChatInterface.tsx b/examples/ts-group-chat/src/components/ChatInterface.tsx index a250bd2c6..c9f369555 100644 --- a/examples/ts-group-chat/src/components/ChatInterface.tsx +++ b/examples/ts-group-chat/src/components/ChatInterface.tsx @@ -8,6 +8,7 @@ interface ChatInterfaceProps { message: string, ) => Promise<{ success: boolean; error?: string }> username: string | null + isJoined?: boolean claudeQueueStatus?: ClaudeQueueStatus } @@ -15,26 +16,31 @@ export function ChatInterface({ messages, onSendMessage, username, + isJoined = false, claudeQueueStatus, }: ChatInterfaceProps) { const [messageText, setMessageText] = useState('') const [isSending, setIsSending] = useState(false) + const [sendError, setSendError] = useState(null) const handleSendMessage = async (e: React.FormEvent) => { e.preventDefault() - if (!messageText.trim() || !username || isSending) return + if (!messageText.trim() || !username || isSending || !isJoined) return setIsSending(true) + setSendError(null) try { const result = await onSendMessage(messageText) if (result.success) { setMessageText('') } else { - console.error('Failed to send message:', result.error) + setSendError(result.error || 'Failed to send message') } } catch (error) { - console.error('Error sending message:', error) + setSendError( + error instanceof Error ? error.message : 'Failed to send message', + ) } finally { setIsSending(false) } @@ -184,22 +190,30 @@ export function ChatInterface({ {/* Message Input */} {username ? ( -
+ + {!isJoined && ( +

Joining chat room…

+ )} + {sendError && ( +

{sendError}

+ )} +
setMessageText(e.target.value)} placeholder="Type your message..." className="flex-1 bg-gray-700 text-white border border-gray-600 rounded px-3 py-2 focus:outline-none focus:border-blue-500" - disabled={isSending} + disabled={isSending || !isJoined} /> +
) : (
diff --git a/examples/ts-group-chat/src/components/UsernameInput.tsx b/examples/ts-group-chat/src/components/UsernameInput.tsx deleted file mode 100644 index 3337948c6..000000000 --- a/examples/ts-group-chat/src/components/UsernameInput.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { useState } from 'react' - -interface UsernameInputProps { - username: string - onUsernameChange: (username: string) => void - isConnected: boolean -} - -export function UsernameInput({ - username, - onUsernameChange, - isConnected, -}: UsernameInputProps) { - const [tempUsername, setTempUsername] = useState(username) - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault() - if (tempUsername.trim()) { - onUsernameChange(tempUsername.trim()) - } - } - - const handleUsernameChange = (value: string) => { - setTempUsername(value) - // Only update username when user finishes typing (on blur or enter) - } - - if (!isConnected) { - return ( -
-
-
- Connecting to chat server... -
-
- ) - } - - return ( -
-
- - handleUsernameChange(e.target.value)} - onBlur={(e) => { - const value = e.target.value.trim() - if (value) { - onUsernameChange(value) - } else { - onUsernameChange('') - } - }} - placeholder="Enter your username" - className="flex-1 bg-gray-700 text-white border border-gray-600 rounded px-3 py-2 focus:outline-none focus:border-blue-500" - maxLength={20} - /> - {username &&
βœ“
} -
- {!username && ( -

- πŸ’‘ Choose a username to start chatting -

- )} -
- ) -} diff --git a/examples/ts-group-chat/src/hooks/useChatConnection.ts b/examples/ts-group-chat/src/hooks/useChatConnection.ts index 8a2477cf3..ae2e8788f 100644 --- a/examples/ts-group-chat/src/hooks/useChatConnection.ts +++ b/examples/ts-group-chat/src/hooks/useChatConnection.ts @@ -1,18 +1,8 @@ -import { useState, useRef, useCallback } from 'react' +import { useState, useRef, useCallback, useEffect } from 'react' import { newWebSocketRpcSession } from 'capnweb' - -export interface ChatAPI { - joinChat(username: string, notificationCallback: Function): Promise - sendMessage(message: string): Promise - getChatState(): Promise - pollMessages(): Promise - leaveChat(): Promise - getClaudeQueueStatus(): Promise<{ - current: string | null - queue: string[] - isProcessing: boolean - }> -} +import type { RpcStub } from 'capnweb' +import type { ChatApi } from '../../chat-server/chat-api' +import { ChatNotifier } from '@/lib/chat-notifier' export interface ConnectionState { isConnected: boolean @@ -29,9 +19,38 @@ export function useChatConnection() { error: null, }) - const apiRef = useRef(null) + const [api, setApi] = useState | null>(null) + const [notifier, setNotifier] = useState(null) + const apiRef = useRef | null>(null) + const connectingRef = useRef(false) + const connectionIdRef = useRef(0) + + const disconnect = useCallback(() => { + connectionIdRef.current += 1 + connectingRef.current = false + if (apiRef.current) { + apiRef.current[Symbol.dispose]() + apiRef.current = null + } + setApi(null) + setNotifier(null) + setState({ + isConnected: false, + isConnecting: false, + connectionStatus: 'Disconnected', + error: null, + }) + }, []) const connect = useCallback(() => { + if (connectingRef.current || apiRef.current) { + return + } + + connectingRef.current = true + const connectionId = ++connectionIdRef.current + const sessionNotifier = new ChatNotifier() + setState((prev) => ({ ...prev, isConnecting: true, @@ -51,31 +70,51 @@ export function useChatConnection() { console.log('Connecting to chat:', wsUrl) - const api = newWebSocketRpcSession(wsUrl) as any as ChatAPI - apiRef.current = api + const stub = newWebSocketRpcSession(wsUrl, sessionNotifier) + apiRef.current = stub + + void (async () => { + try { + const chatState = await stub.getChatState() + if (connectionId !== connectionIdRef.current) { + stub[Symbol.dispose]() + return + } + if (!chatState || !Array.isArray(chatState.onlineUsers)) { + throw new Error('Invalid chat state from server') + } - // Test connection - api - .getChatState() - .then(() => { console.log('Chat RPC connection established') + setNotifier(sessionNotifier) + setApi(stub) + connectingRef.current = false setState({ isConnected: true, isConnecting: false, connectionStatus: 'Connected', error: null, }) - }) - .catch((error) => { + } catch (error) { + if (connectionId !== connectionIdRef.current) { + stub[Symbol.dispose]() + return + } console.error('Chat connection failed:', error) + stub[Symbol.dispose]() + apiRef.current = null + setApi(null) + setNotifier(null) + connectingRef.current = false setState({ isConnected: false, isConnecting: false, connectionStatus: 'Failed to connect', - error: error.message, + error: error instanceof Error ? error.message : 'Unknown error', }) - }) + } + })() } catch (error) { + connectingRef.current = false console.error('Failed to create chat session:', error) setState({ isConnected: false, @@ -86,21 +125,17 @@ export function useChatConnection() { } }, []) - const disconnect = useCallback(() => { - if (apiRef.current) { - apiRef.current = null + useEffect(() => { + return () => { + disconnect() } - setState({ - isConnected: false, - isConnecting: false, - connectionStatus: 'Disconnected', - error: null, - }) - }, []) + }, [disconnect]) return { ...state, - api: apiRef.current, + api, + apiRef, + notifier, connect, disconnect, } diff --git a/examples/ts-group-chat/src/hooks/useChatMessages.ts b/examples/ts-group-chat/src/hooks/useChatMessages.ts index 8129d6b48..6d8562407 100644 --- a/examples/ts-group-chat/src/hooks/useChatMessages.ts +++ b/examples/ts-group-chat/src/hooks/useChatMessages.ts @@ -1,182 +1,168 @@ -import { useState, useEffect, useCallback } from 'react' -import type { ChatAPI } from './useChatConnection' - -export interface ChatMessage { - id: string - username: string - message: string - timestamp: string - type?: 'message' | 'user_joined' | 'user_left' | 'welcome' +import { useState, useEffect, useCallback, type RefObject } from 'react' +import type { RpcStub } from 'capnweb' +import type { + ChatApi, + ChatMessage, + ChatNotification, + ChatState, +} from '../../chat-server/chat-api' +import type { ChatNotifier } from '@/lib/chat-notifier' + +export type { ChatMessage, ChatState } + +function notificationToMessage(notification: ChatNotification): ChatMessage { + return { + id: notification.id || Math.random().toString(36).slice(2, 11), + username: notification.username || 'System', + message: notification.message, + timestamp: notification.timestamp || new Date().toISOString(), + type: notification.type, + } } -export interface ChatState { - onlineUsers: string[] - messages: ChatMessage[] +function formatError(error: unknown) { + if (error instanceof Error) return error.message + if (typeof error === 'object' && error !== null && 'message' in error) { + return String((error as { message: unknown }).message) + } + return String(error) } export function useChatMessages( - api: ChatAPI | null, + api: RpcStub | null, + apiRef: RefObject | null>, + notifier: ChatNotifier | null, isConnected: boolean, username: string | null, ) { + const getApi = useCallback( + () => apiRef.current ?? api, + [api, apiRef], + ) const [chatState, setChatState] = useState({ onlineUsers: [], messages: [], }) - const [isPolling, setIsPolling] = useState(false) const [isJoined, setIsJoined] = useState(false) - // Poll for new messages - const pollMessages = useCallback(async () => { - if (!api || !isConnected || !username || isPolling) { - return - } - - try { - setIsPolling(true) - const newMessages = await api.pollMessages() - - if (newMessages.length > 0) { - setChatState((prev) => ({ - ...prev, - messages: [...prev.messages, ...newMessages].slice(-100), // Keep last 100 messages - })) - } - } catch (error) { - console.error('Error polling messages:', error) - } finally { - setIsPolling(false) - } - }, [api, isConnected, username, isPolling]) + useEffect(() => { + if (!notifier) return - // Update chat state from server - const updateChatState = useCallback(async () => { - if (!api || !isConnected) return + notifier.setHandler((notification: ChatNotification) => { + setChatState((prev) => { + const nextMessages = [ + ...prev.messages, + notificationToMessage(notification), + ] - try { - const state = await api.getChatState() - setChatState(state) - } catch (error) { - console.error('Error updating chat state:', error) - } - }, [api, isConnected]) + return { + onlineUsers: notification.onlineUsers ?? prev.onlineUsers, + messages: nextMessages.slice(-100), + } + }) + }) + }, [notifier]) - // Send a message const sendMessage = useCallback( async (messageText: string) => { - if (!api || !messageText.trim()) { + const activeApi = getApi() + if (!activeApi || !messageText.trim()) { return { success: false, error: 'Cannot send empty message' } } + if (!isJoined) { + return { success: false, error: 'Still joining the chat room…' } + } + try { - await api.sendMessage(messageText) + await activeApi.sendMessage(messageText) return { success: true } } catch (error) { - console.error('Error sending message:', error) + console.error('Error sending message:', formatError(error), error) return { success: false, - error: - error instanceof Error ? error.message : 'Failed to send message', + error: formatError(error) || 'Failed to send message', } } }, - [api], + [getApi, isJoined], ) - // Join chat const joinChat = useCallback( async (chatUsername: string) => { - if (!api) return { success: false, error: 'Not connected' } + const activeApi = getApi() + if (!activeApi) return { success: false, error: 'Not connected' } try { - const result = await api.joinChat(chatUsername, () => { - // Notification callback - not used in polling approach - }) + const result = await activeApi.joinChat(chatUsername) - // Load initial state setChatState({ - onlineUsers: result.onlineUsers || [], - messages: result.recentMessages || [], + onlineUsers: result.onlineUsers, + messages: result.recentMessages, }) return { success: true } } catch (error) { - console.error('Error joining chat:', error) + console.error('Error joining chat:', formatError(error), error) return { success: false, - error: error instanceof Error ? error.message : 'Failed to join chat', + error: formatError(error) || 'Failed to join chat', } } }, - [api], + [getApi], ) - // Leave chat const leaveChat = useCallback(async () => { - if (!api) return + const activeApi = getApi() + if (!activeApi) return try { - await api.leaveChat() + await activeApi.leaveChat() setChatState({ onlineUsers: [], messages: [] }) + setIsJoined(false) } catch (error) { - console.error('Error leaving chat:', error) + console.error('Error leaving chat:', formatError(error), error) } - }, [api]) + }, [getApi]) - // Auto-join when username is provided useEffect(() => { - if (!api || !isConnected || !username || isJoined) return + const activeApi = getApi() + if (!activeApi || !isConnected || !username) return + + let cancelled = false const autoJoin = async () => { + setIsJoined(false) try { - const result = await api.joinChat(username, () => {}) + const result = await activeApi.joinChat(username) + if (cancelled) return + setChatState({ - onlineUsers: result.onlineUsers || [], - messages: result.recentMessages || [], + onlineUsers: result.onlineUsers, + messages: result.recentMessages, }) setIsJoined(true) } catch (error) { - console.error('Error auto-joining chat:', error) - } - } - - autoJoin() - }, [api, isConnected, username, isJoined]) - - // Auto-leave when username is cleared - useEffect(() => { - if (!username && isJoined && api) { - const autoLeave = async () => { - try { - await api.leaveChat() - setIsJoined(false) - setChatState({ onlineUsers: [], messages: [] }) - } catch (error) { - console.error('Error auto-leaving chat:', error) + if (!cancelled) { + console.error('Error auto-joining chat:', formatError(error), error) } } - autoLeave() } - }, [username, isJoined, api]) - - // Set up polling interval when connected and joined - useEffect(() => { - if (!isConnected || !username || !isJoined) return - const interval = setInterval(pollMessages, 1000) // Poll every second + void autoJoin() - return () => clearInterval(interval) - }, [isConnected, username, isJoined, pollMessages]) + return () => { + cancelled = true + } + }, [api, getApi, isConnected, username]) - // Update chat state periodically to get online users useEffect(() => { - if (!isConnected || !username || !isJoined) return - - const interval = setInterval(updateChatState, 5000) // Update every 5 seconds - - return () => clearInterval(interval) - }, [isConnected, username, isJoined, updateChatState]) + if (!username && isJoined && getApi()) { + void leaveChat() + } + }, [username, isJoined, getApi, leaveChat]) return { chatState, diff --git a/examples/ts-group-chat/src/hooks/useClaude.ts b/examples/ts-group-chat/src/hooks/useClaude.ts index c790c3305..5827552db 100644 --- a/examples/ts-group-chat/src/hooks/useClaude.ts +++ b/examples/ts-group-chat/src/hooks/useClaude.ts @@ -1,40 +1,53 @@ -import { useEffect, useState } from 'react' -import type { ChatAPI } from './useChatConnection' - -export interface ClaudeQueueStatus { - current: string | null - queue: string[] - isProcessing: boolean -} - -export function useClaude(api: ChatAPI | null, isConnected: boolean) { +import { useEffect, useState, useCallback, type RefObject } from 'react' +import type { RpcStub } from 'capnweb' +import type { ChatApi, ClaudeQueueStatus } from '../../chat-server/chat-api' + +export type { ClaudeQueueStatus } + +export function useClaude( + api: RpcStub | null, + apiRef: RefObject | null>, + isConnected: boolean, + isJoined: boolean, +) { const [queueStatus, setQueueStatus] = useState({ current: null, queue: [], isProcessing: false, }) - // Poll for queue status + const getApi = useCallback( + () => apiRef.current ?? api, + [api, apiRef], + ) + useEffect(() => { - if (!api || !isConnected) return + const activeApi = getApi() + if (!activeApi || !isConnected || !isJoined) return const pollStatus = async () => { try { - const status = await api.getClaudeQueueStatus() + const status = await activeApi.getClaudeQueueStatus() setQueueStatus(status) } catch (error) { - console.error('Error polling Claude status:', error) + const message = + error instanceof Error + ? error.message + : typeof error === 'object' && + error !== null && + 'message' in error + ? String((error as { message: unknown }).message) + : 'Unknown error' + console.error('Error polling Claude status:', message, error) } } - // Poll immediately - pollStatus() + void pollStatus() - // Then poll every second const interval = setInterval(pollStatus, 1000) return () => clearInterval(interval) - }, [api, isConnected]) + }, [getApi, isConnected, isJoined]) return { queueStatus } } diff --git a/examples/ts-group-chat/src/lib/chat-notifier.ts b/examples/ts-group-chat/src/lib/chat-notifier.ts new file mode 100644 index 000000000..fa8cc5ea0 --- /dev/null +++ b/examples/ts-group-chat/src/lib/chat-notifier.ts @@ -0,0 +1,15 @@ +import { RpcTarget } from 'capnweb' +import type { ChatNotification } from '../../chat-server/chat-api' + +const handlers = new WeakMap void>() + +/** Client-side RpcTarget the server calls to push chat notifications. */ +export class ChatNotifier extends RpcTarget { + setHandler(handler: (notification: ChatNotification) => void) { + handlers.set(this, handler) + } + + notify(notification: ChatNotification) { + handlers.get(this)?.(notification) + } +} diff --git a/examples/ts-group-chat/src/routes/__root.tsx b/examples/ts-group-chat/src/routes/__root.tsx index 1e0156301..062107620 100644 --- a/examples/ts-group-chat/src/routes/__root.tsx +++ b/examples/ts-group-chat/src/routes/__root.tsx @@ -48,7 +48,7 @@ function RootDocument({ children }: { children: React.ReactNode }) { }, ]} eventBusConfig={{ - connectToServerBus: true, + connectToServerBus: false, }} /> diff --git a/examples/ts-group-chat/src/routes/index.tsx b/examples/ts-group-chat/src/routes/index.tsx index fcd1641a0..4a810940b 100644 --- a/examples/ts-group-chat/src/routes/index.tsx +++ b/examples/ts-group-chat/src/routes/index.tsx @@ -15,15 +15,18 @@ export const Route = createFileRoute('/')({ function ChatApp() { const [username, setUsername] = useState('') - const { isConnected, isConnecting, error, api, connect } = useChatConnection() + const { isConnected, isConnecting, error, api, apiRef, notifier, connect } = + useChatConnection() - const { chatState, sendMessage } = useChatMessages( + const { chatState, sendMessage, isJoined } = useChatMessages( api, + apiRef, + notifier, isConnected, username || null, ) - const { queueStatus } = useClaude(api, isConnected) + const { queueStatus } = useClaude(api, apiRef, isConnected, isJoined) useEffect(() => { if (!isConnected && !isConnecting && !error) { @@ -59,6 +62,7 @@ function ChatApp() { messages={chatState.messages} onSendMessage={sendMessage} username={username} + isJoined={isJoined} claudeQueueStatus={queueStatus} />
diff --git a/examples/ts-group-chat/vite.config.mjs b/examples/ts-group-chat/vite.config.ts similarity index 79% rename from examples/ts-group-chat/vite.config.mjs rename to examples/ts-group-chat/vite.config.ts index fa7f81e84..9ea917a86 100644 --- a/examples/ts-group-chat/vite.config.mjs +++ b/examples/ts-group-chat/vite.config.ts @@ -1,22 +1,27 @@ import { defineConfig } from 'vite' import { tanstackStart } from '@tanstack/react-start/plugin/vite' +import { nitro } from 'nitro/vite' import viteReact from '@vitejs/plugin-react' import viteTsConfigPaths from 'vite-tsconfig-paths' import tailwindcss from '@tailwindcss/vite' -import { websocketRpcPlugin } from './chat-server/vite-plugin.js' import { devtools } from '@tanstack/devtools-vite' +import { websocketRpcPlugin } from './chat-server/vite-plugin.js' -const config = defineConfig({ +export default defineConfig({ plugins: [ + websocketRpcPlugin(), devtools(), viteTsConfigPaths({ projects: ['./tsconfig.json'], }), tailwindcss(), tanstackStart(), + nitro(), viteReact(), - websocketRpcPlugin(), ], + resolve: { + dedupe: ['capnweb'], + }, server: { port: 3000, host: true, @@ -33,18 +38,13 @@ const config = defineConfig({ target: 'es2022', }, optimizeDeps: { + exclude: ['capnweb'], esbuildOptions: { target: 'es2022', }, }, ssr: { - noExternal: [ - '@tanstack/ai', - '@tanstack/ai-anthropic', - '@tanstack/ai-client', - '@tanstack/ai-react', - ], + noExternal: ['@tanstack/ai', '@tanstack/ai-anthropic'], }, + nitro: {}, }) - -export default config diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5d92af110..2bb38abed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -516,12 +516,6 @@ importers: '@tanstack/ai-anthropic': specifier: workspace:* version: link:../../packages/ai-anthropic - '@tanstack/ai-client': - specifier: workspace:* - version: link:../../packages/ai-client - '@tanstack/ai-react': - specifier: workspace:* - version: link:../../packages/ai-react '@tanstack/react-devtools': specifier: ^0.9.10 version: 0.9.10(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.10) @@ -531,9 +525,6 @@ importers: '@tanstack/react-router-devtools': specifier: ^1.158.4 version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.159.4)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@tanstack/react-router-ssr-query': - specifier: ^1.158.4 - version: 1.159.5(@tanstack/query-core@5.90.12)(@tanstack/react-query@5.90.12(react@19.2.3))(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.159.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-start': specifier: ^1.159.0 version: 1.159.5(crossws@0.4.6(srvx@0.11.17))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.3.3(@types/node@24.10.3)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass@1.101.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(vite@7.3.3(@types/node@24.10.3)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass@1.101.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) @@ -541,8 +532,11 @@ importers: specifier: ^1.158.4 version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite-plugin-solid@2.11.10(solid-js@1.9.10)(vite@7.3.3(@types/node@24.10.3)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass@1.101.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)))(vite@7.3.3(@types/node@24.10.3)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass@1.101.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) capnweb: - specifier: ^0.1.0 - version: 0.1.0 + specifier: ^0.10.0 + version: 0.10.0 + nitro: + specifier: 3.0.1-alpha.2 + version: 3.0.1-alpha.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(aws4fetch@1.0.20)(chokidar@5.0.0)(rolldown@1.1.3)(rollup@4.60.1)(vite@7.3.3(@types/node@24.10.3)(jiti@2.6.1)(less@4.6.6)(lightningcss@1.30.2)(sass@1.101.0)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) react: specifier: ^19.2.3 version: 19.2.3 @@ -10061,8 +10055,8 @@ packages: caniuse-lite@1.0.30001760: resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} - capnweb@0.1.0: - resolution: {integrity: sha512-+pygKx1JFTZTRdd1hHgaBRg5BwULEDZq8ZoHXkYP2GXNV3lrjXLj5qzlGz+SgBCJjWUmNBtlh7JPWdr0wIbY8w==} + capnweb@0.10.0: + resolution: {integrity: sha512-lQ2nlzaifkqBeNfs8RLErn7NZ8FNf3vn1W7J3nSH73edWG+0B5nHZGR51S2S9HMZEuZcEypOO469dZRY/BluFA==} capnweb@0.8.0: resolution: {integrity: sha512-BK/TuXUiyfLSKsmjojn70yN7oYG/JJzoURZ3tckjg5Zj2KcygPm0A5jyOlswK7SYB4f0Gh9tt+RZ132b80iLfA==} @@ -24708,7 +24702,7 @@ snapshots: caniuse-lite@1.0.30001760: {} - capnweb@0.1.0: {} + capnweb@0.10.0: {} capnweb@0.8.0: {} From 46a662551b8e0805b2e365aa5e4182e40ca9d981 Mon Sep 17 00:00:00 2001 From: Jack Herrington Date: Sun, 19 Jul 2026 08:35:44 -0700 Subject: [PATCH 5/9] feat(examples): add shared todo list with Claude active/passive modes Give ts-group-chat an in-memory todo list users and Claude can manage, with passive @Claude mentions vs active chat watching, plus clean persona switching. Co-authored-by: Cursor --- examples/ts-group-chat/README.md | 22 +- .../ts-group-chat/chat-server/capnweb-rpc.ts | 192 ++++++++++++--- .../ts-group-chat/chat-server/chat-api.ts | 30 ++- .../ts-group-chat/chat-server/chat-logic.ts | 8 +- .../chat-server/claude-service.ts | 229 +++++++++++++----- examples/ts-group-chat/chat-server/index.ts | 1 + .../ts-group-chat/chat-server/todo-logic.ts | 53 ++++ .../src/components/ChatInterface.tsx | 4 +- .../src/components/OnlineUsers.tsx | 19 +- .../ts-group-chat/src/components/TodoList.tsx | 167 +++++++++++++ .../src/hooks/useChatMessages.ts | 49 ++-- examples/ts-group-chat/src/hooks/useClaude.ts | 62 ++++- examples/ts-group-chat/src/hooks/useTodos.ts | 155 ++++++++++++ .../ts-group-chat/src/lib/chat-notifier.ts | 26 +- examples/ts-group-chat/src/routes/index.tsx | 32 ++- 15 files changed, 906 insertions(+), 143 deletions(-) create mode 100644 examples/ts-group-chat/chat-server/todo-logic.ts create mode 100644 examples/ts-group-chat/src/components/TodoList.tsx create mode 100644 examples/ts-group-chat/src/hooks/useTodos.ts diff --git a/examples/ts-group-chat/README.md b/examples/ts-group-chat/README.md index a81e89047..f04c548c1 100644 --- a/examples/ts-group-chat/README.md +++ b/examples/ts-group-chat/README.md @@ -1,11 +1,13 @@ # Cap'n Web Group Chat -A real-time multi-user chat demonstrating **Cap'n Web RPC** bidirectional push with **TanStack Start**. Mention `@Claude` in a message to queue an Anthropic response (requires `ANTHROPIC_API_KEY`). +A real-time multi-user chat demonstrating **Cap'n Web RPC** bidirectional push with **TanStack Start**. Includes a shared in-memory todo list that users and Claude can manage. Mention `@Claude` in passive mode, or switch to active mode so Claude watches the chat for todo intent. ## Features - Real-time messaging via Cap'n Web serverβ†’client push (no polling) - Online presence updates when users join or leave +- Shared in-memory todo list (manual add/remove + Claude tools) +- Claude **passive / active** mode toggle for todo help - Auto-connect on page load - Optional `@Claude` AI responses via `@tanstack/ai` - TanStack Start SSR shell + file-based routing @@ -21,6 +23,8 @@ A real-time multi-user chat demonstrating **Cap'n Web RPC** bidirectional push w chat-server/ β”œβ”€β”€ chat-api.ts # Shared ChatApi interface and types β”œβ”€β”€ chat-logic.ts # In-memory chat state +β”œβ”€β”€ todo-logic.ts # In-memory shared todo list +β”œβ”€β”€ claude-service.ts # Claude queue + todo tools β”œβ”€β”€ capnweb-rpc.ts # ChatServer RpcTarget + push broadcasts └── vite-plugin.ts # Dev-server WebSocket upgrade handler @@ -28,6 +32,7 @@ src/ β”œβ”€β”€ hooks/ β”‚ β”œβ”€β”€ useChatConnection.ts # RpcStub session + Symbol.dispose β”‚ β”œβ”€β”€ useChatMessages.ts # joinChat onNotify push handler +β”‚ β”œβ”€β”€ useTodos.ts # Todo list + Claude mode sync β”‚ └── useClaude.ts # Claude queue status └── routes/index.tsx # Main chat page ``` @@ -75,12 +80,23 @@ chatServer.setClientNotifier(clientNotifier) | `pnpm serve` | Preview production build | | `pnpm test:types` | Typecheck | +## Todo List + Claude Modes + +The room shares one in-memory todo list. Anyone can add/remove items from the UI. Claude has `listTodos`, `addTodo`, and `removeTodo` tools. + +| Mode | Behavior | +|------|----------| +| **Passive** (default) | Claude only runs when a message mentions `@Claude` (or starts with `Claude`). Use that to add/remove todos or ask what's on the list. | +| **Active** | Claude reviews every chat message. If it detects todo add/remove intent or a todo question, it uses tools and replies. Unrelated messages get no reply. | + +Mode is shared for the whole room and syncs live via Cap'n Web push. + ## @Claude Integration -Set `ANTHROPIC_API_KEY` in `.env`. Messages containing `@Claude` (or starting with `Claude`) queue a response from `claude-sonnet-4-5`. Only one Claude request runs at a time; others wait in queue. +Set `ANTHROPIC_API_KEY` in `.env`. Responses use `claude-sonnet-4-5`. Only one Claude request runs at a time; others wait in queue. ## Key Technologies - **Cap'n Web 0.10** β€” bidirectional RPC, pass-by-reference callbacks, `RpcStub` + `[Symbol.dispose]()` - **TanStack Start 1.159** β€” `tanstackStart()` + Nitro 3 -- **TanStack AI** β€” Anthropic adapter for Claude mentions +- **TanStack AI** β€” Anthropic adapter + server tools for the shared todo list diff --git a/examples/ts-group-chat/chat-server/capnweb-rpc.ts b/examples/ts-group-chat/chat-server/capnweb-rpc.ts index 603446734..d86fd0cd5 100644 --- a/examples/ts-group-chat/chat-server/capnweb-rpc.ts +++ b/examples/ts-group-chat/chat-server/capnweb-rpc.ts @@ -1,13 +1,16 @@ // Cap'n Web RPC server implementation for chat import { RpcTarget } from 'capnweb' import { ChatLogic } from './chat-logic.js' +import { TodoLogic } from './todo-logic.js' import type { ChatApi, ChatNotification, ChatNotifierApi, + ClaudeMode, ClaudeQueueStatus, JoinResult, SendResult, + TodoItem, } from './chat-api.js' import type { ClaudeService } from './claude-service.js' import type { WebSocket } from 'ws' @@ -17,12 +20,14 @@ type NotifyTarget = ChatNotifierApi // Global registry of client notification targets (Cap'n Web RpcTarget stubs) export const clients = new Map() +let claudeMode: ClaudeMode = 'passive' + // Lazy-load claude service to avoid importing AI packages at module parse time let globalClaudeService: ClaudeService | null = null async function getClaudeService(): Promise { if (!globalClaudeService) { - const { globalClaudeService: service } = await import('./claude-service.js') - globalClaudeService = service + const { ClaudeService } = await import('./claude-service.js') + globalClaudeService = new ClaudeService(globalTodos) } return globalClaudeService } @@ -50,6 +55,31 @@ function normalizeNotification( } } +function isClaudeMention(messageText: string): boolean { + const trimmedMessage = messageText.trim() + return ( + /@Claude/i.test(messageText) || + /^Claude/i.test(trimmedMessage) || + /^@Claude/i.test(trimmedMessage) + ) +} + +function isNoReply(response: string): boolean { + return response.trim().toUpperCase() === 'NO_REPLY' +} + +// Global shared todo list +export const globalTodos = new TodoLogic({ + async onTodosChanged(todos) { + await ChatServer.broadcastToAll({ + type: 'todos_updated', + message: 'Todo list updated', + username: 'System', + todos, + }) + }, +}) + // Global shared chat instance export const globalChat = new ChatLogic({ async onUserJoined(username) { @@ -166,14 +196,38 @@ export class ChatServer extends RpcTarget implements ChatApi { throw new Error('Client notifier not available on this connection') } - this.currentUsername = username - clients.set(username, this.clientNotifier) + const trimmed = username.trim() + if (!trimmed) { + throw new Error('Username cannot be empty') + } + + // Same connection switching personas: leave the previous identity first + // so it is not left registered in `clients` (which double-delivers pushes). + if (this.currentUsername && this.currentUsername !== trimmed) { + console.log( + `πŸ”„ Switching identity on connection: ${this.currentUsername} β†’ ${trimmed}`, + ) + this.leaveChat() + } + + if (this.currentUsername === trimmed) { + return { + message: 'Already joined the chat', + onlineUsers: globalChat.getOnlineUsers(), + recentMessages: globalChat.getMessages().slice(-20), + todos: globalTodos.getTodos(), + claudeMode, + } + } + + this.currentUsername = trimmed + clients.set(trimmed, this.clientNotifier) - globalChat.addUserSync(username) + globalChat.addUserSync(trimmed) const welcomeMessage = normalizeNotification({ type: 'welcome', - message: `Welcome to the chat, ${username}! πŸ‘‹`, + message: `Welcome to the chat, ${trimmed}! πŸ‘‹`, username: 'System', }) @@ -183,6 +237,8 @@ export class ChatServer extends RpcTarget implements ChatApi { message: 'Successfully joined the chat', onlineUsers: globalChat.getOnlineUsers(), recentMessages: globalChat.getMessages().slice(-20), + todos: globalTodos.getTodos(), + claudeMode, } } @@ -203,7 +259,61 @@ export class ChatServer extends RpcTarget implements ChatApi { } getChatState() { - return globalChat.getChatState() + return { + ...globalChat.getChatState(), + todos: globalTodos.getTodos(), + claudeMode, + } + } + + getTodos(): TodoItem[] { + return globalTodos.getTodos() + } + + addTodo(text: string) { + if (!this.currentUsername) { + throw new Error('You must join the chat first') + } + + const todo = globalTodos.addTodo(text, this.currentUsername) + return { + todo, + todos: globalTodos.getTodos(), + } + } + + removeTodo(id: string) { + if (!this.currentUsername) { + throw new Error('You must join the chat first') + } + + const success = globalTodos.removeTodo(id) + return { + success, + todos: globalTodos.getTodos(), + } + } + + getClaudeMode(): ClaudeMode { + return claudeMode + } + + setClaudeMode(mode: ClaudeMode) { + if (mode !== 'active' && mode !== 'passive') { + throw new Error('Claude mode must be "active" or "passive"') + } + + claudeMode = mode + console.log(`πŸ€– Claude mode set to ${mode}`) + + void ChatServer.broadcastToAll({ + type: 'claude_mode_changed', + message: `Claude is now in ${mode} mode`, + username: 'System', + claudeMode: mode, + }) + + return { mode: claudeMode } } sendMessage(messageText: string): SendResult { @@ -220,37 +330,32 @@ export class ChatServer extends RpcTarget implements ChatApi { } const trimmedMessage = messageText.trim() - const isClaudeMention = - /@Claude/i.test(messageText) || - /^Claude/i.test(trimmedMessage) || - /^@Claude/i.test(trimmedMessage) - - if (isClaudeMention) { - const message = globalChat.sendMessageSync( - this.currentUsername, - messageText.trim(), - ) + const mentioned = isClaudeMention(trimmedMessage) + const shouldAskClaude = + mentioned || (claudeMode === 'active' && this.currentUsername !== 'Claude') - void this.enqueueClaudeRequest(messageText) + const message = globalChat.sendMessageSync( + this.currentUsername, + trimmedMessage, + ) + if (shouldAskClaude) { + void this.enqueueClaudeRequest(trimmedMessage, mentioned) return { - message: 'Claude request queued', + message: mentioned + ? 'Claude request queued' + : 'Message sent; Claude is watching in active mode', chatMessage: message, } } - const message = globalChat.sendMessageSync( - this.currentUsername, - messageText.trim(), - ) - return { message: 'Message sent successfully', chatMessage: message, } } - private async enqueueClaudeRequest(messageText: string) { + private async enqueueClaudeRequest(messageText: string, mentioned: boolean) { const conversationHistory = globalChat.getMessages().map((msg) => ({ role: 'user' as const, content: `${msg.username}: ${msg.message}`, @@ -262,6 +367,8 @@ export class ChatServer extends RpcTarget implements ChatApi { username: this.currentUsername!, message: messageText, conversationHistory, + mode: claudeMode, + mentioned, }) void this.processClaudeQueue() @@ -276,15 +383,21 @@ export class ChatServer extends RpcTarget implements ChatApi { } claudeService.startProcessing() + const currentRequest = claudeService.getCurrentRequest() + const requestMode = currentRequest?.mode ?? claudeMode + const mentioned = currentRequest?.mentioned ?? false + const showResponding = + claudeService.getQueueStatus().showResponding try { - const currentStatus = claudeService.getQueueStatus() - - await ChatServer.broadcastToAll({ - type: 'claude_responding', - message: `Claude is responding to ${currentStatus.current}...`, - username: 'System', - }) + // Only announce when a visible reply is expected (not active silent watches). + if (showResponding) { + await ChatServer.broadcastToAll({ + type: 'claude_responding', + message: 'Claude is responding', + username: currentRequest?.username ?? 'System', + }) + } const conversationHistory = globalChat.getMessages().map((msg) => ({ role: 'user' as const, @@ -294,13 +407,19 @@ export class ChatServer extends RpcTarget implements ChatApi { let accumulatedResponse = '' for await (const chunk of claudeService.streamResponse( conversationHistory, + requestMode, + mentioned, )) { if (chunk.type === 'TEXT_MESSAGE_CONTENT' && chunk.delta) { accumulatedResponse += chunk.delta } } - await globalChat.sendMessage('Claude', accumulatedResponse) + if (!isNoReply(accumulatedResponse) && accumulatedResponse.trim()) { + await globalChat.sendMessage('Claude', accumulatedResponse.trim()) + } else { + console.log('πŸ€– Claude: Skipping empty / NO_REPLY response') + } } catch (error) { console.error('Error in processClaudeQueue:', error) @@ -311,6 +430,12 @@ export class ChatServer extends RpcTarget implements ChatApi { }) } finally { claudeService.finishProcessing() + // Clear any "responding" UI state (not shown as a chat line). + await ChatServer.broadcastToAll({ + type: 'claude_idle', + message: 'Claude finished', + username: 'System', + }) void this.processClaudeQueue() } } @@ -321,6 +446,7 @@ export class ChatServer extends RpcTarget implements ChatApi { current: null, queue: [], isProcessing: false, + showResponding: false, } } diff --git a/examples/ts-group-chat/chat-server/chat-api.ts b/examples/ts-group-chat/chat-server/chat-api.ts index 67968a69a..05f2aa13f 100644 --- a/examples/ts-group-chat/chat-server/chat-api.ts +++ b/examples/ts-group-chat/chat-server/chat-api.ts @@ -4,7 +4,19 @@ export type ChatNotificationType = | 'user_left' | 'welcome' | 'claude_responding' + | 'claude_idle' | 'claude_error' + | 'todos_updated' + | 'claude_mode_changed' + +export type ClaudeMode = 'active' | 'passive' + +export interface TodoItem { + id: string + text: string + createdAt: string + createdBy: string +} export interface ChatNotification { type: ChatNotificationType @@ -13,6 +25,8 @@ export interface ChatNotification { timestamp?: string id?: string onlineUsers?: string[] + todos?: TodoItem[] + claudeMode?: ClaudeMode } export interface ChatMessage { @@ -23,15 +37,22 @@ export interface ChatMessage { type?: ChatNotificationType } -export interface ChatState { +export interface ChatRoomState { onlineUsers: string[] messages: ChatMessage[] } +export interface ChatState extends ChatRoomState { + todos: TodoItem[] + claudeMode: ClaudeMode +} + export interface JoinResult { message: string onlineUsers: string[] recentMessages: ChatMessage[] + todos: TodoItem[] + claudeMode: ClaudeMode } export interface SendResult { @@ -43,6 +64,8 @@ export interface ClaudeQueueStatus { current: string | null queue: string[] isProcessing: boolean + /** False for active-mode watches that may silently NO_REPLY */ + showResponding: boolean } export interface ChatNotifierApi { @@ -55,4 +78,9 @@ export interface ChatApi { getChatState(): ChatState sendMessage(message: string): SendResult getClaudeQueueStatus(): ClaudeQueueStatus + getTodos(): TodoItem[] + addTodo(text: string): { todo: TodoItem; todos: TodoItem[] } + removeTodo(id: string): { success: boolean; todos: TodoItem[] } + getClaudeMode(): ClaudeMode + setClaudeMode(mode: ClaudeMode): { mode: ClaudeMode } } diff --git a/examples/ts-group-chat/chat-server/chat-logic.ts b/examples/ts-group-chat/chat-server/chat-logic.ts index 5ac24459a..5bd1b106a 100644 --- a/examples/ts-group-chat/chat-server/chat-logic.ts +++ b/examples/ts-group-chat/chat-server/chat-logic.ts @@ -1,11 +1,11 @@ // Core chat business logic and data structures -import type { ChatMessage, ChatState } from './chat-api.js' +import type { ChatMessage, ChatRoomState } from './chat-api.js' -export type { ChatMessage, ChatState } +export type { ChatMessage, ChatRoomState } // Core chat business logic class export class ChatLogic { - private chatState: ChatState = { + private chatState: ChatRoomState = { onlineUsers: [], messages: [], } @@ -134,7 +134,7 @@ export class ChatLogic { return message } - getChatState(): ChatState { + getChatState(): ChatRoomState { return { onlineUsers: [...this.chatState.onlineUsers], messages: [...this.chatState.messages], diff --git a/examples/ts-group-chat/chat-server/claude-service.ts b/examples/ts-group-chat/chat-server/claude-service.ts index 76aacf96d..e5e6517ae 100644 --- a/examples/ts-group-chat/chat-server/claude-service.ts +++ b/examples/ts-group-chat/chat-server/claude-service.ts @@ -2,95 +2,183 @@ import { anthropicText } from '@tanstack/ai-anthropic' import { chat, toolDefinition } from '@tanstack/ai' import type { JSONSchema, ModelMessage, StreamChunk } from '@tanstack/ai' +import type { ClaudeMode } from './chat-api.js' +import type { TodoLogic } from './todo-logic.js' -// Define input schema for getWeather tool using JSONSchema -const getWeatherInputSchema: JSONSchema = { +const listTodosInputSchema: JSONSchema = { + type: 'object', + properties: {}, +} + +const listTodosOutputSchema: JSONSchema = { type: 'object', properties: { - location: { + todos: { + type: 'array', + items: { + type: 'object', + properties: { + id: { type: 'string' }, + text: { type: 'string' }, + createdBy: { type: 'string' }, + createdAt: { type: 'string' }, + }, + required: ['id', 'text', 'createdBy', 'createdAt'], + }, + }, + }, + required: ['todos'], +} + +const addTodoInputSchema: JSONSchema = { + type: 'object', + properties: { + text: { type: 'string', - description: 'The city or location to get weather for', + description: 'The todo item text to add', + }, + }, + required: ['text'], +} + +const addTodoOutputSchema: JSONSchema = { + type: 'object', + properties: { + success: { type: 'boolean' }, + todo: { + type: 'object', + properties: { + id: { type: 'string' }, + text: { type: 'string' }, + createdBy: { type: 'string' }, + createdAt: { type: 'string' }, + }, + required: ['id', 'text', 'createdBy', 'createdAt'], }, - unit: { + message: { type: 'string' }, + }, + required: ['success', 'todo', 'message'], +} + +const removeTodoInputSchema: JSONSchema = { + type: 'object', + properties: { + id: { type: 'string', - enum: ['celsius', 'fahrenheit'], - description: 'Temperature unit (defaults to celsius)', + description: 'The id of the todo item to remove (from listTodos)', }, }, - required: ['location'], + required: ['id'], } -// Define output schema for getWeather tool using JSONSchema -const getWeatherOutputSchema: JSONSchema = { +const removeTodoOutputSchema: JSONSchema = { type: 'object', properties: { - location: { type: 'string' }, - temperature: { type: 'number' }, - unit: { type: 'string' }, - conditions: { type: 'string' }, - humidity: { type: 'number' }, + success: { type: 'boolean' }, + message: { type: 'string' }, }, - required: ['location', 'temperature', 'unit', 'conditions'], + required: ['success', 'message'], } -// Create the getWeather tool using JSONSchema instead of Zod -const getWeatherTool = toolDefinition({ - name: 'getWeather', - description: - 'Get the current weather for a location. Returns temperature, conditions, and humidity.', - inputSchema: getWeatherInputSchema, - outputSchema: getWeatherOutputSchema, -}).server((rawArgs) => { - // JSONSchema doesn't carry compile-time types, so `rawArgs` is `unknown`. - const args = rawArgs as { location: string; unit?: string } - // Mock weather data - in a real app this would call a weather API - const mockWeatherData: Record< - string, - { temp: number; conditions: string; humidity: number } - > = { - 'new york': { temp: 72, conditions: 'Partly cloudy', humidity: 65 }, - london: { temp: 58, conditions: 'Overcast', humidity: 80 }, - tokyo: { temp: 68, conditions: 'Sunny', humidity: 55 }, - paris: { temp: 62, conditions: 'Light rain', humidity: 75 }, - sydney: { temp: 78, conditions: 'Clear skies', humidity: 45 }, - } +export const NO_REPLY_TOKEN = 'NO_REPLY' - const location = args.location.toLowerCase() - const unit = args.unit ?? 'celsius' - const weather = mockWeatherData[location] ?? { - temp: 65, - conditions: 'Unknown', - humidity: 50, - } +function createTodoTools(todoLogic: TodoLogic) { + const listTodosTool = toolDefinition({ + name: 'listTodos', + description: + 'List all items on the shared group todo list. Use this when asked what todos exist or before removing an item.', + inputSchema: listTodosInputSchema, + outputSchema: listTodosOutputSchema, + }).server(() => { + const todos = todoLogic.getTodos() + console.log(`πŸ“‹ listTodos tool called (${todos.length} items)`) + return { todos } + }) - // Convert temperature if needed - let temperature = weather.temp - if (unit === 'celsius') { - temperature = Math.round(((temperature - 32) * 5) / 9) - } + const addTodoTool = toolDefinition({ + name: 'addTodo', + description: 'Add an item to the shared group todo list.', + inputSchema: addTodoInputSchema, + outputSchema: addTodoOutputSchema, + }).server((rawArgs) => { + const args = rawArgs as { text: string } + const todo = todoLogic.addTodo(args.text, 'Claude') + console.log(`πŸ“‹ addTodo tool called: ${todo.text}`) + return { + success: true, + todo, + message: `Added "${todo.text}" to the todo list`, + } + }) - console.log(`🌀️ Weather tool called for: ${args.location}`) + const removeTodoTool = toolDefinition({ + name: 'removeTodo', + description: + 'Remove an item from the shared group todo list by id. Call listTodos first if you do not know the id.', + inputSchema: removeTodoInputSchema, + outputSchema: removeTodoOutputSchema, + }).server((rawArgs) => { + const args = rawArgs as { id: string } + const existing = todoLogic.getTodos().find((todo) => todo.id === args.id) + const success = todoLogic.removeTodo(args.id) + console.log(`πŸ“‹ removeTodo tool called: ${args.id} success=${success}`) + return { + success, + message: success + ? `Removed "${existing?.text ?? args.id}" from the todo list` + : `No todo found with id ${args.id}`, + } + }) - return { - location: args.location, - temperature, - unit, - conditions: weather.conditions, - humidity: weather.humidity, + return [listTodosTool, addTodoTool, removeTodoTool] +} + +function buildSystemPrompt(mode: ClaudeMode, mentioned: boolean): string { + const shared = `You are Claude, a friendly AI assistant in a group chat with a shared in-memory todo list. +Keep responses conversational and concise (1-3 sentences unless more detail is needed). +Use the todo tools (listTodos, addTodo, removeTodo) to inspect or change the list β€” do not invent what is on the list. +When answering questions about the items (including rough estimates like cost), call listTodos first and base your answer on those items. +When removing a todo, call listTodos first if you need the item id.` + + // Explicit @Claude / "Claude, ..." always gets a real reply, even in active mode. + if (mentioned || mode === 'passive') { + return `${shared} + +Users are talking to you directly. Always reply helpfully β€” never respond with ${NO_REPLY_TOKEN}. +Help with todo add/remove, questions about the list or its items, and other chat questions they ask you.` } -}) + + return `${shared} + +You are in ACTIVE mode (watching the chat, not necessarily addressed by name). +Reply and use tools when the message relates to the shared todo list, including: +- adding or removing todos +- listing or summarizing the list +- questions about the items themselves (cost estimates, quantities, planning, substitutions, etc.) +Only when the message has nothing to do with the todo list, respond with exactly: ${NO_REPLY_TOKEN} +Do not use ${NO_REPLY_TOKEN} for questions about items that are on (or being added to) the list.` +} export interface ClaudeRequest { id: string username: string message: string conversationHistory: Array + mode: ClaudeMode + mentioned: boolean } export interface ClaudeQueueStatus { current: string | null queue: Array isProcessing: boolean + showResponding: boolean +} + +function shouldShowResponding(request: ClaudeRequest | null): boolean { + if (!request) return false + // Active-mode watches may end in NO_REPLY β€” don't flash "responding" for those. + return request.mentioned || request.mode === 'passive' } export class ClaudeService { @@ -98,9 +186,16 @@ export class ClaudeService { private queue: Array = [] private currentRequest: ClaudeRequest | null = null private isProcessing = false + private todoTools: ReturnType + + constructor(todoLogic: TodoLogic) { + this.todoTools = createTodoTools(todoLogic) + } enqueue(request: ClaudeRequest): void { - console.log(`πŸ€– Claude: Enqueuing request from ${request.username}`) + console.log( + `πŸ€– Claude: Enqueuing request from ${request.username} (mode=${request.mode}, mentioned=${request.mentioned})`, + ) this.queue.push(request) } @@ -109,9 +204,15 @@ export class ClaudeService { current: this.currentRequest?.username || null, queue: this.queue.map((r) => r.username), isProcessing: this.isProcessing, + showResponding: + this.isProcessing && shouldShowResponding(this.currentRequest), } } + getCurrentRequest(): ClaudeRequest | null { + return this.currentRequest + } + startProcessing(): void { if (this.isProcessing || this.queue.length === 0) return @@ -132,13 +233,14 @@ export class ClaudeService { async *streamResponse( conversationHistory: Array, + mode: ClaudeMode, + mentioned: boolean, ): AsyncIterable { - const systemMessage = `You are Claude, a friendly AI assistant participating in a group chat. - Keep responses conversational, concise (2-3 sentences max unless asked for more detail), and helpful. - You can see the entire conversation history with all participants.` + const systemMessage = buildSystemPrompt(mode, mentioned) try { console.log(`πŸ€– Claude: ========== STARTING STREAM RESPONSE ==========`) + console.log(`πŸ€– Claude: Mode: ${mode}, mentioned: ${mentioned}`) console.log( `πŸ€– Claude: Conversation history (${conversationHistory.length} messages):`, ) @@ -155,7 +257,7 @@ export class ClaudeService { adapter: this.adapter, systemPrompts: [systemMessage], messages: [...conversationHistory] as any, - tools: [getWeatherTool], + tools: this.todoTools, })) { chunkCount++ @@ -185,6 +287,3 @@ export class ClaudeService { } } } - -// Global singleton instance -export const globalClaudeService = new ClaudeService() diff --git a/examples/ts-group-chat/chat-server/index.ts b/examples/ts-group-chat/chat-server/index.ts index 030c7790f..83152f2d4 100644 --- a/examples/ts-group-chat/chat-server/index.ts +++ b/examples/ts-group-chat/chat-server/index.ts @@ -1,6 +1,7 @@ // Main entry point for the chat server modules export * from './chat-api.js' export { ChatLogic } from './chat-logic.js' +export { TodoLogic } from './todo-logic.js' export * from './capnweb-rpc.js' // Note: claude-service is only used internally by capnweb-rpc, no need to export export * from './vite-plugin.js' diff --git a/examples/ts-group-chat/chat-server/todo-logic.ts b/examples/ts-group-chat/chat-server/todo-logic.ts new file mode 100644 index 000000000..14cbd11d6 --- /dev/null +++ b/examples/ts-group-chat/chat-server/todo-logic.ts @@ -0,0 +1,53 @@ +import type { TodoItem } from './chat-api.js' + +export class TodoLogic { + private todos: TodoItem[] = [] + private onTodosChanged?: (todos: TodoItem[]) => Promise + + constructor(callbacks?: { + onTodosChanged?: (todos: TodoItem[]) => Promise + }) { + this.onTodosChanged = callbacks?.onTodosChanged + } + + getTodos(): TodoItem[] { + return this.todos.map((todo) => ({ ...todo })) + } + + addTodo(text: string, createdBy: string): TodoItem { + const trimmed = text.trim() + if (!trimmed) { + throw new Error('Todo text cannot be empty') + } + + const todo: TodoItem = { + id: Math.random().toString(36).slice(2, 11), + text: trimmed, + createdAt: new Date().toISOString(), + createdBy, + } + + this.todos.push(todo) + console.log(`βœ… Todo added by ${createdBy}: ${todo.text}`) + void this.emitChange() + return { ...todo } + } + + removeTodo(id: string): boolean { + const index = this.todos.findIndex((todo) => todo.id === id) + if (index === -1) { + return false + } + + const [removed] = this.todos.splice(index, 1) + console.log(`πŸ—‘οΈ Todo removed: ${removed.text}`) + void this.emitChange() + return true + } + + private async emitChange() { + if (this.onTodosChanged) { + await this.onTodosChanged(this.getTodos()) + } + } +} diff --git a/examples/ts-group-chat/src/components/ChatInterface.tsx b/examples/ts-group-chat/src/components/ChatInterface.tsx index c9f369555..8c6d2b591 100644 --- a/examples/ts-group-chat/src/components/ChatInterface.tsx +++ b/examples/ts-group-chat/src/components/ChatInterface.tsx @@ -70,14 +70,14 @@ export function ChatInterface({ const userQueuePosition = claudeQueueStatus?.queue.indexOf(username || '') ?? -1 const isUserWaitingForClaude = userQueuePosition >= 0 - const isClaudeResponding = claudeQueueStatus?.isProcessing || false + const isClaudeResponding = claudeQueueStatus?.showResponding || false return (

πŸ’¬ Chat Messages

- {/* Claude Status Indicator */} + {/* Claude Status Indicator (hidden for active-mode silent NO_REPLY watches) */} {isClaudeResponding && claudeQueueStatus?.current && (
πŸ€– diff --git a/examples/ts-group-chat/src/components/OnlineUsers.tsx b/examples/ts-group-chat/src/components/OnlineUsers.tsx index 0cec464ae..c1fa22552 100644 --- a/examples/ts-group-chat/src/components/OnlineUsers.tsx +++ b/examples/ts-group-chat/src/components/OnlineUsers.tsx @@ -1,11 +1,15 @@ +import type { ClaudeMode } from '../../chat-server/chat-api' + interface OnlineUsersProps { onlineUsers: string[] currentUsername: string | null + claudeMode?: ClaudeMode } export function OnlineUsers({ onlineUsers, currentUsername, + claudeMode = 'passive', }: OnlineUsersProps) { return (
@@ -43,11 +47,20 @@ export function OnlineUsers({

How to use

- β€’ Type @Claude to ask Claude - a question + β€’ Add/remove todos in the panel, or ask Claude to manage them

+ {claudeMode === 'passive' ? ( +

+ β€’ Type @Claude to add, + remove, or ask about todos +

+ ) : ( +

+ β€’ Active mode: Claude watches for todo intent (no{' '} + @Claude needed) +

+ )}

β€’ Messages are sent to all online users

-

β€’ Claude responds in the chat

Real-time presence via Cap'n Web RPC diff --git a/examples/ts-group-chat/src/components/TodoList.tsx b/examples/ts-group-chat/src/components/TodoList.tsx new file mode 100644 index 000000000..0287912a4 --- /dev/null +++ b/examples/ts-group-chat/src/components/TodoList.tsx @@ -0,0 +1,167 @@ +import { useState } from 'react' +import type { ClaudeMode, TodoItem } from '../../chat-server/chat-api' + +interface TodoListProps { + todos: TodoItem[] + claudeMode: ClaudeMode + isJoined: boolean + onAddTodo: (text: string) => Promise<{ success: boolean; error?: string }> + onRemoveTodo: (id: string) => Promise<{ success: boolean; error?: string }> + onClaudeModeChange: ( + mode: ClaudeMode, + ) => Promise<{ success: boolean; error?: string }> +} + +export function TodoList({ + todos, + claudeMode, + isJoined, + onAddTodo, + onRemoveTodo, + onClaudeModeChange, +}: TodoListProps) { + const [text, setText] = useState('') + const [isSubmitting, setIsSubmitting] = useState(false) + const [error, setError] = useState(null) + + const handleAdd = async (e: React.FormEvent) => { + e.preventDefault() + if (!text.trim() || !isJoined || isSubmitting) return + + setIsSubmitting(true) + setError(null) + try { + const result = await onAddTodo(text) + if (result.success) { + setText('') + } else { + setError(result.error || 'Failed to add todo') + } + } finally { + setIsSubmitting(false) + } + } + + const handleRemove = async (id: string) => { + setError(null) + const result = await onRemoveTodo(id) + if (!result.success) { + setError(result.error || 'Failed to remove todo') + } + } + + const handleModeChange = async (mode: ClaudeMode) => { + if (mode === claudeMode || !isJoined) return + setError(null) + const result = await onClaudeModeChange(mode) + if (!result.success) { + setError(result.error || 'Failed to change Claude mode') + } + } + + return ( +
+
+

Todo List

+ {todos.length} items +
+ +
+
+ Claude mode +
+ + +
+
+

+ {claudeMode === 'passive' ? ( + <> + Claude waits for{' '} + @Claude before + adding/removing todos or answering todo questions. + + ) : ( + <> + Claude watches the chat for todo add/remove intent and todo + questions β€” no mention required. + + )} +

+
+ +
+ setText(e.target.value)} + placeholder="Add a todo..." + disabled={!isJoined || isSubmitting} + className="flex-1 bg-gray-700 text-white border border-gray-600 rounded px-3 py-2 text-sm focus:outline-none focus:border-blue-500 disabled:opacity-50" + /> + +
+ + {error &&

{error}

} + +
+ {todos.length === 0 ? ( +
+ No todos yet. Add one manually or ask Claude. +
+ ) : ( + todos.map((todo) => ( +
+
+

{todo.text}

+

+ by {todo.createdBy} +

+
+ +
+ )) + )} +
+
+ ) +} diff --git a/examples/ts-group-chat/src/hooks/useChatMessages.ts b/examples/ts-group-chat/src/hooks/useChatMessages.ts index 6d8562407..c7dc56c2c 100644 --- a/examples/ts-group-chat/src/hooks/useChatMessages.ts +++ b/examples/ts-group-chat/src/hooks/useChatMessages.ts @@ -4,11 +4,11 @@ import type { ChatApi, ChatMessage, ChatNotification, - ChatState, + ChatRoomState, } from '../../chat-server/chat-api' import type { ChatNotifier } from '@/lib/chat-notifier' -export type { ChatMessage, ChatState } +export type { ChatMessage, ChatRoomState } function notificationToMessage(notification: ChatNotification): ChatMessage { return { @@ -39,7 +39,7 @@ export function useChatMessages( () => apiRef.current ?? api, [api, apiRef], ) - const [chatState, setChatState] = useState({ + const [chatState, setChatState] = useState({ onlineUsers: [], messages: [], }) @@ -49,7 +49,17 @@ export function useChatMessages( useEffect(() => { if (!notifier) return - notifier.setHandler((notification: ChatNotification) => { + return notifier.addHandler((notification: ChatNotification) => { + // Status / sync events β€” not chat lines + if ( + notification.type === 'todos_updated' || + notification.type === 'claude_mode_changed' || + notification.type === 'claude_responding' || + notification.type === 'claude_idle' + ) { + return + } + setChatState((prev) => { const nextMessages = [ ...prev.messages, @@ -95,16 +105,19 @@ export function useChatMessages( if (!activeApi) return { success: false, error: 'Not connected' } try { + await activeApi.leaveChat() const result = await activeApi.joinChat(chatUsername) setChatState({ onlineUsers: result.onlineUsers, messages: result.recentMessages, }) + setIsJoined(true) return { success: true } } catch (error) { console.error('Error joining chat:', formatError(error), error) + setIsJoined(false) return { success: false, error: formatError(error) || 'Failed to join chat', @@ -129,13 +142,23 @@ export function useChatMessages( useEffect(() => { const activeApi = getApi() - if (!activeApi || !isConnected || !username) return + if (!activeApi || !isConnected) return let cancelled = false - const autoJoin = async () => { + const switchPersona = async () => { setIsJoined(false) + try { + // Always leave first so the previous persona is removed from the room. + await activeApi.leaveChat() + if (cancelled) return + + if (!username) { + setChatState({ onlineUsers: [], messages: [] }) + return + } + const result = await activeApi.joinChat(username) if (cancelled) return @@ -146,24 +169,22 @@ export function useChatMessages( setIsJoined(true) } catch (error) { if (!cancelled) { - console.error('Error auto-joining chat:', formatError(error), error) + console.error( + 'Error switching chat persona:', + formatError(error), + error, + ) } } } - void autoJoin() + void switchPersona() return () => { cancelled = true } }, [api, getApi, isConnected, username]) - useEffect(() => { - if (!username && isJoined && getApi()) { - void leaveChat() - } - }, [username, isJoined, getApi, leaveChat]) - return { chatState, sendMessage, diff --git a/examples/ts-group-chat/src/hooks/useClaude.ts b/examples/ts-group-chat/src/hooks/useClaude.ts index 5827552db..ab513e8e8 100644 --- a/examples/ts-group-chat/src/hooks/useClaude.ts +++ b/examples/ts-group-chat/src/hooks/useClaude.ts @@ -1,29 +1,69 @@ import { useEffect, useState, useCallback, type RefObject } from 'react' import type { RpcStub } from 'capnweb' -import type { ChatApi, ClaudeQueueStatus } from '../../chat-server/chat-api' +import type { + ChatApi, + ChatNotification, + ClaudeQueueStatus, +} from '../../chat-server/chat-api' +import type { ChatNotifier } from '@/lib/chat-notifier' export type { ClaudeQueueStatus } +const idleStatus: ClaudeQueueStatus = { + current: null, + queue: [], + isProcessing: false, + showResponding: false, +} + export function useClaude( api: RpcStub | null, apiRef: RefObject | null>, + notifier: ChatNotifier | null, isConnected: boolean, isJoined: boolean, ) { - const [queueStatus, setQueueStatus] = useState({ - current: null, - queue: [], - isProcessing: false, - }) + const [queueStatus, setQueueStatus] = useState(idleStatus) - const getApi = useCallback( - () => apiRef.current ?? api, - [api, apiRef], - ) + const getApi = useCallback(() => apiRef.current ?? api, [api, apiRef]) + + useEffect(() => { + if (!notifier) return + + return notifier.addHandler((notification: ChatNotification) => { + if (notification.type === 'claude_responding') { + setQueueStatus((prev) => ({ + ...prev, + isProcessing: true, + showResponding: true, + current: notification.username ?? prev.current, + })) + return + } + + if (notification.type === 'claude_idle') { + void (async () => { + const activeApi = getApi() + if (!activeApi) { + setQueueStatus(idleStatus) + return + } + try { + setQueueStatus(await activeApi.getClaudeQueueStatus()) + } catch { + setQueueStatus(idleStatus) + } + })() + } + }) + }, [notifier, getApi]) useEffect(() => { const activeApi = getApi() - if (!activeApi || !isConnected || !isJoined) return + if (!activeApi || !isConnected || !isJoined) { + setQueueStatus(idleStatus) + return + } const pollStatus = async () => { try { diff --git a/examples/ts-group-chat/src/hooks/useTodos.ts b/examples/ts-group-chat/src/hooks/useTodos.ts new file mode 100644 index 000000000..b124d6ee7 --- /dev/null +++ b/examples/ts-group-chat/src/hooks/useTodos.ts @@ -0,0 +1,155 @@ +import { useCallback, useEffect, useState, type RefObject } from 'react' +import type { RpcStub } from 'capnweb' +import type { + ChatApi, + ChatNotification, + ClaudeMode, + TodoItem, +} from '../../chat-server/chat-api' +import type { ChatNotifier } from '@/lib/chat-notifier' + +function formatError(error: unknown) { + if (error instanceof Error) return error.message + if (typeof error === 'object' && error !== null && 'message' in error) { + return String((error as { message: unknown }).message) + } + return String(error) +} + +export function useTodos( + api: RpcStub | null, + apiRef: RefObject | null>, + notifier: ChatNotifier | null, + isConnected: boolean, + isJoined: boolean, +) { + const getApi = useCallback(() => apiRef.current ?? api, [api, apiRef]) + const [todos, setTodos] = useState([]) + const [claudeMode, setClaudeMode] = useState('passive') + const [error, setError] = useState(null) + + useEffect(() => { + if (!notifier) return + + return notifier.addHandler((notification: ChatNotification) => { + if (notification.type === 'todos_updated' && notification.todos) { + setTodos(notification.todos) + } + if ( + notification.type === 'claude_mode_changed' && + notification.claudeMode + ) { + setClaudeMode(notification.claudeMode) + } + }) + }, [notifier]) + + useEffect(() => { + const activeApi = getApi() + if (!activeApi || !isConnected || !isJoined) { + if (!isJoined) { + setTodos([]) + setClaudeMode('passive') + } + return + } + + let cancelled = false + + const sync = async () => { + try { + const state = await activeApi.getChatState() + if (cancelled) return + setTodos(state.todos) + setClaudeMode(state.claudeMode) + setError(null) + } catch (err) { + if (!cancelled) { + console.error('Error syncing todos:', formatError(err), err) + setError(formatError(err)) + } + } + } + + void sync() + + return () => { + cancelled = true + } + }, [getApi, isConnected, isJoined]) + + const addTodo = useCallback( + async (text: string) => { + const activeApi = getApi() + if (!activeApi || !isJoined) { + return { success: false as const, error: 'Not joined' } + } + + try { + const result = await activeApi.addTodo(text) + setTodos(result.todos) + setError(null) + return { success: true as const, todo: result.todo } + } catch (err) { + const message = formatError(err) + console.error('Error adding todo:', message, err) + setError(message) + return { success: false as const, error: message } + } + }, + [getApi, isJoined], + ) + + const removeTodo = useCallback( + async (id: string) => { + const activeApi = getApi() + if (!activeApi || !isJoined) { + return { success: false as const, error: 'Not joined' } + } + + try { + const result = await activeApi.removeTodo(id) + setTodos(result.todos) + setError(null) + return { success: result.success } + } catch (err) { + const message = formatError(err) + console.error('Error removing todo:', message, err) + setError(message) + return { success: false as const, error: message } + } + }, + [getApi, isJoined], + ) + + const updateClaudeMode = useCallback( + async (mode: ClaudeMode) => { + const activeApi = getApi() + if (!activeApi || !isJoined) { + return { success: false as const, error: 'Not joined' } + } + + try { + const result = await activeApi.setClaudeMode(mode) + setClaudeMode(result.mode) + setError(null) + return { success: true as const, mode: result.mode } + } catch (err) { + const message = formatError(err) + console.error('Error setting Claude mode:', message, err) + setError(message) + return { success: false as const, error: message } + } + }, + [getApi, isJoined], + ) + + return { + todos, + claudeMode, + error, + addTodo, + removeTodo, + setClaudeMode: updateClaudeMode, + } +} diff --git a/examples/ts-group-chat/src/lib/chat-notifier.ts b/examples/ts-group-chat/src/lib/chat-notifier.ts index fa8cc5ea0..75f8d5657 100644 --- a/examples/ts-group-chat/src/lib/chat-notifier.ts +++ b/examples/ts-group-chat/src/lib/chat-notifier.ts @@ -1,15 +1,35 @@ import { RpcTarget } from 'capnweb' import type { ChatNotification } from '../../chat-server/chat-api' -const handlers = new WeakMap void>() +const handlers = new WeakMap< + ChatNotifier, + Set<(notification: ChatNotification) => void> +>() /** Client-side RpcTarget the server calls to push chat notifications. */ export class ChatNotifier extends RpcTarget { + addHandler(handler: (notification: ChatNotification) => void) { + let set = handlers.get(this) + if (!set) { + set = new Set() + handlers.set(this, set) + } + set.add(handler) + return () => { + set?.delete(handler) + } + } + + /** @deprecated Prefer addHandler for multiple listeners */ setHandler(handler: (notification: ChatNotification) => void) { - handlers.set(this, handler) + handlers.set(this, new Set([handler])) } notify(notification: ChatNotification) { - handlers.get(this)?.(notification) + const set = handlers.get(this) + if (!set) return + for (const handler of set) { + handler(notification) + } } } diff --git a/examples/ts-group-chat/src/routes/index.tsx b/examples/ts-group-chat/src/routes/index.tsx index 4a810940b..5ff5307ba 100644 --- a/examples/ts-group-chat/src/routes/index.tsx +++ b/examples/ts-group-chat/src/routes/index.tsx @@ -4,9 +4,11 @@ import { useState, useEffect } from 'react' import { useChatConnection } from '@/hooks/useChatConnection' import { useChatMessages } from '@/hooks/useChatMessages' import { useClaude } from '@/hooks/useClaude' +import { useTodos } from '@/hooks/useTodos' import { UserSelector } from '@/components/UserSelector' import { ChatInterface } from '@/components/ChatInterface' import { OnlineUsers } from '@/components/OnlineUsers' +import { TodoList } from '@/components/TodoList' export const Route = createFileRoute('/')({ component: ChatApp, @@ -26,7 +28,21 @@ function ChatApp() { username || null, ) - const { queueStatus } = useClaude(api, apiRef, isConnected, isJoined) + const { queueStatus } = useClaude( + api, + apiRef, + notifier, + isConnected, + isJoined, + ) + + const { todos, claudeMode, addTodo, removeTodo, setClaudeMode } = useTodos( + api, + apiRef, + notifier, + isConnected, + isJoined, + ) useEffect(() => { if (!isConnected && !isConnecting && !error) { @@ -50,7 +66,6 @@ function ChatApp() { )}
- {/* Left Column - Main Chat Interface */}
- {/* Right Column - Online Users & Info */} -
+
+ +
From 9db963fcfb8ece68817630f351ad06bc55735632 Mon Sep 17 00:00:00 2001 From: Jack Herrington Date: Sun, 19 Jul 2026 08:37:05 -0700 Subject: [PATCH 6/9] chore: remove accidental .agentsroom files from branch Co-authored-by: Cursor --- .agentsroom/.gitignore | 4 ---- .agentsroom/agents.json | 10 ---------- .agentsroom/prompts.json | 4 ---- 3 files changed, 18 deletions(-) delete mode 100644 .agentsroom/.gitignore delete mode 100644 .agentsroom/agents.json delete mode 100644 .agentsroom/prompts.json diff --git a/.agentsroom/.gitignore b/.agentsroom/.gitignore deleted file mode 100644 index 1acd1a387..000000000 --- a/.agentsroom/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# AgentsRoom: personal files (not committed to git) -*-personal.json -agents-local.json -sessions/ diff --git a/.agentsroom/agents.json b/.agentsroom/agents.json deleted file mode 100644 index e9a83e418..000000000 --- a/.agentsroom/agents.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "role": "fullstack", - "model": "opus", - "customName": "Full-Stack Developer", - "isPersonal": false, - "id": "agent-1776361243376-3sekdc", - "claudeSessionId": "96773a93-be2a-45a9-a732-ceb224d3d0e5" - } -] \ No newline at end of file diff --git a/.agentsroom/prompts.json b/.agentsroom/prompts.json deleted file mode 100644 index f4455d843..000000000 --- a/.agentsroom/prompts.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "folders": [], - "prompts": [] -} \ No newline at end of file From 8750a923f29c2a55ea3ba7c3cc2c7139a8a278ae Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 15:39:10 +0000 Subject: [PATCH 7/9] ci: apply automated fixes --- examples/ts-group-chat/README.md | 24 +++++++------ .../ts-group-chat/chat-server/capnweb-rpc.ts | 6 ++-- .../src/components/ChatInterface.tsx | 34 +++++++++---------- .../src/components/OnlineUsers.tsx | 4 +-- .../ts-group-chat/src/components/TodoList.tsx | 5 ++- .../src/hooks/useChatMessages.ts | 5 +-- examples/ts-group-chat/src/hooks/useClaude.ts | 4 +-- 7 files changed, 37 insertions(+), 45 deletions(-) diff --git a/examples/ts-group-chat/README.md b/examples/ts-group-chat/README.md index f04c548c1..d998e38ad 100644 --- a/examples/ts-group-chat/README.md +++ b/examples/ts-group-chat/README.md @@ -62,7 +62,9 @@ Open [http://localhost:3000](http://localhost:3000) in multiple browser tabs, pi ```typescript // Client: export ChatNotifier as localMain, call typed ChatApi methods const notifier = new ChatNotifier() -notifier.onNotification = (n) => { /* update UI */ } +notifier.onNotification = (n) => { + /* update UI */ +} const api = newWebSocketRpcSession(wsUrl, notifier) await api.joinChat(username) @@ -73,21 +75,21 @@ chatServer.setClientNotifier(clientNotifier) ## Scripts -| Script | Description | -|--------|-------------| -| `pnpm dev` | Start dev server with Cap'n Web WebSocket (port 3000) | -| `pnpm build` | Build TanStack Start app | -| `pnpm serve` | Preview production build | -| `pnpm test:types` | Typecheck | +| Script | Description | +| ----------------- | ----------------------------------------------------- | +| `pnpm dev` | Start dev server with Cap'n Web WebSocket (port 3000) | +| `pnpm build` | Build TanStack Start app | +| `pnpm serve` | Preview production build | +| `pnpm test:types` | Typecheck | ## Todo List + Claude Modes The room shares one in-memory todo list. Anyone can add/remove items from the UI. Claude has `listTodos`, `addTodo`, and `removeTodo` tools. -| Mode | Behavior | -|------|----------| -| **Passive** (default) | Claude only runs when a message mentions `@Claude` (or starts with `Claude`). Use that to add/remove todos or ask what's on the list. | -| **Active** | Claude reviews every chat message. If it detects todo add/remove intent or a todo question, it uses tools and replies. Unrelated messages get no reply. | +| Mode | Behavior | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Passive** (default) | Claude only runs when a message mentions `@Claude` (or starts with `Claude`). Use that to add/remove todos or ask what's on the list. | +| **Active** | Claude reviews every chat message. If it detects todo add/remove intent or a todo question, it uses tools and replies. Unrelated messages get no reply. | Mode is shared for the whole room and syncs live via Cap'n Web push. diff --git a/examples/ts-group-chat/chat-server/capnweb-rpc.ts b/examples/ts-group-chat/chat-server/capnweb-rpc.ts index d86fd0cd5..ad3b98290 100644 --- a/examples/ts-group-chat/chat-server/capnweb-rpc.ts +++ b/examples/ts-group-chat/chat-server/capnweb-rpc.ts @@ -332,7 +332,8 @@ export class ChatServer extends RpcTarget implements ChatApi { const trimmedMessage = messageText.trim() const mentioned = isClaudeMention(trimmedMessage) const shouldAskClaude = - mentioned || (claudeMode === 'active' && this.currentUsername !== 'Claude') + mentioned || + (claudeMode === 'active' && this.currentUsername !== 'Claude') const message = globalChat.sendMessageSync( this.currentUsername, @@ -386,8 +387,7 @@ export class ChatServer extends RpcTarget implements ChatApi { const currentRequest = claudeService.getCurrentRequest() const requestMode = currentRequest?.mode ?? claudeMode const mentioned = currentRequest?.mentioned ?? false - const showResponding = - claudeService.getQueueStatus().showResponding + const showResponding = claudeService.getQueueStatus().showResponding try { // Only announce when a visible reply is expected (not active silent watches). diff --git a/examples/ts-group-chat/src/components/ChatInterface.tsx b/examples/ts-group-chat/src/components/ChatInterface.tsx index 8c6d2b591..8da63c325 100644 --- a/examples/ts-group-chat/src/components/ChatInterface.tsx +++ b/examples/ts-group-chat/src/components/ChatInterface.tsx @@ -194,25 +194,23 @@ export function ChatInterface({ {!isJoined && (

Joining chat room…

)} - {sendError && ( -

{sendError}

- )} + {sendError &&

{sendError}

}
- setMessageText(e.target.value)} - placeholder="Type your message..." - className="flex-1 bg-gray-700 text-white border border-gray-600 rounded px-3 py-2 focus:outline-none focus:border-blue-500" - disabled={isSending || !isJoined} - /> - + setMessageText(e.target.value)} + placeholder="Type your message..." + className="flex-1 bg-gray-700 text-white border border-gray-600 rounded px-3 py-2 focus:outline-none focus:border-blue-500" + disabled={isSending || !isJoined} + /> +
) : ( diff --git a/examples/ts-group-chat/src/components/OnlineUsers.tsx b/examples/ts-group-chat/src/components/OnlineUsers.tsx index c1fa22552..9b0cae4cb 100644 --- a/examples/ts-group-chat/src/components/OnlineUsers.tsx +++ b/examples/ts-group-chat/src/components/OnlineUsers.tsx @@ -46,9 +46,7 @@ export function OnlineUsers({

How to use

-

- β€’ Add/remove todos in the panel, or ask Claude to manage them -

+

β€’ Add/remove todos in the panel, or ask Claude to manage them

{claudeMode === 'passive' ? (

β€’ Type @Claude to add, diff --git a/examples/ts-group-chat/src/components/TodoList.tsx b/examples/ts-group-chat/src/components/TodoList.tsx index 0287912a4..6d2fe60cc 100644 --- a/examples/ts-group-chat/src/components/TodoList.tsx +++ b/examples/ts-group-chat/src/components/TodoList.tsx @@ -99,9 +99,8 @@ export function TodoList({

{claudeMode === 'passive' ? ( <> - Claude waits for{' '} - @Claude before - adding/removing todos or answering todo questions. + Claude waits for @Claude{' '} + before adding/removing todos or answering todo questions. ) : ( <> diff --git a/examples/ts-group-chat/src/hooks/useChatMessages.ts b/examples/ts-group-chat/src/hooks/useChatMessages.ts index c7dc56c2c..11cb9050b 100644 --- a/examples/ts-group-chat/src/hooks/useChatMessages.ts +++ b/examples/ts-group-chat/src/hooks/useChatMessages.ts @@ -35,10 +35,7 @@ export function useChatMessages( isConnected: boolean, username: string | null, ) { - const getApi = useCallback( - () => apiRef.current ?? api, - [api, apiRef], - ) + const getApi = useCallback(() => apiRef.current ?? api, [api, apiRef]) const [chatState, setChatState] = useState({ onlineUsers: [], messages: [], diff --git a/examples/ts-group-chat/src/hooks/useClaude.ts b/examples/ts-group-chat/src/hooks/useClaude.ts index ab513e8e8..dc1564cec 100644 --- a/examples/ts-group-chat/src/hooks/useClaude.ts +++ b/examples/ts-group-chat/src/hooks/useClaude.ts @@ -73,9 +73,7 @@ export function useClaude( const message = error instanceof Error ? error.message - : typeof error === 'object' && - error !== null && - 'message' in error + : typeof error === 'object' && error !== null && 'message' in error ? String((error as { message: unknown }).message) : 'Unknown error' console.error('Error polling Claude status:', message, error) From 0e3913fdb0d41b26529fedf53aeacc7d25e47aff Mon Sep 17 00:00:00 2001 From: Jack Herrington Date: Sun, 19 Jul 2026 09:12:49 -0700 Subject: [PATCH 8/9] fix(examples): satisfy ESLint rules in ts-group-chat Align array/method signature styles and remove always-false conditionals so the Cap'n Web example matches repo lint conventions. Co-authored-by: Cursor --- .../ts-group-chat/chat-server/capnweb-rpc.ts | 12 ++---- .../ts-group-chat/chat-server/chat-api.ts | 40 +++++++++---------- .../ts-group-chat/chat-server/todo-logic.ts | 8 ++-- .../ts-group-chat/chat-server/vite-plugin.ts | 4 +- .../src/components/ChatInterface.tsx | 5 +-- .../src/components/OnlineUsers.tsx | 2 +- .../ts-group-chat/src/components/TodoList.tsx | 2 +- .../src/hooks/useChatConnection.ts | 4 +- .../src/hooks/useChatMessages.ts | 19 +++++---- examples/ts-group-chat/src/hooks/useClaude.ts | 5 ++- examples/ts-group-chat/src/hooks/useTodos.ts | 7 ++-- .../ts-group-chat/src/lib/chat-notifier.ts | 2 +- examples/ts-group-chat/src/routes/index.tsx | 2 +- 13 files changed, 56 insertions(+), 56 deletions(-) diff --git a/examples/ts-group-chat/chat-server/capnweb-rpc.ts b/examples/ts-group-chat/chat-server/capnweb-rpc.ts index ad3b98290..7a6961438 100644 --- a/examples/ts-group-chat/chat-server/capnweb-rpc.ts +++ b/examples/ts-group-chat/chat-server/capnweb-rpc.ts @@ -142,12 +142,12 @@ export class ChatServer extends RpcTarget implements ChatApi { }) } - static async broadcastToAll( + static broadcastToAll( notification: ChatNotification, excludeUser?: string, ) { const payload = normalizeNotification(notification) - const msgPreview = payload.message?.substring(0, 50) || '' + const msgPreview = payload.message.substring(0, 50) console.log( `\nπŸ“¬ broadcastToAll() - type: ${payload.type}, from: ${payload.username}, message: "${msgPreview}..."`, ) @@ -155,7 +155,7 @@ export class ChatServer extends RpcTarget implements ChatApi { console.log(`πŸ“¬ Exclude user: ${excludeUser || 'none'}`) let successCount = 0 - const successful: string[] = [] + const successful: Array = [] for (const [username, callback] of clients.entries()) { if (excludeUser && username === excludeUser) { @@ -266,7 +266,7 @@ export class ChatServer extends RpcTarget implements ChatApi { } } - getTodos(): TodoItem[] { + getTodos(): Array { return globalTodos.getTodos() } @@ -299,10 +299,6 @@ export class ChatServer extends RpcTarget implements ChatApi { } setClaudeMode(mode: ClaudeMode) { - if (mode !== 'active' && mode !== 'passive') { - throw new Error('Claude mode must be "active" or "passive"') - } - claudeMode = mode console.log(`πŸ€– Claude mode set to ${mode}`) diff --git a/examples/ts-group-chat/chat-server/chat-api.ts b/examples/ts-group-chat/chat-server/chat-api.ts index 05f2aa13f..866ba0811 100644 --- a/examples/ts-group-chat/chat-server/chat-api.ts +++ b/examples/ts-group-chat/chat-server/chat-api.ts @@ -24,8 +24,8 @@ export interface ChatNotification { username?: string timestamp?: string id?: string - onlineUsers?: string[] - todos?: TodoItem[] + onlineUsers?: Array + todos?: Array claudeMode?: ClaudeMode } @@ -38,20 +38,20 @@ export interface ChatMessage { } export interface ChatRoomState { - onlineUsers: string[] - messages: ChatMessage[] + onlineUsers: Array + messages: Array } export interface ChatState extends ChatRoomState { - todos: TodoItem[] + todos: Array claudeMode: ClaudeMode } export interface JoinResult { message: string - onlineUsers: string[] - recentMessages: ChatMessage[] - todos: TodoItem[] + onlineUsers: Array + recentMessages: Array + todos: Array claudeMode: ClaudeMode } @@ -62,25 +62,25 @@ export interface SendResult { export interface ClaudeQueueStatus { current: string | null - queue: string[] + queue: Array isProcessing: boolean /** False for active-mode watches that may silently NO_REPLY */ showResponding: boolean } export interface ChatNotifierApi { - notify(notification: ChatNotification): void | Promise + notify: (notification: ChatNotification) => void | Promise } export interface ChatApi { - joinChat(username: string): JoinResult - leaveChat(): { message: string } - getChatState(): ChatState - sendMessage(message: string): SendResult - getClaudeQueueStatus(): ClaudeQueueStatus - getTodos(): TodoItem[] - addTodo(text: string): { todo: TodoItem; todos: TodoItem[] } - removeTodo(id: string): { success: boolean; todos: TodoItem[] } - getClaudeMode(): ClaudeMode - setClaudeMode(mode: ClaudeMode): { mode: ClaudeMode } + joinChat: (username: string) => JoinResult + leaveChat: () => { message: string } + getChatState: () => ChatState + sendMessage: (message: string) => SendResult + getClaudeQueueStatus: () => ClaudeQueueStatus + getTodos: () => Array + addTodo: (text: string) => { todo: TodoItem; todos: Array } + removeTodo: (id: string) => { success: boolean; todos: Array } + getClaudeMode: () => ClaudeMode + setClaudeMode: (mode: ClaudeMode) => { mode: ClaudeMode } } diff --git a/examples/ts-group-chat/chat-server/todo-logic.ts b/examples/ts-group-chat/chat-server/todo-logic.ts index 14cbd11d6..13bd77e97 100644 --- a/examples/ts-group-chat/chat-server/todo-logic.ts +++ b/examples/ts-group-chat/chat-server/todo-logic.ts @@ -1,16 +1,16 @@ import type { TodoItem } from './chat-api.js' export class TodoLogic { - private todos: TodoItem[] = [] - private onTodosChanged?: (todos: TodoItem[]) => Promise + private todos: Array = [] + private onTodosChanged?: (todos: Array) => Promise constructor(callbacks?: { - onTodosChanged?: (todos: TodoItem[]) => Promise + onTodosChanged?: (todos: Array) => Promise }) { this.onTodosChanged = callbacks?.onTodosChanged } - getTodos(): TodoItem[] { + getTodos(): Array { return this.todos.map((todo) => ({ ...todo })) } diff --git a/examples/ts-group-chat/chat-server/vite-plugin.ts b/examples/ts-group-chat/chat-server/vite-plugin.ts index 2a54af29a..639433b5b 100644 --- a/examples/ts-group-chat/chat-server/vite-plugin.ts +++ b/examples/ts-group-chat/chat-server/vite-plugin.ts @@ -1,8 +1,8 @@ // Vite WebSocket plugin for handling Cap'n Web RPC connections -import type { Plugin } from 'vite' import { WebSocketServer } from 'ws' import { newWebSocketRpcSession } from 'capnweb' import { ChatServer } from './capnweb-rpc.js' +import type { Plugin } from 'vite' import type { ChatNotifierApi } from './chat-api.js' export function websocketRpcPlugin(): Plugin { @@ -17,7 +17,7 @@ export function websocketRpcPlugin(): Plugin { }) server.httpServer.on('upgrade', (request, socket, head) => { - const pathname = new URL(request.url!, `http://${request.headers.host}`) + const pathname = new URL(request.url, `http://${request.headers.host}`) .pathname if (pathname === '/api/websocket') { diff --git a/examples/ts-group-chat/src/components/ChatInterface.tsx b/examples/ts-group-chat/src/components/ChatInterface.tsx index 8da63c325..385b224fe 100644 --- a/examples/ts-group-chat/src/components/ChatInterface.tsx +++ b/examples/ts-group-chat/src/components/ChatInterface.tsx @@ -3,7 +3,7 @@ import type { ChatMessage } from '../hooks/useChatMessages' import type { ClaudeQueueStatus } from '../hooks/useClaude' interface ChatInterfaceProps { - messages: ChatMessage[] + messages: Array onSendMessage: ( message: string, ) => Promise<{ success: boolean; error?: string }> @@ -101,8 +101,7 @@ export function ChatInterface({ ) : ( messages .map((msg) => { - // Ensure we have proper message data - if (!msg || !msg.id) { + if (!msg.id) { console.warn('Invalid message:', msg) return null } diff --git a/examples/ts-group-chat/src/components/OnlineUsers.tsx b/examples/ts-group-chat/src/components/OnlineUsers.tsx index 9b0cae4cb..627db1869 100644 --- a/examples/ts-group-chat/src/components/OnlineUsers.tsx +++ b/examples/ts-group-chat/src/components/OnlineUsers.tsx @@ -1,7 +1,7 @@ import type { ClaudeMode } from '../../chat-server/chat-api' interface OnlineUsersProps { - onlineUsers: string[] + onlineUsers: Array currentUsername: string | null claudeMode?: ClaudeMode } diff --git a/examples/ts-group-chat/src/components/TodoList.tsx b/examples/ts-group-chat/src/components/TodoList.tsx index 6d2fe60cc..00466b020 100644 --- a/examples/ts-group-chat/src/components/TodoList.tsx +++ b/examples/ts-group-chat/src/components/TodoList.tsx @@ -2,7 +2,7 @@ import { useState } from 'react' import type { ClaudeMode, TodoItem } from '../../chat-server/chat-api' interface TodoListProps { - todos: TodoItem[] + todos: Array claudeMode: ClaudeMode isJoined: boolean onAddTodo: (text: string) => Promise<{ success: boolean; error?: string }> diff --git a/examples/ts-group-chat/src/hooks/useChatConnection.ts b/examples/ts-group-chat/src/hooks/useChatConnection.ts index ae2e8788f..935209d15 100644 --- a/examples/ts-group-chat/src/hooks/useChatConnection.ts +++ b/examples/ts-group-chat/src/hooks/useChatConnection.ts @@ -1,4 +1,4 @@ -import { useState, useRef, useCallback, useEffect } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { newWebSocketRpcSession } from 'capnweb' import type { RpcStub } from 'capnweb' import type { ChatApi } from '../../chat-server/chat-api' @@ -80,7 +80,7 @@ export function useChatConnection() { stub[Symbol.dispose]() return } - if (!chatState || !Array.isArray(chatState.onlineUsers)) { + if (!Array.isArray(chatState.onlineUsers)) { throw new Error('Invalid chat state from server') } diff --git a/examples/ts-group-chat/src/hooks/useChatMessages.ts b/examples/ts-group-chat/src/hooks/useChatMessages.ts index 11cb9050b..9ffb978a6 100644 --- a/examples/ts-group-chat/src/hooks/useChatMessages.ts +++ b/examples/ts-group-chat/src/hooks/useChatMessages.ts @@ -1,4 +1,5 @@ -import { useState, useEffect, useCallback, type RefObject } from 'react' +import { useCallback, useEffect, useState } from 'react' +import type { RefObject } from 'react' import type { RpcStub } from 'capnweb' import type { ChatApi, @@ -23,7 +24,7 @@ function notificationToMessage(notification: ChatNotification): ChatMessage { function formatError(error: unknown) { if (error instanceof Error) return error.message if (typeof error === 'object' && error !== null && 'message' in error) { - return String((error as { message: unknown }).message) + return String(error.message) } return String(error) } @@ -141,7 +142,8 @@ export function useChatMessages( const activeApi = getApi() if (!activeApi || !isConnected) return - let cancelled = false + // Object flag so cancellation stays visible across sync RpcStub awaits. + const cancelled: { current: boolean } = { current: false } const switchPersona = async () => { setIsJoined(false) @@ -149,15 +151,16 @@ export function useChatMessages( try { // Always leave first so the previous persona is removed from the room. await activeApi.leaveChat() - if (cancelled) return if (!username) { - setChatState({ onlineUsers: [], messages: [] }) + if (!cancelled.current) { + setChatState({ onlineUsers: [], messages: [] }) + } return } const result = await activeApi.joinChat(username) - if (cancelled) return + if (cancelled.current) return setChatState({ onlineUsers: result.onlineUsers, @@ -165,7 +168,7 @@ export function useChatMessages( }) setIsJoined(true) } catch (error) { - if (!cancelled) { + if (!cancelled.current) { console.error( 'Error switching chat persona:', formatError(error), @@ -178,7 +181,7 @@ export function useChatMessages( void switchPersona() return () => { - cancelled = true + cancelled.current = true } }, [api, getApi, isConnected, username]) diff --git a/examples/ts-group-chat/src/hooks/useClaude.ts b/examples/ts-group-chat/src/hooks/useClaude.ts index dc1564cec..55b99c64f 100644 --- a/examples/ts-group-chat/src/hooks/useClaude.ts +++ b/examples/ts-group-chat/src/hooks/useClaude.ts @@ -1,4 +1,5 @@ -import { useEffect, useState, useCallback, type RefObject } from 'react' +import { useCallback, useEffect, useState } from 'react' +import type { RefObject } from 'react' import type { RpcStub } from 'capnweb' import type { ChatApi, @@ -74,7 +75,7 @@ export function useClaude( error instanceof Error ? error.message : typeof error === 'object' && error !== null && 'message' in error - ? String((error as { message: unknown }).message) + ? String(error.message) : 'Unknown error' console.error('Error polling Claude status:', message, error) } diff --git a/examples/ts-group-chat/src/hooks/useTodos.ts b/examples/ts-group-chat/src/hooks/useTodos.ts index b124d6ee7..8b192411b 100644 --- a/examples/ts-group-chat/src/hooks/useTodos.ts +++ b/examples/ts-group-chat/src/hooks/useTodos.ts @@ -1,4 +1,5 @@ -import { useCallback, useEffect, useState, type RefObject } from 'react' +import { useCallback, useEffect, useState } from 'react' +import type { RefObject } from 'react' import type { RpcStub } from 'capnweb' import type { ChatApi, @@ -11,7 +12,7 @@ import type { ChatNotifier } from '@/lib/chat-notifier' function formatError(error: unknown) { if (error instanceof Error) return error.message if (typeof error === 'object' && error !== null && 'message' in error) { - return String((error as { message: unknown }).message) + return String(error.message) } return String(error) } @@ -24,7 +25,7 @@ export function useTodos( isJoined: boolean, ) { const getApi = useCallback(() => apiRef.current ?? api, [api, apiRef]) - const [todos, setTodos] = useState([]) + const [todos, setTodos] = useState>([]) const [claudeMode, setClaudeMode] = useState('passive') const [error, setError] = useState(null) diff --git a/examples/ts-group-chat/src/lib/chat-notifier.ts b/examples/ts-group-chat/src/lib/chat-notifier.ts index 75f8d5657..7b1912cf9 100644 --- a/examples/ts-group-chat/src/lib/chat-notifier.ts +++ b/examples/ts-group-chat/src/lib/chat-notifier.ts @@ -16,7 +16,7 @@ export class ChatNotifier extends RpcTarget { } set.add(handler) return () => { - set?.delete(handler) + set.delete(handler) } } diff --git a/examples/ts-group-chat/src/routes/index.tsx b/examples/ts-group-chat/src/routes/index.tsx index 5ff5307ba..b7da48472 100644 --- a/examples/ts-group-chat/src/routes/index.tsx +++ b/examples/ts-group-chat/src/routes/index.tsx @@ -1,5 +1,5 @@ import { createFileRoute } from '@tanstack/react-router' -import { useState, useEffect } from 'react' +import { useEffect, useState } from 'react' import { useChatConnection } from '@/hooks/useChatConnection' import { useChatMessages } from '@/hooks/useChatMessages' From 2453eefebb1ab1c8ead3e889a9e15adc911dff44 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:14:27 +0000 Subject: [PATCH 9/9] ci: apply automated fixes --- examples/ts-group-chat/chat-server/capnweb-rpc.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/ts-group-chat/chat-server/capnweb-rpc.ts b/examples/ts-group-chat/chat-server/capnweb-rpc.ts index 7a6961438..822861c7e 100644 --- a/examples/ts-group-chat/chat-server/capnweb-rpc.ts +++ b/examples/ts-group-chat/chat-server/capnweb-rpc.ts @@ -142,10 +142,7 @@ export class ChatServer extends RpcTarget implements ChatApi { }) } - static broadcastToAll( - notification: ChatNotification, - excludeUser?: string, - ) { + static broadcastToAll(notification: ChatNotification, excludeUser?: string) { const payload = normalizeNotification(notification) const msgPreview = payload.message.substring(0, 50) console.log(