<html lang="en">
<head></head>
<body>

<form id="mainForm" method="post" action="https://stackblitz.com/run" target="_self">
<input type="hidden" name="project[files][README.md]" value="# Kitchen Sink Example for RivetKit

Example project demonstrating all RivetKit features with [RivetKit](https://rivetkit.org).

[Learn More →](https://github.com/rivet-dev/rivetkit)

[Discord](https://rivet.dev/discord) — [Documentation](https://rivetkit.org) — [Issues](https://github.com/rivet-dev/rivetkit/issues)

## Getting Started

### Prerequisites

- Node.js 18+ or Bun
- RivetKit development environment

### Installation

```sh
git clone https://github.com/rivet-dev/rivetkit
cd rivetkit/examples/kitchen-sink
npm install
```

### Development

```sh
npm run dev
```

This will start both the backend server (port 8080) and frontend development server (port 5173).

Open [http://localhost:5173](http://localhost:5173) in your browser.

## License

Apache 2.0
">
<input type="hidden" name="project[files][SPEC.md]" value="# Kitchen Sink Example Specification

## Overview
Comprehensive example demonstrating all RivetKit features with a simple React frontend.

## UI Structure

### Header (Always visible)

**Configuration Row 1:**
- **Transport**: WebSocket ↔ SSE toggle
- **Encoding**: JSON ↔ CBOR ↔ Bare dropdown
- **Connection Mode**: Handle ↔ Connection toggle

**Actor Management Row 2:**
- **Actor Name**: dropdown
- **Key**: input
- **Region**: input
- **Input JSON**: textarea
- **Buttons**: `create()` | `get()` | `getOrCreate()` | `getForId()` (when no actor connected)
- **OR Button**: `dispose()` (when actor connected)

**Status Row 3:**
- Connection status indicator with actor info (Name | Key | Actor ID | Status)

### Main Content (8 Tabs - disabled until actor connected)

#### 1. Actions
- Action name dropdown/input
- Arguments JSON textarea
- Call button
- Response display

#### 2. Events
- Event listener controls
- Live event feed with timestamps
- Event type filter

#### 3. Schedule
- `schedule.at()` with timestamp input
- `schedule.after()` with delay input
- Scheduled task history
- Custom alarm data input

#### 4. Sleep
- `sleep()` button
- Sleep timeout configuration
- Lifecycle event display (`onWake`, `onSleep`)

#### 5. Connections (Only visible in Connection Mode)
- Connection state display
- Connection info (ID, transport, encoding)
- Connection-specific event history

#### 6. Raw HTTP
- HTTP method dropdown
- Path input
- Headers textarea
- Body textarea
- Send button &amp; response display

#### 7. Raw WebSocket
- Message input (text/binary toggle)
- Send button
- Message history (sent/received with timestamps)

#### 8. Metadata
- Actor name, tags, region display

## User Flow
1. Configure transport/encoding/connection mode
2. Fill actor details (name, key, region, input)
3. Click create/get/getOrCreate/getForId
4. Status shows connection info, tabs become enabled
5. Use tabs to test features
6. Click dispose() to disconnect and return to step 1

## Actors

### 1. `demo` - Main comprehensive actor
- All action types (sync/async/promise)
- Events and state management
- Scheduling capabilities (`schedule.at()`, `schedule.after()`)
- Sleep functionality with configurable timeout
- Connection state support
- Lifecycle hooks (`onWake`, `onSleep`, `onConnect`, `onDisconnect`)
- Metadata access

### 2. `http` - Raw HTTP handling
- `onFetch()` handler
- Multiple endpoint examples
- Various HTTP methods support

### 3. `websocket` - Raw WebSocket handling
- `onWebSocket()` handler
- Text and binary message support
- Connection lifecycle management

## Features Demonstrated

### Core Features
- **Actions**: sync, async, promise-based with various input types
- **Events**: broadcasting to connected clients
- **State Management**: actor state + per-connection state
- **Scheduling**: `schedule.at()`, `schedule.after()` with alarm handlers
- **Force Sleep**: `sleep()` method with configurable sleep timeout
- **Lifecycle Hooks**: `onWake`, `onSleep`, `onConnect`, `onDisconnect`

### Configuration Options
- **Transport**: WebSocket vs Server-Sent Events
- **Encoding**: JSON, CBOR, Bare

### Raw Protocols
- **Raw HTTP**: Direct HTTP request handling
- **Raw WebSocket**: Direct WebSocket connection handling

### Connection Patterns
- **Handle Mode**: Fire-and-forget action calls
- **Connection Mode**: Persistent connection with real-time events

### Actor Management
- **Create**: `client.actor.create(key, opts)`
- **Get**: `client.actor.get(key, opts)`
- **Get or Create**: `client.actor.getOrCreate(key, opts)`
- **Get by ID**: `client.actor.getForId(actorId, opts)`
- **Dispose**: `client.dispose()` - disconnect all connections
">
<input type="hidden" name="project[files][index.html]" value="&lt;!doctype html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;link rel=&quot;icon&quot; type=&quot;image/svg+xml&quot; href=&quot;/vite.svg&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
    &lt;title&gt;RivetKit Kitchen Sink&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
    &lt;script type=&quot;module&quot; src=&quot;/src/frontend/main.tsx&quot;&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;">
<input type="hidden" name="project[files][package.json]" value="{&quot;name&quot;:&quot;example-kitchen-sink&quot;,&quot;private&quot;:true,&quot;version&quot;:&quot;2.0.21&quot;,&quot;type&quot;:&quot;module&quot;,&quot;scripts&quot;:{&quot;dev&quot;:&quot;concurrently \&quot;npm run dev:backend\&quot; \&quot;npm run dev:frontend\&quot;&quot;,&quot;dev:backend&quot;:&quot;tsx --watch src/backend/server.ts&quot;,&quot;dev:frontend&quot;:&quot;vite&quot;,&quot;build&quot;:&quot;tsc &amp;&amp; vite build&quot;,&quot;preview&quot;:&quot;vite preview&quot;,&quot;check-types&quot;:&quot;tsc --noEmit&quot;},&quot;dependencies&quot;:{&quot;@rivetkit/react&quot;:&quot;https://pkg.pr.new/rivet-dev/rivet/@rivetkit/react@1784d8f4835dfe0620f43e819ff860dd7dd02821&quot;,&quot;rivetkit&quot;:&quot;https://pkg.pr.new/rivet-dev/rivet/rivetkit@1784d8f4835dfe0620f43e819ff860dd7dd02821&quot;,&quot;react&quot;:&quot;^18.2.0&quot;,&quot;react-dom&quot;:&quot;^18.2.0&quot;},&quot;devDependencies&quot;:{&quot;@types/react&quot;:&quot;^18.2.66&quot;,&quot;@types/react-dom&quot;:&quot;^18.2.22&quot;,&quot;@vitejs/plugin-react&quot;:&quot;^4.2.1&quot;,&quot;concurrently&quot;:&quot;^8.2.2&quot;,&quot;tsx&quot;:&quot;^4.7.1&quot;,&quot;typescript&quot;:&quot;^5.2.2&quot;,&quot;vite&quot;:&quot;^5.2.0&quot;}}">
<input type="hidden" name="project[files][tsconfig.json]" value="{
	&quot;compilerOptions&quot;: {
		&quot;target&quot;: &quot;ES2020&quot;,
		&quot;useDefineForClassFields&quot;: true,
		&quot;lib&quot;: [&quot;ES2020&quot;, &quot;DOM&quot;, &quot;DOM.Iterable&quot;],
		&quot;module&quot;: &quot;ESNext&quot;,
		&quot;skipLibCheck&quot;: true,
		&quot;moduleResolution&quot;: &quot;bundler&quot;,
		&quot;allowImportingTsExtensions&quot;: true,
		&quot;resolveJsonModule&quot;: true,
		&quot;isolatedModules&quot;: true,
		&quot;noEmit&quot;: true,
		&quot;jsx&quot;: &quot;react-jsx&quot;,
		&quot;strict&quot;: true,
		&quot;noUnusedLocals&quot;: true,
		&quot;noUnusedParameters&quot;: true,
		&quot;noFallthroughCasesInSwitch&quot;: true
	},
	&quot;include&quot;: [&quot;src&quot;],
	&quot;references&quot;: [{ &quot;path&quot;: &quot;./tsconfig.node.json&quot; }]
}
">
<input type="hidden" name="project[files][tsconfig.node.json]" value="{
	&quot;compilerOptions&quot;: {
		&quot;composite&quot;: true,
		&quot;skipLibCheck&quot;: true,
		&quot;module&quot;: &quot;ESNext&quot;,
		&quot;moduleResolution&quot;: &quot;bundler&quot;,
		&quot;allowSyntheticDefaultImports&quot;: true
	},
	&quot;include&quot;: [&quot;vite.config.ts&quot;]
}
">
<input type="hidden" name="project[files][turbo.json]" value="{
	&quot;$schema&quot;: &quot;https://turbo.build/schema.json&quot;,
	&quot;extends&quot;: [&quot;//&quot;]
}
">
<input type="hidden" name="project[files][vite.config.ts]" value="import react from &quot;@vitejs/plugin-react&quot;;
import { defineConfig } from &quot;vite&quot;;

export default defineConfig({
	plugins: [react()],
	server: {
		port: 5173,
	},
});
">
<input type="hidden" name="project[files][src/backend/registry.ts]" value="import { setup } from &quot;rivetkit&quot;;
import { demo } from &quot;./actors/demo&quot;;

export const registry = setup({
	use: {
		demo,
	},
});

export type Registry = typeof registry;
">
<input type="hidden" name="project[files][src/backend/server.ts]" value="import { registry } from &quot;./registry&quot;;

registry.start();
">
<input type="hidden" name="project[files][src/frontend/App.tsx]" value="import { createClient } from &quot;@rivetkit/react&quot;;
import { useState, useMemo, useEffect } from &quot;react&quot;;
import type { Registry } from &quot;../backend/registry&quot;;
import ConnectionScreen from &quot;./components/ConnectionScreen&quot;;
import InteractionScreen from &quot;./components/InteractionScreen&quot;;

export interface AppState {
  // Configuration
  transport: &quot;websocket&quot; | &quot;sse&quot;;
  encoding: &quot;json&quot; | &quot;cbor&quot; | &quot;bare&quot;;
  connectionMode: &quot;handle&quot; | &quot;connection&quot;;

  // Actor management
  actorMethod: &quot;get&quot; | &quot;getOrCreate&quot; | &quot;getForId&quot; | &quot;create&quot;;
  actorName: string;
  actorKey: string;
  actorId: string;
  actorRegion: string;
  createInput: string;

  // Connection state
  isConnected: boolean;
  connectionError?: string;
}

function App() {
  const [state, setState] = useState&lt;AppState | null&gt;(null);

  const handleConnect = (config: AppState) =&gt; {
    setState(config);
  };

  const handleDisconnect = () =&gt; {
    setState(null);
  };

  const updateState = (updates: Partial&lt;AppState&gt;) =&gt; {
    setState(prev =&gt; prev ? { ...prev, ...updates } : null);
  };

  // Create client with user-selected encoding and transport
  const client = useMemo(() =&gt; {
    if (!state) return null;

    return createClient&lt;Registry&gt;({
      endpoint: &quot;http://localhost:6420&quot;,
      encoding: state.encoding,
      transport: state.transport,
    });
  }, [state?.encoding, state?.transport]);

  // Create the connection/handle once based on state
  const [actorHandle, setActorHandle] = useState&lt;any&gt;(null);

  useEffect(() =&gt; {
    if (!state || !client) {
      setActorHandle(null);
      return;
    }

    const accessor = (client as any)[state.actorName];
    const key = state.actorKey ? [state.actorKey] : [];

    const initHandle = async () =&gt; {
      let baseHandle: any;
      switch (state.actorMethod) {
        case &quot;get&quot;:
          baseHandle = accessor.get(key);
          break;
        case &quot;getOrCreate&quot;: {
          const createInput = state.createInput ? JSON.parse(state.createInput) : undefined;
          baseHandle = accessor.getOrCreate(key, { createWithInput: createInput });
          break;
        }
        case &quot;getForId&quot;:
          if (!state.actorId) {
            throw new Error(&quot;Actor ID is required for getForId method&quot;);
          }
          baseHandle = accessor.getForId(state.actorId);
          break;
        case &quot;create&quot;: {
          const createInput = state.createInput ? JSON.parse(state.createInput) : undefined;
          baseHandle = await accessor.create(key, { input: createInput });
          break;
        }
        default:
          throw new Error(`Unknown actor method: ${state.actorMethod}`);
      }

      // Apply connection mode
      const handle = state.connectionMode === &quot;connection&quot;
        ? baseHandle.connect()
        : baseHandle;

      setActorHandle(handle);
    };

    initHandle();
  }, [state, client]);

  return (
    &lt;div className=&quot;app&quot;&gt;
      &lt;ConnectionScreen onConnect={handleConnect} /&gt;
      {state &amp;&amp; actorHandle &amp;&amp; (
        &lt;InteractionScreen
          state={state}
          updateState={updateState}
          client={client}
          actorHandle={actorHandle}
          onDisconnect={handleDisconnect}
        /&gt;
      )}
    &lt;/div&gt;
  );
}

export default App;
">
<input type="hidden" name="project[files][src/frontend/index.css]" value="* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, &#39;Inter&#39;, Roboto, sans-serif;
  background: #000000;
  color: #ffffff;
  height: 100vh;
  overflow: hidden;
  margin: 0;
  padding: 0;
}

:root {
  --primary-color: #007aff;
  --primary-hover: #0056cc;
  --primary-light: rgba(0, 122, 255, 0.1);
  --success-color: #30d158;
  --success-light: rgba(48, 209, 88, 0.1);
  --danger-color: #ff3b30;
  --danger-light: rgba(255, 59, 48, 0.1);
  --warning-color: #ff9500;
  --warning-light: rgba(255, 149, 0, 0.1);

  --bg-primary: #000000;
  --bg-secondary: #1c1c1e;
  --bg-tertiary: #2c2c2e;
  --bg-quaternary: #3a3a3c;

  --text-primary: #ffffff;
  --text-secondary: #8e8e93;
  --text-tertiary: #64748b;

  --border-primary: #2c2c2e;
  --border-secondary: #3a3a3c;
  --border-tertiary: #48484a;

  --border-radius: 8px;
  --border-radius-sm: 6px;
  --border-radius-lg: 12px;
  --border-radius-xl: 16px;

  --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
  --shadow: 0 2px 10px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 4px 20px rgba(0, 0, 0, 0.5);
}

.app {
  display: flex;
  height: 100vh;
  width: 100vw;
  background: var(--bg-primary);
  overflow: hidden;
}

/* Legacy header styles removed - now using ConnectionScreen and InteractionScreen */

.form-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 20px;
}

.form-group label {
  font-weight: 600;
  color: var(--text-secondary);
  font-size: 14px;
  display: block;
  margin-bottom: 8px;
}

.form-control {
  width: 100%;
  padding: 10px 14px;
  border: 1px solid var(--border-secondary);
  border-radius: var(--border-radius);
  font-size: 14px;
  transition: all 0.2s ease;
  background: var(--bg-tertiary);
  color: var(--text-primary);
}

.form-control:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 3px var(--primary-light);
}

.form-control:hover {
  border-color: var(--border-tertiary);
}

.form-control::placeholder {
  color: var(--text-tertiary);
  opacity: 0.6;
}

.toggle {
  display: flex;
  background: var(--bg-tertiary);
  border-radius: var(--border-radius);
  overflow: hidden;
  border: 1px solid var(--border-secondary);
  box-shadow: var(--shadow-sm);
}

.toggle button {
  padding: 8px 16px;
  border: none;
  background: transparent;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  transition: all 0.2s ease;
  color: var(--text-secondary);
}

.toggle button:hover:not(.active) {
  background: var(--bg-quaternary);
  color: var(--text-primary);
}

.toggle button.active {
  background: var(--primary-color);
  color: white;
}

/* New toggle group styling */
.toggle-group {
  display: flex;
  background: var(--bg-tertiary);
  border-radius: var(--border-radius);
  overflow: hidden;
  border: 1px solid var(--border-secondary);
  padding: 2px;
  gap: 2px;
}

.toggle-button {
  flex: 1;
  padding: 8px 12px;
  border: none;
  background: transparent;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  transition: all 0.2s ease;
  color: var(--text-secondary);
  border-radius: calc(var(--border-radius) - 2px);
  text-align: center;
}

.toggle-button:hover:not(.active) {
  background: var(--bg-quaternary);
  color: var(--text-primary);
}

.toggle-button.active {
  background: var(--primary-color);
  color: white;
  box-shadow: 0 1px 3px rgba(0, 122, 255, 0.3);
}

.btn {
  padding: 10px 20px;
  border: 1px solid var(--border-secondary);
  border-radius: var(--border-radius);
  background: var(--bg-tertiary);
  color: var(--text-primary);
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  transition: all 0.2s ease;
  box-shadow: var(--shadow-sm);
}

.btn:hover {
  background: var(--bg-quaternary);
  border-color: var(--border-tertiary);
  transform: translateY(-1px);
  box-shadow: var(--shadow);
}

.btn-primary {
  background: var(--primary-color);
  color: white;
  border-color: var(--primary-color);
  font-weight: 500;
}

.btn-primary:hover {
  background: var(--primary-hover);
  border-color: var(--primary-hover);
  transform: translateY(-1px);
}

.btn-danger {
  background: var(--danger-color);
  color: white;
  border-color: var(--danger-color);
  font-weight: 500;
}

.btn-danger:hover {
  background: #c82333;
  border-color: #c82333;
  transform: translateY(-1px);
}

.btn-secondary {
  background: var(--bg-tertiary);
  color: var(--text-primary);
  border-color: var(--border-secondary);
  font-weight: 500;
}

.btn-secondary:hover {
  background: var(--bg-quaternary);
  border-color: var(--border-tertiary);
  transform: translateY(-1px);
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
  background: var(--bg-quaternary);
  border-color: var(--border-secondary);
  color: var(--text-secondary);
}

.status {
  padding: 10px;
  border-radius: 4px;
  font-size: 14px;
  margin-top: 10px;
}

.status.connected {
  background: var(--success-light);
  color: #155724;
  border: 1px solid #c3e6cb;
  border-left: 4px solid var(--success-color);
}

.status.disconnected {
  background: var(--danger-light);
  color: #721c24;
  border: 1px solid #f5c6cb;
  border-left: 4px solid var(--danger-color);
}

.tabs {
  background: var(--bg-secondary);
  border-radius: 0 0 var(--border-radius) var(--border-radius);
  box-shadow: var(--shadow);
  border: 1px solid var(--border-primary);
  overflow: hidden;
  flex: 1;
  display: flex;
  flex-direction: column;
}

.tab-list {
  display: flex;
  border-bottom: 1px solid var(--border-primary);
  overflow-x: auto;
  background: var(--bg-secondary);
  flex-shrink: 0;
}

.tab-button {
  padding: 16px 24px;
  border: none;
  background: none;
  cursor: pointer;
  font-size: 14px;
  font-weight: 500;
  border-bottom: 3px solid transparent;
  transition: all 0.2s ease;
  white-space: nowrap;
  color: var(--text-secondary);
}

.tab-button:hover {
  background: var(--bg-tertiary);
  color: var(--text-primary);
}

.tab-button.active {
  border-bottom-color: var(--primary-color);
  color: var(--primary-color);
  background: var(--primary-light);
}

.tab-button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.tab-content {
  padding: 20px;
  background: var(--bg-primary);
  flex: 1;
  overflow-y: auto;
}

.section {
  margin-bottom: 32px;
  padding: 20px;
  background: var(--bg-secondary);
  border-radius: var(--border-radius);
  border: 1px solid var(--border-primary);
  box-shadow: var(--shadow-sm);
}

.section:last-child {
  margin-bottom: 0;
}

.section h3 {
  margin: -20px -20px 20px -20px;
  padding: 16px 20px;
  color: var(--text-primary);
  font-size: 18px;
  font-weight: 600;
  background: var(--bg-tertiary);
  border-bottom: 1px solid var(--border-primary);
  border-radius: var(--border-radius) var(--border-radius) 0 0;
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 15px;
  margin-bottom: 15px;
}

.textarea {
  min-height: 80px;
  resize: vertical;
  font-family: &#39;SF Mono&#39;, &#39;Monaco&#39;, &#39;Inconsolata&#39;, &#39;Roboto Mono&#39;, monospace;
  line-height: 1.5;
}

.response {
  background: var(--bg-tertiary);
  border: 1px solid var(--border-secondary);
  border-radius: var(--border-radius);
  padding: 16px;
  margin-top: 16px;
  font-family: &#39;SF Mono&#39;, &#39;Monaco&#39;, &#39;Inconsolata&#39;, &#39;Roboto Mono&#39;, monospace;
  font-size: 13px;
  line-height: 1.5;
  white-space: pre-wrap;
  max-height: 300px;
  overflow-y: auto;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.4);
  color: var(--text-primary);
}

.event-list {
  max-height: 400px;
  overflow-y: auto;
  border: 1px solid var(--border-secondary);
  border-radius: var(--border-radius);
  background: var(--bg-tertiary);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.4);
}

.event-item {
  padding: 12px 16px;
  border-bottom: 1px solid var(--border-primary);
  font-family: &#39;SF Mono&#39;, &#39;Monaco&#39;, &#39;Inconsolata&#39;, &#39;Roboto Mono&#39;, monospace;
  font-size: 13px;
  line-height: 1.4;
  transition: background-color 0.2s ease;
  color: var(--text-primary);
}

.event-item:last-child {
  border-bottom: none;
}

.event-item:hover {
  background: var(--bg-secondary);
}

.event-item .timestamp {
  color: var(--text-secondary);
  margin-right: 12px;
  font-size: 12px;
}

.event-item .name {
  color: var(--primary-color);
  font-weight: 600;
  margin-right: 12px;
}

.event-item .name.sent {
  color: var(--success-color);
}

.event-item .name.received {
  color: var(--primary-color);
}

.disabled-content {
  opacity: 0.5;
  pointer-events: none;
  filter: grayscale(20%);
}

/* Modern scrollbar styling */
::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background: var(--bg-quaternary);
  border-radius: 3px;
  transition: background 0.2s ease;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--border-tertiary);
}

/* Modern focus ring */
*:focus-visible {
  outline: 2px solid var(--primary-color);
  outline-offset: 2px;
}

/* Mobile responsive adjustments */
@media (max-width: 768px) {
  .app {
    padding: 16px;
  }

  .form-group {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
  }

  .form-group label {
    min-width: auto;
  }

  .tab-list {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  .section {
    padding: 16px;
    margin-bottom: 24px;
  }

  .section h3 {
    margin: -16px -16px 16px -16px;
    padding: 12px 16px;
  }
}

/* Enhanced form controls and visual elements */
select.form-control {
  background-image: url(&quot;data:image/svg+xml,%3csvg xmlns=&#39;http://www.w3.org/2000/svg&#39; fill=&#39;none&#39; viewBox=&#39;0 0 20 20&#39;%3e%3cpath stroke=&#39;%236b7280&#39; stroke-linecap=&#39;round&#39; stroke-linejoin=&#39;round&#39; stroke-width=&#39;1.5&#39; d=&#39;m6 8 4 4 4-4&#39;/%3e%3c/svg%3e&quot;);
  background-position: right 8px center;
  background-repeat: no-repeat;
  background-size: 16px;
  padding-right: 40px;
  appearance: none;
}

/* Card-like sections with better visual hierarchy */
.section:first-child {
  margin-top: 0;
}

/* Enhanced status indicators */
.status {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border-radius: var(--border-radius-sm);
  font-size: 14px;
  font-weight: 500;
  margin-top: 12px;
}

.status.connected::before {
  content: &#39;🟢&#39;;
  font-size: 12px;
}

.status.disconnected::before {
  content: &#39;🔴&#39;;
  font-size: 12px;
}

/* Modern toggle enhancements */
.toggle button.active {
  background: var(--primary-color);
  color: white;
  box-shadow: 0 2px 4px rgba(0, 123, 255, 0.3);
}

.toggle button:hover:not(.active) {
  background: var(--bg-quaternary);
  color: var(--text-primary);
}

/* Enhanced buttons */
.btn-primary:active {
  transform: translateY(0);
  box-shadow: 0 1px 3px rgba(0, 123, 255, 0.3);
}

.btn-danger:active {
  transform: translateY(0);
  box-shadow: 0 1px 3px rgba(220, 53, 69, 0.3);
}

/* Code and monospace improvements */
code {
  background: var(--bg-tertiary);
  color: var(--text-primary);
  padding: 2px 6px;
  border-radius: 3px;
  font-family: &#39;SF Mono&#39;, &#39;Monaco&#39;, &#39;Inconsolata&#39;, &#39;Roboto Mono&#39;, monospace;
  font-size: 0.9em;
}

pre {
  background: var(--bg-tertiary);
  border: 1px solid var(--border-secondary);
  border-radius: var(--border-radius-sm);
  padding: 16px;
  overflow-x: auto;
  font-family: &#39;SF Mono&#39;, &#39;Monaco&#39;, &#39;Inconsolata&#39;, &#39;Roboto Mono&#39;, monospace;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-primary);
}

/* Loading states */
.btn[aria-busy=&quot;true&quot;] {
  position: relative;
  color: transparent;
}

.btn[aria-busy=&quot;true&quot;]::after {
  content: &#39;&#39;;
  position: absolute;
  width: 16px;
  height: 16px;
  top: 50%;
  left: 50%;
  margin-left: -8px;
  margin-top: -8px;
  border: 2px solid transparent;
  border-top: 2px solid white;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* Form grid improvements */
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 20px;
  margin-bottom: 20px;
}

@media (max-width: 768px) {
  .form-grid {
    grid-template-columns: 1fr;
    gap: 16px;
  }
}

/* Connection Screen Styles */
.connection-screen {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  width: 100%;
  height: 100%;
}

.connection-modal {
  max-width: 500px;
  width: 100%;
  background: var(--bg-secondary);
  border-radius: var(--border-radius-lg);
  box-shadow: var(--shadow-lg);
  border: 1px solid var(--border-primary);
  overflow: hidden;
  max-height: 90vh;
  overflow-y: auto;
}

.modal-header {
  padding: 24px 24px 0 24px;
  text-align: center;
}

.modal-header h1 {
  margin: 0 0 8px 0;
  font-size: 24px;
  font-weight: 600;
  color: var(--text-primary);
}

.modal-header p {
  margin: 0 0 24px 0;
  font-size: 14px;
  color: var(--text-secondary);
}

.modal-content {
  padding: 0 24px 24px 24px;
}

.method-toggle {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}

.method-toggle .toggle-button {
  font-size: 13px;
  padding: 8px 8px;
  white-space: nowrap;
}

.form-section {
  margin-bottom: 24px;
}

.form-section:last-child {
  margin-bottom: 0;
}

.form-section h3 {
  margin: 0 0 16px 0;
  color: var(--text-primary);
  font-size: 16px;
  font-weight: 600;
  border-bottom: 1px solid var(--border-primary);
  padding-bottom: 8px;
}

.modal-actions {
  text-align: center;
  padding-top: 24px;
  border-top: 1px solid var(--border-primary);
}

.connect-button {
  font-size: 16px;
  padding: 12px 32px;
  font-weight: 600;
  min-width: 160px;
}

.actor-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.actor-actions .btn {
  padding: 10px 16px;
  font-size: 13px;
  font-weight: 500;
}

/* Interaction Modal Styles */
.interaction-modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: rgba(0, 0, 0, 0.8);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  z-index: 2000;
}

.interaction-modal {
  display: flex;
  flex-direction: column;
  width: 90vw;
  max-width: 1400px;
  height: 85vh;
  background: var(--bg-primary);
  border-radius: var(--border-radius-lg);
  box-shadow: var(--shadow-lg);
  border: 1px solid var(--border-primary);
  overflow: hidden;
}

.interaction-header {
  background: var(--bg-secondary);
  padding: 20px;
  border-bottom: 1px solid var(--border-primary);
  flex-shrink: 0;
}

.connection-info {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
}

.connection-details h2 {
  margin: 0 0 8px 0;
  color: var(--text-primary);
  font-size: 24px;
  font-weight: 600;
}

.connection-meta {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.actor-name {
  background: var(--primary-color);
  color: white;
  padding: 4px 12px;
  border-radius: 20px;
  font-weight: 500;
  font-size: 14px;
}

.actor-key {
  background: var(--bg-tertiary);
  color: var(--text-secondary);
  padding: 4px 10px;
  border-radius: 16px;
  font-family: &#39;SF Mono&#39;, &#39;Monaco&#39;, &#39;Inconsolata&#39;, &#39;Roboto Mono&#39;, monospace;
  font-size: 13px;
}

.transport-info {
  background: var(--success-light);
  color: var(--success-color);
  padding: 4px 10px;
  border-radius: 16px;
  font-weight: 500;
  font-size: 12px;
  text-transform: uppercase;
}

.mode-info {
  padding: 4px 10px;
  border-radius: 16px;
  font-weight: 500;
  font-size: 13px;
}

.mode-info.connection {
  background: var(--primary-light);
  color: var(--primary-color);
}

.mode-info.handle {
  background: var(--warning-light);
  color: var(--warning-color);
}

.connection-actions {
  display: flex;
  gap: 12px;
  align-items: center;
}

/* Mobile responsive adjustments for new screens */
@media (max-width: 768px) {
  .connection-screen {
    padding: 16px;
  }

  .connection-modal {
    max-width: none;
    width: 100%;
    max-height: 95vh;
  }

  .modal-header {
    padding: 20px 20px 0 20px;
  }

  .modal-header h1 {
    font-size: 20px;
  }

  .modal-content {
    padding: 0 20px 20px 20px;
  }

  .form-section {
    margin-bottom: 20px;
  }

  .actor-actions {
    grid-template-columns: 1fr;
    gap: 8px;
  }

  .method-toggle {
    grid-template-columns: repeat(2, 1fr);
    gap: 4px;
  }

  .method-toggle .toggle-button {
    font-size: 12px;
    padding: 8px 6px;
  }

  .connection-info {
    flex-direction: column;
    align-items: stretch;
    gap: 16px;
  }

  .connection-actions {
    justify-content: center;
  }

  .connection-meta {
    justify-content: center;
  }

  .interaction-modal-overlay {
    padding: 16px;
  }

  .interaction-modal {
    width: 100%;
    height: 90vh;
    max-width: none;
  }
}">
<input type="hidden" name="project[files][src/frontend/main.tsx]" value="import React from &#39;react&#39;
import ReactDOM from &#39;react-dom/client&#39;
import App from &#39;./App.tsx&#39;
import &#39;./index.css&#39;

ReactDOM.createRoot(document.getElementById(&#39;root&#39;)!).render(
  &lt;React.StrictMode&gt;
    &lt;App /&gt;
  &lt;/React.StrictMode&gt;,
)">
<input type="hidden" name="project[files][src/backend/actors/demo.ts]" value="import { actor } from &quot;rivetkit&quot;;
import { handleHttpRequest, httpActions } from &quot;./http&quot;;
import { handleWebSocket, websocketActions } from &quot;./websocket&quot;;

export const demo = actor({
	createState: (_c, input) =&gt; ({
		input,
		count: 0,
		lastMessage: &quot;&quot;,
		alarmHistory: [] as { id: string; time: number; data?: any }[],
		startCount: 0,
		stopCount: 0,
	}),
	connState: {
		connectionTime: 0,
	},
	onWake: (c) =&gt; {
		c.state.startCount += 1;
		c.log.info({
			msg: &quot;demo actor started&quot;,
			startCount: c.state.startCount,
		});
	},
	onSleep: (c) =&gt; {
		c.state.stopCount += 1;
		c.log.info({ msg: &quot;demo actor stopped&quot;, stopCount: c.state.stopCount });
	},
	onConnect: (c, conn) =&gt; {
		conn.state.connectionTime = Date.now();
		c.log.info({
			msg: &quot;client connected&quot;,
			connectionTime: conn.state.connectionTime,
		});
	},
	onDisconnect: (c) =&gt; {
		c.log.info(&quot;client disconnected&quot;);
	},
	onFetch: handleHttpRequest,
	onWebSocket: handleWebSocket,
	actions: {
		// Sync actions
		increment: (c, amount: number = 1) =&gt; {
			c.state.count += amount;
			c.broadcast(&quot;countChanged&quot;, { count: c.state.count, amount });
			return c.state.count;
		},
		getCount: (c) =&gt; {
			return c.state.count;
		},
		setMessage: (c, message: string) =&gt; {
			c.state.lastMessage = message;
			c.broadcast(&quot;messageChanged&quot;, { message });
			return message;
		},

		// Async actions
		delayedIncrement: async (
			c,
			amount: number = 1,
			delayMs: number = 1000,
		) =&gt; {
			await new Promise((resolve) =&gt; setTimeout(resolve, delayMs));
			c.state.count += amount;
			c.broadcast(&quot;countChanged&quot;, { count: c.state.count, amount });
			return c.state.count;
		},

		// Promise action
		promiseAction: () =&gt; {
			return Promise.resolve({
				timestamp: Date.now(),
				message: &quot;promise resolved&quot;,
			});
		},

		// State management
		getState: (c) =&gt; {
			return {
				actorState: c.state,
				connectionState: c.conn.state,
			};
		},

		// Scheduling
		scheduleAlarmAt: (c, timestamp: number, data?: any) =&gt; {
			const id = `alarm-${Date.now()}`;
			c.schedule.at(timestamp, &quot;onAlarm&quot;, { id, data });
			return { id, scheduledFor: timestamp };
		},
		scheduleAlarmAfter: (c, delayMs: number, data?: any) =&gt; {
			const id = `alarm-${Date.now()}`;
			c.schedule.after(delayMs, &quot;onAlarm&quot;, { id, data });
			return { id, scheduledFor: Date.now() + delayMs };
		},
		onAlarm: (c, payload: { id: string; data?: any }) =&gt; {
			const alarmEntry = { ...payload, time: Date.now() };
			c.state.alarmHistory.push(alarmEntry);
			c.broadcast(&quot;alarmTriggered&quot;, alarmEntry);
			c.log.info({ msg: &quot;alarm triggered&quot;, ...alarmEntry });
		},
		getAlarmHistory: (c) =&gt; {
			return c.state.alarmHistory;
		},
		clearAlarmHistory: (c) =&gt; {
			c.state.alarmHistory = [];
			return true;
		},

		// Sleep
		triggerSleep: (c) =&gt; {
			c.sleep();
			return &quot;sleep triggered&quot;;
		},

		// Lifecycle info
		getLifecycleInfo: (c) =&gt; {
			return {
				startCount: c.state.startCount,
				stopCount: c.state.stopCount,
			};
		},

		// Metadata
		getMetadata: (c) =&gt; {
			return {
				name: c.name,
			};
		},
		getInput: (c) =&gt; {
			return c.state.input;
		},
		getActorState: (c) =&gt; {
			return c.state;
		},
		getConnState: (c) =&gt; {
			return c.conn.state;
		},

		// Events
		broadcastCustomEvent: (c, eventName: string, data: any) =&gt; {
			c.broadcast(eventName, data);
			return { eventName, data, timestamp: Date.now() };
		},

		// Connections
		listConnections: (c) =&gt; {
			return Array.from(c.conns.values()).map((conn) =&gt; ({
				id: conn.id,
				connectedAt: conn.state.connectionTime,
			}));
		},

		// HTTP actions
		...httpActions,

		// WebSocket actions
		...websocketActions,
	},
	options: {
		sleepTimeout: 5_000,
	},
});
">
<input type="hidden" name="project[files][src/backend/actors/http.ts]" value="import type { ActorContext } from &quot;rivetkit&quot;;

export function handleHttpRequest(
	c: ActorContext&lt;any, any, any, any, any, any&gt;,
	request: Request,
) {
	const url = new URL(request.url);
	const method = request.method;
	const path = url.pathname;

	// Track request
	if (!c.state.requestCount) c.state.requestCount = 0;
	if (!c.state.requestHistory) c.state.requestHistory = [];

	c.state.requestCount++;
	c.state.requestHistory.push({
		method,
		path,
		timestamp: Date.now(),
		headers: Object.fromEntries(request.headers.entries()),
	});

	c.log.info({
		msg: &quot;http request received&quot;,
		method,
		path,
		fullUrl: request.url,
		requestCount: c.state.requestCount,
	});

	// Handle different endpoints
	if (path === &quot;/api/hello&quot;) {
		return new Response(
			JSON.stringify({
				message: &quot;Hello from HTTP actor!&quot;,
				timestamp: Date.now(),
				requestCount: c.state.requestCount,
			}),
			{
				headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
			},
		);
	}

	if (path === &quot;/api/echo&quot; &amp;&amp; method === &quot;POST&quot;) {
		return new Response(request.body, {
			headers: {
				&quot;Content-Type&quot;:
					request.headers.get(&quot;Content-Type&quot;) || &quot;text/plain&quot;,
				&quot;X-Echo-Timestamp&quot;: Date.now().toString(),
			},
		});
	}

	if (path === &quot;/api/stats&quot;) {
		return new Response(
			JSON.stringify({
				requestCount: c.state.requestCount,
				requestHistory: c.state.requestHistory.slice(-10), // Last 10 requests
			}),
			{
				headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
			},
		);
	}

	if (path === &quot;/api/headers&quot;) {
		const headers = Object.fromEntries(request.headers.entries());
		return new Response(
			JSON.stringify({
				headers,
				method,
				path,
				timestamp: Date.now(),
			}),
			{
				headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
			},
		);
	}

	if (path === &quot;/api/json&quot; &amp;&amp; method === &quot;POST&quot;) {
		return request.json().then((body) =&gt; {
			return new Response(
				JSON.stringify({
					received: body,
					method,
					timestamp: Date.now(),
					processed: true,
				}),
				{
					headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
				},
			);
		});
	}

	// Handle custom paths with query parameters
	if (path.startsWith(&quot;/api/custom&quot;)) {
		const searchParams = Object.fromEntries(url.searchParams.entries());
		return new Response(
			JSON.stringify({
				path,
				method,
				queryParams: searchParams,
				timestamp: Date.now(),
				message: &quot;Custom endpoint response&quot;,
			}),
			{
				headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
			},
		);
	}

	// Return 404 for unhandled paths
	return new Response(
		JSON.stringify({
			error: &quot;Not Found&quot;,
			path,
			method,
			availableEndpoints: [
				&quot;/api/hello&quot;,
				&quot;/api/echo&quot;,
				&quot;/api/stats&quot;,
				&quot;/api/headers&quot;,
				&quot;/api/json&quot;,
				&quot;/api/custom/*&quot;,
			],
		}),
		{
			status: 404,
			headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
		},
	);
}

export const httpActions = {
	getHttpStats: (c: any) =&gt; {
		return {
			requestCount: c.state.requestCount || 0,
			requestHistory: c.state.requestHistory || [],
		};
	},
	clearHttpHistory: (c: any) =&gt; {
		c.state.requestHistory = [];
		c.state.requestCount = 0;
		return true;
	},
};
">
<input type="hidden" name="project[files][src/backend/actors/websocket.ts]" value="import type { UniversalWebSocket } from &quot;rivetkit&quot;;

export function handleWebSocket(
	c: any,
	websocket: UniversalWebSocket,
	opts: any,
) {
	const connectionId = `conn-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;

	// Initialize WebSocket state if not exists
	if (!c.state.connectionCount) c.state.connectionCount = 0;
	if (!c.state.messageCount) c.state.messageCount = 0;
	if (!c.state.messageHistory) c.state.messageHistory = [];

	c.state.connectionCount++;
	c.log.info(&quot;websocket connected&quot;, {
		connectionCount: c.state.connectionCount,
		connectionId,
		url: opts.request.url,
	});

	// Send welcome message
	const welcomeMessage = JSON.stringify({
		type: &quot;welcome&quot;,
		connectionId,
		connectionCount: c.state.connectionCount,
		timestamp: Date.now(),
	});

	websocket.send(welcomeMessage);
	c.state.messageHistory.push({
		type: &quot;sent&quot;,
		data: welcomeMessage,
		timestamp: Date.now(),
		connectionId,
	});

	// Handle incoming messages
	websocket.addEventListener(&quot;message&quot;, (event: any) =&gt; {
		c.state.messageCount++;
		const timestamp = Date.now();

		c.log.info(&quot;websocket message received&quot;, {
			messageCount: c.state.messageCount,
			connectionId,
			dataType: typeof event.data,
		});

		// Record received message
		c.state.messageHistory.push({
			type: &quot;received&quot;,
			data: event.data,
			timestamp,
			connectionId,
		});

		const data = event.data;

		if (typeof data === &quot;string&quot;) {
			try {
				const parsed = JSON.parse(data);

				if (parsed.type === &quot;ping&quot;) {
					const pongMessage = JSON.stringify({
						type: &quot;pong&quot;,
						timestamp,
						originalTimestamp: parsed.timestamp,
					});
					websocket.send(pongMessage);
					c.state.messageHistory.push({
						type: &quot;sent&quot;,
						data: pongMessage,
						timestamp,
						connectionId,
					});
				} else if (parsed.type === &quot;echo&quot;) {
					const echoMessage = JSON.stringify({
						type: &quot;echo-response&quot;,
						originalMessage: parsed.message,
						timestamp,
					});
					websocket.send(echoMessage);
					c.state.messageHistory.push({
						type: &quot;sent&quot;,
						data: echoMessage,
						timestamp,
						connectionId,
					});
				} else if (parsed.type === &quot;getStats&quot;) {
					const statsMessage = JSON.stringify({
						type: &quot;stats&quot;,
						connectionCount: c.state.connectionCount,
						messageCount: c.state.messageCount,
						timestamp,
					});
					websocket.send(statsMessage);
					c.state.messageHistory.push({
						type: &quot;sent&quot;,
						data: statsMessage,
						timestamp,
						connectionId,
					});
				} else if (parsed.type === &quot;broadcast&quot;) {
					// Broadcast to all connections would need additional infrastructure
					const broadcastResponse = JSON.stringify({
						type: &quot;broadcast-ack&quot;,
						message: parsed.message,
						timestamp,
					});
					websocket.send(broadcastResponse);
					c.state.messageHistory.push({
						type: &quot;sent&quot;,
						data: broadcastResponse,
						timestamp,
						connectionId,
					});
				} else {
					// Echo back unknown JSON messages
					websocket.send(data);
					c.state.messageHistory.push({
						type: &quot;sent&quot;,
						data: data,
						timestamp,
						connectionId,
					});
				}
			} catch {
				// If not JSON, just echo it back
				websocket.send(data);
				c.state.messageHistory.push({
					type: &quot;sent&quot;,
					data: data,
					timestamp,
					connectionId,
				});
			}
		} else if (data instanceof ArrayBuffer || data instanceof Uint8Array) {
			// Handle binary data - reverse the bytes
			const bytes = new Uint8Array(data);
			const reversed = new Uint8Array(bytes.length);
			for (let i = 0; i &lt; bytes.length; i++) {
				reversed[i] = bytes[bytes.length - 1 - i];
			}
			websocket.send(reversed);
			c.state.messageHistory.push({
				type: &quot;sent&quot;,
				data: `[Binary: ${reversed.length} bytes - reversed]`,
				timestamp,
				connectionId,
			});
		} else {
			// Echo other data types
			websocket.send(data);
			c.state.messageHistory.push({
				type: &quot;sent&quot;,
				data: data,
				timestamp,
				connectionId,
			});
		}
	});

	// Handle connection close
	websocket.addEventListener(&quot;close&quot;, () =&gt; {
		c.state.connectionCount--;
		c.log.info(&quot;websocket disconnected&quot;, {
			connectionCount: c.state.connectionCount,
			connectionId,
		});
	});

	// Handle errors
	websocket.addEventListener(&quot;error&quot;, (error: any) =&gt; {
		c.log.error(&quot;websocket error&quot;, { error: error.message, connectionId });
	});
}

export const websocketActions = {
	getWebSocketStats: (c: any) =&gt; {
		return {
			connectionCount: c.state.connectionCount || 0,
			messageCount: c.state.messageCount || 0,
			messageHistory: (c.state.messageHistory || []).slice(-50), // Last 50 messages
		};
	},
	clearWebSocketHistory: (c: any) =&gt; {
		c.state.messageHistory = [];
		c.state.messageCount = 0;
		return true;
	},
	getWebSocketMessageHistory: (c: any, limit: number = 20) =&gt; {
		return (c.state.messageHistory || []).slice(-limit);
	},
};
">
<input type="hidden" name="project[files][src/frontend/components/ConnectionScreen.tsx]" value="import { useState } from &quot;react&quot;;
import type { AppState } from &quot;../App&quot;;

interface ConnectionScreenProps {
  onConnect: (config: AppState) =&gt; void;
}

type ActorMethod = &quot;get&quot; | &quot;getOrCreate&quot; | &quot;getForId&quot; | &quot;create&quot;;

export default function ConnectionScreen({ onConnect }: ConnectionScreenProps) {
  const [actorMethod, setActorMethod] = useState&lt;ActorMethod&gt;(&quot;getOrCreate&quot;);
  const [actorName, setActorName] = useState(&quot;demo&quot;);
  const [actorKey, setActorKey] = useState(&quot;&quot;);
  const [actorId, setActorId] = useState(&quot;&quot;);
  const [actorRegion, setActorRegion] = useState(&quot;&quot;);
  const [createInput, setCreateInput] = useState(&quot;&quot;);
  const [transport, setTransport] = useState&lt;&quot;websocket&quot; | &quot;sse&quot;&gt;(&quot;websocket&quot;);
  const [encoding, setEncoding] = useState&lt;&quot;json&quot; | &quot;cbor&quot; | &quot;bare&quot;&gt;(&quot;bare&quot;);
  const [connectionMode, setConnectionMode] = useState&lt;&quot;connection&quot; | &quot;handle&quot;&gt;(&quot;handle&quot;);
  const [isConnecting, setIsConnecting] = useState(false);

  const handleConnect = async () =&gt; {
    setIsConnecting(true);

    const config: AppState = {
      actorMethod,
      actorName,
      actorKey,
      actorId,
      actorRegion,
      createInput,
      transport,
      encoding,
      connectionMode,
      isConnected: true,
    };

    onConnect(config);
    setIsConnecting(false);
  };

  return (
    &lt;div className=&quot;connection-screen&quot;&gt;
      &lt;div className=&quot;connection-modal&quot;&gt;
        &lt;div className=&quot;modal-header&quot;&gt;
          &lt;h1&gt;Connect to Actor&lt;/h1&gt;
          &lt;p&gt;Configure your RivetKit connection&lt;/p&gt;
        &lt;/div&gt;

        &lt;div className=&quot;modal-content&quot;&gt;
          &lt;div className=&quot;form-section&quot;&gt;
            &lt;h3&gt;Actor Configuration&lt;/h3&gt;
            &lt;div className=&quot;form-group&quot;&gt;
              &lt;label&gt;Method&lt;/label&gt;
              &lt;div className=&quot;toggle-group method-toggle&quot;&gt;
                &lt;button
                  className={`toggle-button ${actorMethod === &quot;get&quot; ? &quot;active&quot; : &quot;&quot;}`}
                  onClick={() =&gt; setActorMethod(&quot;get&quot;)}
                &gt;
                  Get
                &lt;/button&gt;
                &lt;button
                  className={`toggle-button ${actorMethod === &quot;getOrCreate&quot; ? &quot;active&quot; : &quot;&quot;}`}
                  onClick={() =&gt; setActorMethod(&quot;getOrCreate&quot;)}
                &gt;
                  Get or Create
                &lt;/button&gt;
                &lt;button
                  className={`toggle-button ${actorMethod === &quot;getForId&quot; ? &quot;active&quot; : &quot;&quot;}`}
                  onClick={() =&gt; setActorMethod(&quot;getForId&quot;)}
                &gt;
                  Get for ID
                &lt;/button&gt;
                &lt;button
                  className={`toggle-button ${actorMethod === &quot;create&quot; ? &quot;active&quot; : &quot;&quot;}`}
                  onClick={() =&gt; setActorMethod(&quot;create&quot;)}
                &gt;
                  Create
                &lt;/button&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;div className=&quot;form-group&quot;&gt;
              &lt;label&gt;Actor Name&lt;/label&gt;
              &lt;select
                className=&quot;form-control&quot;
                value={actorName}
                onChange={(e) =&gt; setActorName(e.target.value)}
              &gt;
                &lt;option value=&quot;demo&quot;&gt;Demo Actor&lt;/option&gt;
              &lt;/select&gt;
            &lt;/div&gt;

            {actorMethod === &quot;getForId&quot; ? (
              &lt;div className=&quot;form-group&quot;&gt;
                &lt;label&gt;Actor ID&lt;/label&gt;
                &lt;input
                  className=&quot;form-control&quot;
                  type=&quot;text&quot;
                  value={actorId}
                  onChange={(e) =&gt; setActorId(e.target.value)}
                  placeholder=&quot;Actor ID&quot;
                  required
                /&gt;
              &lt;/div&gt;
            ) : (
              &lt;div className=&quot;form-group&quot;&gt;
                &lt;label&gt;Key&lt;/label&gt;
                &lt;input
                  className=&quot;form-control&quot;
                  type=&quot;text&quot;
                  value={actorKey}
                  onChange={(e) =&gt; setActorKey(e.target.value)}
                  placeholder=&quot;Optional key for actor instance&quot;
                /&gt;
              &lt;/div&gt;
            )}

            {(actorMethod === &quot;create&quot; || actorMethod === &quot;getOrCreate&quot;) &amp;&amp; (
              &lt;&gt;
                &lt;div className=&quot;form-group&quot;&gt;
                  &lt;label&gt;Region&lt;/label&gt;
                  &lt;input
                    className=&quot;form-control&quot;
                    type=&quot;text&quot;
                    value={actorRegion}
                    onChange={(e) =&gt; setActorRegion(e.target.value)}
                    placeholder=&quot;Optional region&quot;
                  /&gt;
                &lt;/div&gt;
                &lt;div className=&quot;form-group&quot;&gt;
                  &lt;label&gt;Input Data&lt;/label&gt;
                  &lt;textarea
                    className=&quot;form-control textarea&quot;
                    value={createInput}
                    onChange={(e) =&gt; setCreateInput(e.target.value)}
                    placeholder=&quot;Optional JSON input data&quot;
                    rows={3}
                  /&gt;
                &lt;/div&gt;
              &lt;/&gt;
            )}
          &lt;/div&gt;

          &lt;div className=&quot;form-section&quot;&gt;
            &lt;h3&gt;Connection Settings&lt;/h3&gt;
            &lt;div className=&quot;form-group&quot;&gt;
              &lt;label&gt;Mode&lt;/label&gt;
              &lt;div className=&quot;toggle-group&quot;&gt;
                &lt;button
                  className={`toggle-button ${connectionMode === &quot;handle&quot; ? &quot;active&quot; : &quot;&quot;}`}
                  onClick={() =&gt; setConnectionMode(&quot;handle&quot;)}
                &gt;
                  Handle
                &lt;/button&gt;
                &lt;button
                  className={`toggle-button ${connectionMode === &quot;connection&quot; ? &quot;active&quot; : &quot;&quot;}`}
                  onClick={() =&gt; setConnectionMode(&quot;connection&quot;)}
                &gt;
                  Connection
                &lt;/button&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            {connectionMode === &quot;connection&quot; &amp;&amp; (
              &lt;div className=&quot;form-group&quot;&gt;
                &lt;label&gt;Transport&lt;/label&gt;
                &lt;div className=&quot;toggle-group&quot;&gt;
                  &lt;button
                    className={`toggle-button ${transport === &quot;websocket&quot; ? &quot;active&quot; : &quot;&quot;}`}
                    onClick={() =&gt; setTransport(&quot;websocket&quot;)}
                  &gt;
                    WebSocket
                  &lt;/button&gt;
                  &lt;button
                    className={`toggle-button ${transport === &quot;sse&quot; ? &quot;active&quot; : &quot;&quot;}`}
                    onClick={() =&gt; setTransport(&quot;sse&quot;)}
                  &gt;
                    SSE
                  &lt;/button&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            )}

            &lt;div className=&quot;form-group&quot;&gt;
              &lt;label&gt;Encoding&lt;/label&gt;
              &lt;div className=&quot;toggle-group&quot;&gt;
                &lt;button
                  className={`toggle-button ${encoding === &quot;bare&quot; ? &quot;active&quot; : &quot;&quot;}`}
                  onClick={() =&gt; setEncoding(&quot;bare&quot;)}
                &gt;
                  Bare
                &lt;/button&gt;
                &lt;button
                  className={`toggle-button ${encoding === &quot;cbor&quot; ? &quot;active&quot; : &quot;&quot;}`}
                  onClick={() =&gt; setEncoding(&quot;cbor&quot;)}
                &gt;
                  CBOR
                &lt;/button&gt;
                &lt;button
                  className={`toggle-button ${encoding === &quot;json&quot; ? &quot;active&quot; : &quot;&quot;}`}
                  onClick={() =&gt; setEncoding(&quot;json&quot;)}
                &gt;
                  JSON
                &lt;/button&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;


          &lt;div className=&quot;modal-actions&quot;&gt;
            &lt;button
              className=&quot;btn btn-primary connect-button&quot;
              onClick={handleConnect}
              disabled={isConnecting}
              aria-busy={isConnecting ? &quot;true&quot; : &quot;false&quot;}
            &gt;
              {isConnecting ? &quot;Connecting...&quot; : &quot;Connect to Actor&quot;}
            &lt;/button&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
">
<input type="hidden" name="project[files][src/frontend/components/InteractionScreen.tsx]" value="import { useState } from &quot;react&quot;;
import type { AppState } from &quot;../App&quot;;
import ActionsTab from &quot;./tabs/ActionsTab&quot;;
import EventsTab from &quot;./tabs/EventsTab&quot;;
import ScheduleTab from &quot;./tabs/ScheduleTab&quot;;
import SleepTab from &quot;./tabs/SleepTab&quot;;
import RawHttpTab from &quot;./tabs/RawHttpTab&quot;;
import RawWebSocketTab from &quot;./tabs/RawWebSocketTab&quot;;
import MetadataTab from &quot;./tabs/MetadataTab&quot;;
import ConnectionsTab from &quot;./tabs/ConnectionsTab&quot;;

interface InteractionScreenProps {
  state: AppState;
  updateState: (updates: Partial&lt;AppState&gt;) =&gt; void;
  client: any;
  actorHandle: any;
  onDisconnect: () =&gt; void;
}

export interface EventSubscription {
  eventName: string;
  unsubscribe: () =&gt; void;
}

export interface EventItem {
  timestamp: number;
  name: string;
  data: any;
  id: string;
}

type TabType = &quot;actions&quot; | &quot;events&quot; | &quot;connections&quot; | &quot;schedule&quot; | &quot;sleep&quot; | &quot;raw-http&quot; | &quot;raw-websocket&quot; | &quot;metadata&quot;;

export default function InteractionScreen({
  state,
  updateState,
  client,
  actorHandle,
  onDisconnect
}: InteractionScreenProps) {
  const [activeTab, setActiveTab] = useState&lt;TabType&gt;(&quot;actions&quot;);
  const [isDisconnecting, setIsDisconnecting] = useState(false);
  const [eventSubscriptions, setEventSubscriptions] = useState&lt;Map&lt;string, EventSubscription&gt;&gt;(new Map());
  const [events, setEvents] = useState&lt;EventItem[]&gt;([]);

  // Switch to an enabled tab if current tab becomes disabled
  const isCurrentTabDisabled = activeTab === &quot;events&quot; &amp;&amp; state.connectionMode !== &quot;connection&quot;;
  if (isCurrentTabDisabled) {
    setActiveTab(&quot;actions&quot;);
  }

  const handleDisconnect = async () =&gt; {
    setIsDisconnecting(true);

    onDisconnect();
    setIsDisconnecting(false);
  };

  const [isDisposing, setIsDisposing] = useState(false);

  const handleDispose = async () =&gt; {
    setIsDisposing(true);

    try {
      const actorName = state.actorName as keyof typeof client;
      const accessor = client[actorName];
      const key = state.actorKey ? [state.actorKey] : [];
      await accessor.get(key).dispose();

      // After disposal, go back to connection screen
      onDisconnect();
    } catch (error) {
      console.error(&quot;Failed to dispose actor:&quot;, error);
      alert(&quot;Failed to dispose actor. See console for details.&quot;);
    } finally {
      setIsDisposing(false);
    }
  };

  const tabs = [
    { id: &quot;actions&quot; as const, label: &quot;Actions&quot;, component: ActionsTab, disabled: false },
    { id: &quot;events&quot; as const, label: &quot;Events&quot;, component: EventsTab, disabled: state.connectionMode !== &quot;connection&quot; },
    { id: &quot;connections&quot; as const, label: &quot;Connections&quot;, component: ConnectionsTab, disabled: false },
    { id: &quot;schedule&quot; as const, label: &quot;Schedule&quot;, component: ScheduleTab, disabled: false },
    { id: &quot;sleep&quot; as const, label: &quot;Sleep&quot;, component: SleepTab, disabled: false },
    { id: &quot;raw-http&quot; as const, label: &quot;Raw HTTP&quot;, component: RawHttpTab, disabled: false },
    { id: &quot;raw-websocket&quot; as const, label: &quot;Raw WebSocket&quot;, component: RawWebSocketTab, disabled: false },
    { id: &quot;metadata&quot; as const, label: &quot;Metadata&quot;, component: MetadataTab, disabled: false },
  ];

  const ActiveTabComponent = tabs.find(tab =&gt; tab.id === activeTab)?.component || ActionsTab;

  return (
    &lt;div className=&quot;interaction-modal-overlay&quot;&gt;
      &lt;div className=&quot;interaction-modal&quot;&gt;
        {/* Header with connection info and controls */}
        &lt;div className=&quot;interaction-header&quot;&gt;
          &lt;div className=&quot;connection-info&quot;&gt;
            &lt;div className=&quot;connection-details&quot;&gt;
              &lt;h2&gt;Connected to Actor&lt;/h2&gt;
              &lt;div className=&quot;connection-meta&quot;&gt;
                &lt;span className=&quot;actor-name&quot;&gt;{state.actorName}&lt;/span&gt;
                {state.actorKey &amp;&amp; &lt;span className=&quot;actor-key&quot;&gt;#{state.actorKey}&lt;/span&gt;}
                &lt;span className=&quot;transport-info&quot;&gt;{state.transport.toUpperCase()}/{state.encoding.toUpperCase()}&lt;/span&gt;
                &lt;span className={`mode-info ${state.connectionMode}`}&gt;
                  {state.connectionMode === &quot;connection&quot; ? &quot;🔗 Connected&quot; : &quot;🔧 Handle&quot;}
                &lt;/span&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;div className=&quot;connection-actions&quot;&gt;
              &lt;button
                className=&quot;btn&quot;
                onClick={handleDispose}
                disabled={isDisposing}
                aria-busy={isDisposing ? &quot;true&quot; : &quot;false&quot;}
              &gt;
                {isDisposing ? &quot;Disposing...&quot; : &quot;Dispose&quot;}
              &lt;/button&gt;
              &lt;button
                className=&quot;btn&quot;
                onClick={handleDisconnect}
                disabled={isDisconnecting}
                aria-busy={isDisconnecting ? &quot;true&quot; : &quot;false&quot;}
              &gt;
                {isDisconnecting ? &quot;Disconnecting...&quot; : &quot;Disconnect&quot;}
              &lt;/button&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;

        {/* Tabs */}
        &lt;div className=&quot;tabs&quot;&gt;
          &lt;div className=&quot;tab-list&quot;&gt;
            {tabs.map(tab =&gt; (
              &lt;button
                key={tab.id}
                className={`tab-button ${activeTab === tab.id ? &quot;active&quot; : &quot;&quot;}`}
                onClick={() =&gt; setActiveTab(tab.id)}
                disabled={tab.disabled}
              &gt;
                {tab.label}
              &lt;/button&gt;
            ))}
          &lt;/div&gt;

          &lt;div className=&quot;tab-content&quot;&gt;
            {activeTab === &quot;events&quot; ? (
              &lt;EventsTab
                state={state}
                updateState={updateState}
                client={client}
                actorHandle={actorHandle}
                eventSubscriptions={eventSubscriptions}
                setEventSubscriptions={setEventSubscriptions}
                events={events}
                setEvents={setEvents}
              /&gt;
            ) : (
              &lt;ActiveTabComponent
                state={state}
                updateState={updateState}
                client={client}
                actorHandle={actorHandle}
                {...({} as any)}
              /&gt;
            )}
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
">
<input type="hidden" name="project[files][src/frontend/components/tabs/ActionsTab.tsx]" value="import { useState } from &quot;react&quot;;
import type { AppState } from &quot;../../App&quot;;

interface TabProps {
  state: AppState;
  updateState: (updates: Partial&lt;AppState&gt;) =&gt; void;
  client: any;
  actorHandle: any;
}

interface ActionResponse {
  [key: string]: string;
}

export default function ActionsTab({ state, actorHandle }: TabProps) {
  const [responses, setResponses] = useState&lt;ActionResponse&gt;({});
  const [loading, setLoading] = useState&lt;{ [key: string]: boolean }&gt;({});

  // Demo actor state
  const [incrementAmount, setIncrementAmount] = useState(&quot;1&quot;);
  const [message, setMessage] = useState(&quot;Hello World&quot;);
  const [delayAmount, setDelayAmount] = useState(&quot;3&quot;);
  const [delayMs, setDelayMs] = useState(&quot;2000&quot;);
  const [eventName, setEventName] = useState(&quot;userAction&quot;);
  const [eventData, setEventData] = useState(&#39;{&quot;type&quot;: &quot;click&quot;, &quot;target&quot;: &quot;button&quot;}&#39;);

  // WebSocket actor state
  const [messageLimit, setMessageLimit] = useState(&quot;10&quot;);

  const callAction = async (actionName: string, ...args: any[]) =&gt; {
    setLoading((prev) =&gt; ({ ...prev, [actionName]: true }));
    setResponses((prev) =&gt; ({ ...prev, [actionName]: &quot;&quot; }));

    try {
      const result = await actorHandle[actionName](...args);
      setResponses((prev) =&gt; ({
        ...prev,
        [actionName]: JSON.stringify(result, null, 2),
      }));
    } catch (error) {
      setResponses((prev) =&gt; ({
        ...prev,
        [actionName]: `Error: ${error instanceof Error ? error.message : String(error)}`,
      }));
    } finally {
      setLoading((prev) =&gt; ({ ...prev, [actionName]: false }));
    }
  };

  const ActionSection = ({
    title,
    description,
    actionName,
    children,
    onCall,
  }: {
    title: string;
    description: string;
    actionName: string;
    children?: React.ReactNode;
    onCall: () =&gt; void;
  }) =&gt; (
    &lt;div className=&quot;section&quot;&gt;
      &lt;h3&gt;{title}&lt;/h3&gt;
      &lt;p style={{ fontSize: &quot;14px&quot;, color: &quot;#666&quot;, marginBottom: &quot;10px&quot; }}&gt;
        {description}
      &lt;/p&gt;
      {children}
      &lt;button
        className=&quot;btn btn-primary&quot;
        onClick={onCall}
        disabled={loading[actionName]}
        style={{ marginTop: children ? &quot;10px&quot; : &quot;0&quot; }}
      &gt;
        {loading[actionName] ? &quot;Calling...&quot; : `Call ${title}`}
      &lt;/button&gt;
      {responses[actionName] &amp;&amp; (
        &lt;div className=&quot;response&quot; style={{ marginTop: &quot;10px&quot; }}&gt;
          {responses[actionName]}
        &lt;/div&gt;
      )}
    &lt;/div&gt;
  );

  const renderDemoActions = () =&gt; (
    &lt;&gt;
      &lt;ActionSection
        title=&quot;getCount&quot;
        description=&quot;Get current counter value&quot;
        actionName=&quot;getCount&quot;
        onCall={() =&gt; callAction(&quot;getCount&quot;)}
      /&gt;

      &lt;ActionSection
        title=&quot;increment&quot;
        description=&quot;Increment counter by amount&quot;
        actionName=&quot;increment&quot;
        onCall={() =&gt; callAction(&quot;increment&quot;, Number(incrementAmount))}
      &gt;
        &lt;div className=&quot;form-group&quot;&gt;
          &lt;label&gt;Amount:&lt;/label&gt;
          &lt;input
            className=&quot;form-control&quot;
            type=&quot;number&quot;
            value={incrementAmount}
            onChange={(e) =&gt; setIncrementAmount(e.target.value)}
          /&gt;
        &lt;/div&gt;
      &lt;/ActionSection&gt;

      &lt;ActionSection
        title=&quot;setMessage&quot;
        description=&quot;Set a message string&quot;
        actionName=&quot;setMessage&quot;
        onCall={() =&gt; callAction(&quot;setMessage&quot;, message)}
      &gt;
        &lt;div className=&quot;form-group&quot;&gt;
          &lt;label&gt;Message:&lt;/label&gt;
          &lt;input
            className=&quot;form-control&quot;
            type=&quot;text&quot;
            value={message}
            onChange={(e) =&gt; setMessage(e.target.value)}
          /&gt;
        &lt;/div&gt;
      &lt;/ActionSection&gt;

      &lt;ActionSection
        title=&quot;delayedIncrement&quot;
        description=&quot;Increment after delay&quot;
        actionName=&quot;delayedIncrement&quot;
        onCall={() =&gt; callAction(&quot;delayedIncrement&quot;, Number(delayAmount), Number(delayMs))}
      &gt;
        &lt;div className=&quot;form-grid&quot;&gt;
          &lt;div className=&quot;form-group&quot;&gt;
            &lt;label&gt;Amount:&lt;/label&gt;
            &lt;input
              className=&quot;form-control&quot;
              type=&quot;number&quot;
              value={delayAmount}
              onChange={(e) =&gt; setDelayAmount(e.target.value)}
            /&gt;
          &lt;/div&gt;
          &lt;div className=&quot;form-group&quot;&gt;
            &lt;label&gt;Delay (ms):&lt;/label&gt;
            &lt;input
              className=&quot;form-control&quot;
              type=&quot;number&quot;
              value={delayMs}
              onChange={(e) =&gt; setDelayMs(e.target.value)}
            /&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/ActionSection&gt;

      &lt;ActionSection
        title=&quot;promiseAction&quot;
        description=&quot;Returns a resolved promise with timestamp&quot;
        actionName=&quot;promiseAction&quot;
        onCall={() =&gt; callAction(&quot;promiseAction&quot;)}
      /&gt;

      &lt;ActionSection
        title=&quot;getState&quot;
        description=&quot;Get both actor and connection state&quot;
        actionName=&quot;getState&quot;
        onCall={() =&gt; callAction(&quot;getState&quot;)}
      /&gt;

      &lt;ActionSection
        title=&quot;getLifecycleInfo&quot;
        description=&quot;Get start/stop counts&quot;
        actionName=&quot;getLifecycleInfo&quot;
        onCall={() =&gt; callAction(&quot;getLifecycleInfo&quot;)}
      /&gt;

      &lt;ActionSection
        title=&quot;getMetadata&quot;
        description=&quot;Get actor metadata&quot;
        actionName=&quot;getMetadata&quot;
        onCall={() =&gt; callAction(&quot;getMetadata&quot;)}
      /&gt;

      &lt;ActionSection
        title=&quot;getInput&quot;
        description=&quot;Get input data passed during actor creation&quot;
        actionName=&quot;getInput&quot;
        onCall={() =&gt; callAction(&quot;getInput&quot;)}
      /&gt;

      &lt;ActionSection
        title=&quot;broadcastCustomEvent&quot;
        description=&quot;Broadcast custom event&quot;
        actionName=&quot;broadcastCustomEvent&quot;
        onCall={() =&gt; {
          try {
            const data = JSON.parse(eventData);
            callAction(&quot;broadcastCustomEvent&quot;, eventName, data);
          } catch (error) {
            setResponses((prev) =&gt; ({
              ...prev,
              broadcastCustomEvent: `Error: Invalid JSON data`,
            }));
          }
        }}
      &gt;
        &lt;div className=&quot;form-grid&quot;&gt;
          &lt;div className=&quot;form-group&quot;&gt;
            &lt;label&gt;Event Name:&lt;/label&gt;
            &lt;input
              className=&quot;form-control&quot;
              type=&quot;text&quot;
              value={eventName}
              onChange={(e) =&gt; setEventName(e.target.value)}
            /&gt;
          &lt;/div&gt;
          &lt;div className=&quot;form-group&quot;&gt;
            &lt;label&gt;Data (JSON):&lt;/label&gt;
            &lt;input
              className=&quot;form-control&quot;
              type=&quot;text&quot;
              value={eventData}
              onChange={(e) =&gt; setEventData(e.target.value)}
            /&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/ActionSection&gt;
    &lt;/&gt;
  );

  const renderHttpActions = () =&gt; (
    &lt;&gt;
      &lt;ActionSection
        title=&quot;getStats&quot;
        description=&quot;Get HTTP request statistics&quot;
        actionName=&quot;getStats&quot;
        onCall={() =&gt; callAction(&quot;getStats&quot;)}
      /&gt;

      &lt;ActionSection
        title=&quot;clearHistory&quot;
        description=&quot;Clear request history&quot;
        actionName=&quot;clearHistory&quot;
        onCall={() =&gt; callAction(&quot;clearHistory&quot;)}
      /&gt;
    &lt;/&gt;
  );

  const renderWebSocketActions = () =&gt; (
    &lt;&gt;
      &lt;ActionSection
        title=&quot;getStats&quot;
        description=&quot;Get WebSocket statistics&quot;
        actionName=&quot;getStats&quot;
        onCall={() =&gt; callAction(&quot;getStats&quot;)}
      /&gt;

      &lt;ActionSection
        title=&quot;clearHistory&quot;
        description=&quot;Clear message history&quot;
        actionName=&quot;clearHistory&quot;
        onCall={() =&gt; callAction(&quot;clearHistory&quot;)}
      /&gt;

      &lt;ActionSection
        title=&quot;getMessageHistory&quot;
        description=&quot;Get recent WebSocket messages&quot;
        actionName=&quot;getMessageHistory&quot;
        onCall={() =&gt; callAction(&quot;getMessageHistory&quot;, Number(messageLimit))}
      &gt;
        &lt;div className=&quot;form-group&quot;&gt;
          &lt;label&gt;Limit:&lt;/label&gt;
          &lt;input
            className=&quot;form-control&quot;
            type=&quot;number&quot;
            value={messageLimit}
            onChange={(e) =&gt; setMessageLimit(e.target.value)}
          /&gt;
        &lt;/div&gt;
      &lt;/ActionSection&gt;
    &lt;/&gt;
  );

  return (
    &lt;div&gt;
      {state.actorName === &quot;demo&quot; &amp;&amp; renderDemoActions()}
      {state.actorName === &quot;http&quot; &amp;&amp; renderHttpActions()}
      {state.actorName === &quot;websocket&quot; &amp;&amp; renderWebSocketActions()}
    &lt;/div&gt;
  );
}">
<input type="hidden" name="project[files][src/frontend/components/tabs/ConnectionsTab.tsx]" value="import { useState, useEffect } from &quot;react&quot;;
import type { AppState } from &quot;../../App&quot;;

interface TabProps {
  state: AppState;
  updateState: (updates: Partial&lt;AppState&gt;) =&gt; void;
  client: any;
  actorHandle: any;
}

interface ConnectionInfo {
  id: string;
  connectedAt: number;
}

export default function ConnectionsTab({ state, actorHandle }: TabProps) {
  const [currentConnectionInfo, setCurrentConnectionInfo] = useState&lt;any&gt;(null);
  const [allConnections, setAllConnections] = useState&lt;ConnectionInfo[]&gt;([]);
  const [isLoadingConnections, setIsLoadingConnections] = useState(false);

  // Update current connection info when actor handle changes
  useEffect(() =&gt; {
    if (actorHandle &amp;&amp; state.connectionMode === &quot;connection&quot;) {
      setCurrentConnectionInfo({
        isConnected: true,
        connectionId: actorHandle.connectionId || &quot;N/A&quot;,
        transport: state.transport,
        encoding: state.encoding,
        actorName: state.actorName,
        actorKey: state.actorKey,
        actorId: state.actorId,
        lastActivity: Date.now(),
      });
    } else {
      setCurrentConnectionInfo(null);
    }
  }, [actorHandle, state]);

  const formatTimestamp = (timestamp: number) =&gt; {
    return new Date(timestamp).toLocaleString();
  };

  const handleListConnections = async () =&gt; {
    setIsLoadingConnections(true);
    try {
      const connections = await actorHandle.listConnections();
      setAllConnections(connections);
    } catch (error) {
      console.error(&quot;Failed to list connections:&quot;, error);
      alert(`Failed to list connections: ${error instanceof Error ? error.message : String(error)}`);
    } finally {
      setIsLoadingConnections(false);
    }
  };

  if (state.actorName !== &quot;demo&quot;) {
    return (
      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Connections&lt;/h3&gt;
        &lt;p&gt;Connection features are only available for the &lt;code&gt;demo&lt;/code&gt; actor. Please select the demo actor in the header.&lt;/p&gt;
      &lt;/div&gt;
    );
  }

  return (
    &lt;div&gt;
      {/* Current Connection Information */}
      {state.connectionMode === &quot;connection&quot; &amp;&amp; (
        &lt;div className=&quot;section&quot;&gt;
          &lt;h3&gt;Current Connection&lt;/h3&gt;

          {!currentConnectionInfo ? (
            &lt;div style={{
              padding: &quot;20px&quot;,
              textAlign: &quot;center&quot;,
              color: &quot;var(--text-secondary)&quot;
            }}&gt;
              No active connection
            &lt;/div&gt;
          ) : (
            &lt;div style={{ display: &quot;grid&quot;, gridTemplateColumns: &quot;auto 1fr&quot;, gap: &quot;10px 20px&quot;, alignItems: &quot;center&quot; }}&gt;
              &lt;strong&gt;Status:&lt;/strong&gt;
              &lt;span&gt;🟢 Connected&lt;/span&gt;

              &lt;strong&gt;Connection ID:&lt;/strong&gt;
              &lt;code&gt;
                {currentConnectionInfo.connectionId}
              &lt;/code&gt;

              &lt;strong&gt;Transport:&lt;/strong&gt;
              &lt;span&gt;{currentConnectionInfo.transport.toUpperCase()}&lt;/span&gt;

              &lt;strong&gt;Encoding:&lt;/strong&gt;
              &lt;span&gt;{currentConnectionInfo.encoding.toUpperCase()}&lt;/span&gt;

              &lt;strong&gt;Actor Name:&lt;/strong&gt;
              &lt;span&gt;{currentConnectionInfo.actorName}&lt;/span&gt;

              &lt;strong&gt;Actor Key:&lt;/strong&gt;
              &lt;span&gt;{currentConnectionInfo.actorKey || &quot;(empty)&quot;}&lt;/span&gt;

              {currentConnectionInfo.actorId &amp;&amp; (
                &lt;&gt;
                  &lt;strong&gt;Actor ID:&lt;/strong&gt;
                  &lt;code&gt;
                    {currentConnectionInfo.actorId}
                  &lt;/code&gt;
                &lt;/&gt;
              )}

              &lt;strong&gt;Last Activity:&lt;/strong&gt;
              &lt;span&gt;{formatTimestamp(currentConnectionInfo.lastActivity)}&lt;/span&gt;
            &lt;/div&gt;
          )}
        &lt;/div&gt;
      )}

      {/* All Connections */}
      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;All Connections&lt;/h3&gt;
        &lt;p style={{ marginBottom: &quot;15px&quot; }}&gt;
          List all active connections to this actor instance.
        &lt;/p&gt;

        &lt;button
          className=&quot;btn btn-primary&quot;
          onClick={handleListConnections}
          disabled={isLoadingConnections}
        &gt;
          {isLoadingConnections ? &quot;Loading...&quot; : &quot;List All Connections&quot;}
        &lt;/button&gt;

        {allConnections.length &gt; 0 &amp;&amp; (
          &lt;div style={{ marginTop: &quot;15px&quot; }}&gt;
            &lt;table style={{
              width: &quot;100%&quot;,
              borderCollapse: &quot;collapse&quot;,
              fontSize: &quot;14px&quot;
            }}&gt;
              &lt;thead&gt;
                &lt;tr style={{ borderBottom: &quot;2px solid #333&quot; }}&gt;
                  &lt;th style={{ padding: &quot;8px&quot;, textAlign: &quot;left&quot; }}&gt;Connection ID&lt;/th&gt;
                  &lt;th style={{ padding: &quot;8px&quot;, textAlign: &quot;left&quot; }}&gt;Connected At&lt;/th&gt;
                &lt;/tr&gt;
              &lt;/thead&gt;
              &lt;tbody&gt;
                {allConnections.map((conn) =&gt; (
                  &lt;tr key={conn.id} style={{ borderBottom: &quot;1px solid #222&quot; }}&gt;
                    &lt;td style={{ padding: &quot;8px&quot; }}&gt;
                      &lt;code&gt;{conn.id}&lt;/code&gt;
                    &lt;/td&gt;
                    &lt;td style={{ padding: &quot;8px&quot; }}&gt;
                      {conn.connectedAt ? formatTimestamp(conn.connectedAt) : &quot;N/A&quot;}
                    &lt;/td&gt;
                  &lt;/tr&gt;
                ))}
              &lt;/tbody&gt;
            &lt;/table&gt;
          &lt;/div&gt;
        )}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}
">
<input type="hidden" name="project[files][src/frontend/components/tabs/EventsTab.tsx]" value="import { useState, useRef } from &quot;react&quot;;
import type { AppState } from &quot;../../App&quot;;
import type { EventSubscription, EventItem } from &quot;../InteractionScreen&quot;;

interface TabProps {
  state: AppState;
  updateState: (updates: Partial&lt;AppState&gt;) =&gt; void;
  client: any;
  actorHandle: any;
  eventSubscriptions: Map&lt;string, EventSubscription&gt;;
  setEventSubscriptions: React.Dispatch&lt;React.SetStateAction&lt;Map&lt;string, EventSubscription&gt;&gt;&gt;;
  events: EventItem[];
  setEvents: React.Dispatch&lt;React.SetStateAction&lt;EventItem[]&gt;&gt;;
}

export default function EventsTab({
  actorHandle,
  eventSubscriptions,
  setEventSubscriptions,
  events,
  setEvents
}: TabProps) {
  const [selectedEventType, setSelectedEventType] = useState(&quot;countChanged&quot;);
  const [customEventName, setCustomEventName] = useState(&quot;&quot;);
  const eventIdCounter = useRef(0);

  const predefinedEvents = [
    &quot;countChanged&quot;,
    &quot;messageChanged&quot;,
    &quot;preferenceChanged&quot;,
    &quot;alarmTriggered&quot;,
  ];

  const addEvent = (name: string, data: any) =&gt; {
    const event: EventItem = {
      timestamp: Date.now(),
      name,
      data,
      id: `event-${++eventIdCounter.current}`,
    };
    setEvents(prev =&gt; [...prev, event].slice(-100)); // Keep last 100 events
  };

  const subscribe = (eventName: string) =&gt; {
    if (!actorHandle || eventSubscriptions.has(eventName)) return;

    const unsubscribe = actorHandle.on(eventName, (...args: any[]) =&gt; {
      addEvent(eventName, args.length === 1 ? args[0] : args);
    });

    setEventSubscriptions(prev =&gt; {
      const next = new Map(prev);
      next.set(eventName, { eventName, unsubscribe });
      return next;
    });
  };

  const unsubscribe = (eventName: string) =&gt; {
    const subscription = eventSubscriptions.get(eventName);
    if (!subscription) return;

    subscription.unsubscribe();
    setEventSubscriptions(prev =&gt; {
      const next = new Map(prev);
      next.delete(eventName);
      return next;
    });
  };

  const handleSubscribe = () =&gt; {
    const eventName = selectedEventType === &quot;custom&quot; ? customEventName.trim() : selectedEventType;
    if (!eventName) return;

    subscribe(eventName);

    // Reset custom input if it was used
    if (selectedEventType === &quot;custom&quot;) {
      setCustomEventName(&quot;&quot;);
    }
  };

  const clearEvents = () =&gt; {
    setEvents([]);
  };

  const formatTimestamp = (timestamp: number) =&gt; {
    return new Date(timestamp).toLocaleTimeString();
  };

  const formatData = (data: any) =&gt; {
    if (typeof data === &quot;string&quot;) return data;
    return JSON.stringify(data, null, 2);
  };

  return (
    &lt;div&gt;
      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Subscribe to Events&lt;/h3&gt;

        &lt;div style={{ display: &quot;flex&quot;, gap: &quot;10px&quot;, marginBottom: &quot;15px&quot; }}&gt;
          &lt;div className=&quot;form-group&quot; style={{ flex: 1, marginBottom: 0 }}&gt;
            &lt;label&gt;Event Name:&lt;/label&gt;
            &lt;select
              className=&quot;form-control&quot;
              value={selectedEventType}
              onChange={(e) =&gt; setSelectedEventType(e.target.value)}
            &gt;
              {predefinedEvents.map(event =&gt; (
                &lt;option key={event} value={event}&gt;{event}&lt;/option&gt;
              ))}
              &lt;option value=&quot;custom&quot;&gt;Custom...&lt;/option&gt;
            &lt;/select&gt;
          &lt;/div&gt;

          {selectedEventType === &quot;custom&quot; &amp;&amp; (
            &lt;div className=&quot;form-group&quot; style={{ flex: 1, marginBottom: 0 }}&gt;
              &lt;label&gt;Custom Event Name:&lt;/label&gt;
              &lt;input
                className=&quot;form-control&quot;
                type=&quot;text&quot;
                value={customEventName}
                onChange={(e) =&gt; setCustomEventName(e.target.value)}
                placeholder=&quot;Enter event name...&quot;
                onKeyDown={(e) =&gt; {
                  if (e.key === &quot;Enter&quot;) handleSubscribe();
                }}
              /&gt;
            &lt;/div&gt;
          )}

          &lt;div style={{ display: &quot;flex&quot;, alignItems: &quot;flex-end&quot; }}&gt;
            &lt;button
              className=&quot;btn btn-primary&quot;
              onClick={handleSubscribe}
              disabled={selectedEventType === &quot;custom&quot; &amp;&amp; !customEventName.trim()}
            &gt;
              Subscribe
            &lt;/button&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Active Subscriptions ({eventSubscriptions.size})&lt;/h3&gt;

        {eventSubscriptions.size === 0 ? (
          &lt;div style={{ textAlign: &quot;center&quot;, color: &quot;var(--text-secondary)&quot;, padding: &quot;20px&quot; }}&gt;
            No active subscriptions
          &lt;/div&gt;
        ) : (
          &lt;div style={{
            display: &quot;flex&quot;,
            flexWrap: &quot;wrap&quot;,
            gap: &quot;8px&quot;
          }}&gt;
            {Array.from(eventSubscriptions.values()).map(sub =&gt; (
              &lt;div
                key={sub.eventName}
                style={{
                  display: &quot;flex&quot;,
                  alignItems: &quot;center&quot;,
                  gap: &quot;8px&quot;,
                  background: &quot;var(--bg-tertiary)&quot;,
                  border: &quot;1px solid var(--border-secondary)&quot;,
                  borderRadius: &quot;20px&quot;,
                  padding: &quot;6px 12px&quot;,
                  fontSize: &quot;14px&quot;
                }}
              &gt;
                &lt;span style={{ fontFamily: &quot;monospace&quot; }}&gt;{sub.eventName}&lt;/span&gt;
                &lt;button
                  onClick={() =&gt; unsubscribe(sub.eventName)}
                  style={{
                    background: &quot;none&quot;,
                    border: &quot;none&quot;,
                    color: &quot;var(--danger-color)&quot;,
                    cursor: &quot;pointer&quot;,
                    fontSize: &quot;16px&quot;,
                    padding: 0,
                    lineHeight: 1,
                    width: &quot;16px&quot;,
                    height: &quot;16px&quot;,
                    display: &quot;flex&quot;,
                    alignItems: &quot;center&quot;,
                    justifyContent: &quot;center&quot;
                  }}
                  title=&quot;Unsubscribe&quot;
                &gt;
                  ×
                &lt;/button&gt;
              &lt;/div&gt;
            ))}
          &lt;/div&gt;
        )}
      &lt;/div&gt;

      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Event History ({events.length} events)&lt;/h3&gt;

        &lt;div style={{ marginBottom: &quot;15px&quot; }}&gt;
          &lt;button
            className=&quot;btn&quot;
            onClick={clearEvents}
          &gt;
            Clear ({events.length})
          &lt;/button&gt;
        &lt;/div&gt;

        {events.length === 0 ? (
          &lt;div style={{ textAlign: &quot;center&quot;, color: &quot;var(--text-secondary)&quot;, padding: &quot;20px&quot; }}&gt;
            No events received yet
          &lt;/div&gt;
        ) : (
          &lt;div className=&quot;event-list&quot;&gt;
            {events.map(event =&gt; (
              &lt;div key={event.id} className=&quot;event-item&quot;&gt;
                &lt;span className=&quot;timestamp&quot;&gt;{formatTimestamp(event.timestamp)}&lt;/span&gt;
                &lt;span className=&quot;name&quot;&gt;{event.name}&lt;/span&gt;
                &lt;div style={{ marginTop: &quot;5px&quot; }}&gt;
                  {formatData(event.data)}
                &lt;/div&gt;
              &lt;/div&gt;
            ))}
          &lt;/div&gt;
        )}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}">
<input type="hidden" name="project[files][src/frontend/components/tabs/MetadataTab.tsx]" value="import { useState } from &quot;react&quot;;
import type { AppState } from &quot;../../App&quot;;

interface TabProps {
  state: AppState;
  updateState: (updates: Partial&lt;AppState&gt;) =&gt; void;
  client: any;
  actorHandle: any;
}

export default function MetadataTab({ state, actorHandle }: TabProps) {
  const [metadata, setMetadata] = useState&lt;any&gt;(null);
  const [actorState, setActorState] = useState&lt;any&gt;(null);
  const [connState, setConnState] = useState&lt;any&gt;(null);
  const [loadingStates, setLoadingStates] = useState({
    metadata: false,
    actorState: false,
    connState: false
  });

  const callAction = async (actionName: string, args: any[] = []) =&gt; {
    return await actorHandle[actionName](...args);
  };

  const handleGetMetadata = async () =&gt; {
    setLoadingStates(prev =&gt; ({ ...prev, metadata: true }));
    setMetadata(null);

    try {
      const result = await callAction(&quot;getMetadata&quot;);
      setMetadata(result);
    } catch (error) {
      setMetadata({
        error: error instanceof Error ? error.message : String(error)
      });
    } finally {
      setLoadingStates(prev =&gt; ({ ...prev, metadata: false }));
    }
  };

  const handleGetActorState = async () =&gt; {
    setLoadingStates(prev =&gt; ({ ...prev, actorState: true }));
    setActorState(null);

    try {
      const result = await callAction(&quot;getActorState&quot;);
      setActorState(result);
    } catch (error) {
      setActorState({
        error: error instanceof Error ? error.message : String(error)
      });
    } finally {
      setLoadingStates(prev =&gt; ({ ...prev, actorState: false }));
    }
  };

  const handleGetConnState = async () =&gt; {
    setLoadingStates(prev =&gt; ({ ...prev, connState: true }));
    setConnState(null);

    try {
      const result = await callAction(&quot;getConnState&quot;);
      setConnState(result);
    } catch (error) {
      setConnState({
        error: error instanceof Error ? error.message : String(error)
      });
    } finally {
      setLoadingStates(prev =&gt; ({ ...prev, connState: false }));
    }
  };

  if (state.actorName !== &quot;demo&quot;) {
    return (
      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Metadata&lt;/h3&gt;
        &lt;p&gt;Metadata features are only available for the &lt;code&gt;demo&lt;/code&gt; actor. Please select the demo actor in the header.&lt;/p&gt;
      &lt;/div&gt;
    );
  }

  return (
    &lt;div&gt;
      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Actor Metadata&lt;/h3&gt;
        &lt;p style={{ marginBottom: &quot;15px&quot; }}&gt;
          Actors can access metadata about themselves including their name, tags, and region.
          This information is useful for logging, monitoring, and conditional behavior.
        &lt;/p&gt;

        &lt;button
          className=&quot;btn btn-primary&quot;
          onClick={handleGetMetadata}
          disabled={loadingStates.metadata}
        &gt;
          {loadingStates.metadata ? &quot;Loading...&quot; : &quot;Get Metadata&quot;}
        &lt;/button&gt;

        {metadata &amp;&amp; (
          &lt;div className=&quot;response&quot;&gt;
            &lt;pre style={{ margin: 0, fontSize: &quot;13px&quot;, overflow: &quot;auto&quot; }}&gt;
              {JSON.stringify(metadata, null, 2)}
            &lt;/pre&gt;
          &lt;/div&gt;
        )}
      &lt;/div&gt;

      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Actor State&lt;/h3&gt;
        &lt;p style={{ marginBottom: &quot;15px&quot; }}&gt;
          Current state of the actor. This state is shared across all connections to the actor.
        &lt;/p&gt;

        &lt;button
          className=&quot;btn btn-primary&quot;
          onClick={handleGetActorState}
          disabled={loadingStates.actorState}
        &gt;
          {loadingStates.actorState ? &quot;Loading...&quot; : &quot;Get Actor State&quot;}
        &lt;/button&gt;

        {actorState &amp;&amp; (
          &lt;div className=&quot;response&quot;&gt;
            &lt;pre style={{ margin: 0, fontSize: &quot;13px&quot;, overflow: &quot;auto&quot; }}&gt;
              {JSON.stringify(actorState, null, 2)}
            &lt;/pre&gt;
          &lt;/div&gt;
        )}
      &lt;/div&gt;

      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Connection State&lt;/h3&gt;
        &lt;p style={{ marginBottom: &quot;15px&quot; }}&gt;
          State specific to the current connection. Each connection has its own isolated state.
        &lt;/p&gt;

        &lt;button
          className=&quot;btn btn-primary&quot;
          onClick={handleGetConnState}
          disabled={loadingStates.connState}
        &gt;
          {loadingStates.connState ? &quot;Loading...&quot; : &quot;Get Connection State&quot;}
        &lt;/button&gt;

        {connState &amp;&amp; (
          &lt;div className=&quot;response&quot;&gt;
            &lt;pre style={{ margin: 0, fontSize: &quot;13px&quot;, overflow: &quot;auto&quot; }}&gt;
              {JSON.stringify(connState, null, 2)}
            &lt;/pre&gt;
          &lt;/div&gt;
        )}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}">
<input type="hidden" name="project[files][src/frontend/components/tabs/RawHttpTab.tsx]" value="import { useState } from &quot;react&quot;;
import type { AppState } from &quot;../../App&quot;;

interface TabProps {
  state: AppState;
  updateState: (updates: Partial&lt;AppState&gt;) =&gt; void;
  client: any;
  actorHandle: any;
}

export default function RawHttpTab({ state }: TabProps) {
  const [method, setMethod] = useState(&quot;GET&quot;);
  const [path, setPath] = useState(&quot;/api/hello&quot;);
  const [headers, setHeaders] = useState(&quot;{}&quot;);
  const [body, setBody] = useState(&quot;&quot;);
  const [response, setResponse] = useState(&quot;&quot;);
  const [isLoading, setIsLoading] = useState(false);

  const parseHeaders = (headersString: string) =&gt; {
    try {
      return JSON.parse(headersString);
    } catch {
      return {};
    }
  };

  const getActorUrl = () =&gt; {
    const baseUrl = &quot;http://localhost:8080&quot;;
    const actorPath = state.actorKey ?
      `/actors/${state.actorName}/${encodeURIComponent(state.actorKey)}` :
      `/actors/${state.actorName}`;
    return `${baseUrl}${actorPath}`;
  };

  const handleSendRequest = async () =&gt; {
    setIsLoading(true);
    setResponse(&quot;&quot;);

    try {
      const url = `${getActorUrl()}${path}`;
      const requestHeaders = parseHeaders(headers);

      const requestOptions: RequestInit = {
        method,
        headers: {
          &quot;Content-Type&quot;: &quot;application/json&quot;,
          ...requestHeaders,
        },
      };

      if (method !== &quot;GET&quot; &amp;&amp; method !== &quot;HEAD&quot; &amp;&amp; body) {
        requestOptions.body = body;
      }

      const startTime = Date.now();
      const res = await fetch(url, requestOptions);
      const endTime = Date.now();

      const responseHeaders = Object.fromEntries(res.headers.entries());
      const responseBody = await res.text();

      const responseData = {
        status: res.status,
        statusText: res.statusText,
        duration: `${endTime - startTime}ms`,
        headers: responseHeaders,
        body: responseBody,
        url: url,
      };

      setResponse(JSON.stringify(responseData, null, 2));
    } catch (error) {
      setResponse(`Error: ${error instanceof Error ? error.message : String(error)}`);
    } finally {
      setIsLoading(false);
    }
  };

  const predefinedPaths = [
    &quot;/api/hello&quot;,
    &quot;/api/echo&quot;,
    &quot;/api/stats&quot;,
    &quot;/api/headers&quot;,
    &quot;/api/json&quot;,
    &quot;/api/custom?param=value&quot;,
  ];

  const exampleRequests = [
    {
      name: &quot;Get Hello&quot;,
      method: &quot;GET&quot;,
      path: &quot;/api/hello&quot;,
      headers: &quot;{}&quot;,
      body: &quot;&quot;,
    },
    {
      name: &quot;Echo POST&quot;,
      method: &quot;POST&quot;,
      path: &quot;/api/echo&quot;,
      headers: &#39;{&quot;Content-Type&quot;: &quot;text/plain&quot;}&#39;,
      body: &quot;Hello from client!&quot;,
    },
    {
      name: &quot;JSON POST&quot;,
      method: &quot;POST&quot;,
      path: &quot;/api/json&quot;,
      headers: &#39;{&quot;Content-Type&quot;: &quot;application/json&quot;}&#39;,
      body: &#39;{&quot;message&quot;: &quot;Hello&quot;, &quot;timestamp&quot;: 1234567890}&#39;,
    },
    {
      name: &quot;Get Stats&quot;,
      method: &quot;GET&quot;,
      path: &quot;/api/stats&quot;,
      headers: &quot;{}&quot;,
      body: &quot;&quot;,
    },
  ];

  const loadExample = (example: typeof exampleRequests[0]) =&gt; {
    setMethod(example.method);
    setPath(example.path);
    setHeaders(example.headers);
    setBody(example.body);
  };

  return (
    &lt;div&gt;
      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Raw HTTP Request Builder&lt;/h3&gt;

        &lt;div className=&quot;form-grid&quot;&gt;
          &lt;div className=&quot;form-group&quot;&gt;
            &lt;label&gt;Method:&lt;/label&gt;
            &lt;select
              className=&quot;form-control&quot;
              value={method}
              onChange={(e) =&gt; setMethod(e.target.value)}
            &gt;
              &lt;option value=&quot;GET&quot;&gt;GET&lt;/option&gt;
              &lt;option value=&quot;POST&quot;&gt;POST&lt;/option&gt;
              &lt;option value=&quot;PUT&quot;&gt;PUT&lt;/option&gt;
              &lt;option value=&quot;DELETE&quot;&gt;DELETE&lt;/option&gt;
              &lt;option value=&quot;PATCH&quot;&gt;PATCH&lt;/option&gt;
              &lt;option value=&quot;HEAD&quot;&gt;HEAD&lt;/option&gt;
            &lt;/select&gt;
          &lt;/div&gt;

          &lt;div className=&quot;form-group&quot;&gt;
            &lt;label&gt;Path:&lt;/label&gt;
            &lt;select
              className=&quot;form-control&quot;
              value={path}
              onChange={(e) =&gt; setPath(e.target.value)}
            &gt;
              {predefinedPaths.map(p =&gt; (
                &lt;option key={p} value={p}&gt;{p}&lt;/option&gt;
              ))}
            &lt;/select&gt;
          &lt;/div&gt;
        &lt;/div&gt;

        &lt;div className=&quot;form-group&quot; style={{ marginBottom: &quot;15px&quot; }}&gt;
          &lt;label&gt;Custom Path:&lt;/label&gt;
          &lt;input
            className=&quot;form-control&quot;
            type=&quot;text&quot;
            value={path}
            onChange={(e) =&gt; setPath(e.target.value)}
            placeholder=&quot;/api/custom&quot;
          /&gt;
        &lt;/div&gt;

        &lt;div className=&quot;form-group&quot; style={{ marginBottom: &quot;15px&quot; }}&gt;
          &lt;label&gt;Headers (JSON):&lt;/label&gt;
          &lt;textarea
            className=&quot;form-control textarea&quot;
            value={headers}
            onChange={(e) =&gt; setHeaders(e.target.value)}
            placeholder=&#39;{&quot;Content-Type&quot;: &quot;application/json&quot;}&#39;
            rows={3}
          /&gt;
        &lt;/div&gt;

        {method !== &quot;GET&quot; &amp;&amp; method !== &quot;HEAD&quot; &amp;&amp; (
          &lt;div className=&quot;form-group&quot; style={{ marginBottom: &quot;15px&quot; }}&gt;
            &lt;label&gt;Body:&lt;/label&gt;
            &lt;textarea
              className=&quot;form-control textarea&quot;
              value={body}
              onChange={(e) =&gt; setBody(e.target.value)}
              placeholder=&quot;Request body...&quot;
              rows={4}
            /&gt;
          &lt;/div&gt;
        )}

        &lt;div style={{ marginBottom: &quot;15px&quot; }}&gt;
          &lt;button
            className=&quot;btn btn-primary&quot;
            onClick={handleSendRequest}
            disabled={isLoading}
          &gt;
            {isLoading ? &quot;Sending...&quot; : &quot;Send Request&quot;}
          &lt;/button&gt;
        &lt;/div&gt;

        &lt;div style={{
          background: &quot;var(--bg-tertiary)&quot;,
          border: &quot;1px solid var(--border-secondary)&quot;,
          borderRadius: &quot;4px&quot;,
          padding: &quot;10px&quot;,
          marginBottom: &quot;15px&quot;,
          fontSize: &quot;13px&quot;
        }}&gt;
          &lt;strong&gt;Target URL:&lt;/strong&gt; &lt;code&gt;{getActorUrl()}{path}&lt;/code&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Example Requests&lt;/h3&gt;
        &lt;div style={{ display: &quot;flex&quot;, gap: &quot;10px&quot;, flexWrap: &quot;wrap&quot;, marginBottom: &quot;15px&quot; }}&gt;
          {exampleRequests.map(example =&gt; (
            &lt;button
              key={example.name}
              className=&quot;btn&quot;
              onClick={() =&gt; loadExample(example)}
              style={{ fontSize: &quot;12px&quot; }}
            &gt;
              {example.name}
            &lt;/button&gt;
          ))}
        &lt;/div&gt;
      &lt;/div&gt;

      {response &amp;&amp; (
        &lt;div className=&quot;section&quot;&gt;
          &lt;h3&gt;Response&lt;/h3&gt;
          &lt;div className=&quot;response&quot;&gt;
            {response}
          &lt;/div&gt;
        &lt;/div&gt;
      )}
    &lt;/div&gt;
  );
}">
<input type="hidden" name="project[files][src/frontend/components/tabs/RawWebSocketTab.tsx]" value="import { useState, useEffect, useRef } from &quot;react&quot;;
import type { AppState } from &quot;../../App&quot;;

interface TabProps {
  state: AppState;
  updateState: (updates: Partial&lt;AppState&gt;) =&gt; void;
  client: any;
  actorHandle: any;
}

interface Message {
  id: string;
  type: &quot;sent&quot; | &quot;received&quot;;
  data: string;
  timestamp: number;
  isBinary: boolean;
}

export default function RawWebSocketTab({ state }: TabProps) {
  const [message, setMessage] = useState(&#39;{&quot;type&quot;: &quot;ping&quot;, &quot;timestamp&quot;: 0}&#39;);
  const [isBinary, setIsBinary] = useState(false);
  const [messages, setMessages] = useState&lt;Message[]&gt;([]);
  const [isConnected, setIsConnected] = useState(false);
  const [connectionStatus, setConnectionStatus] = useState(&quot;disconnected&quot;);
  const [connectionError, setConnectionError] = useState(&quot;&quot;);

  const websocketRef = useRef&lt;WebSocket | null&gt;(null);
  const messageIdCounter = useRef(0);

  const getWebSocketUrl = () =&gt; {
    const wsProtocol = window.location.protocol === &quot;https:&quot; ? &quot;wss:&quot; : &quot;ws:&quot;;
    const actorPath = state.actorKey ?
      `/actors/${state.actorName}/${encodeURIComponent(state.actorKey)}/ws` :
      `/actors/${state.actorName}/ws`;
    return `${wsProtocol}//localhost:8080${actorPath}`;
  };

  const addMessage = (type: &quot;sent&quot; | &quot;received&quot;, data: string, isBinary = false) =&gt; {
    const message: Message = {
      id: `msg-${++messageIdCounter.current}`,
      type,
      data,
      timestamp: Date.now(),
      isBinary,
    };
    setMessages(prev =&gt; [...prev, message].slice(-50)); // Keep last 50 messages
  };

  const connectWebSocket = () =&gt; {
    if (websocketRef.current?.readyState === WebSocket.OPEN) {
      return;
    }

    setConnectionStatus(&quot;connecting&quot;);
    setConnectionError(&quot;&quot;);

    try {
      const ws = new WebSocket(getWebSocketUrl());
      websocketRef.current = ws;

      ws.onopen = () =&gt; {
        setIsConnected(true);
        setConnectionStatus(&quot;connected&quot;);
        addMessage(&quot;received&quot;, &quot;WebSocket connected&quot;, false);
      };

      ws.onmessage = (event) =&gt; {
        const data = event.data;
        const isBinary = data instanceof ArrayBuffer || data instanceof Blob;

        if (isBinary) {
          // Convert binary data to hex string for display
          if (data instanceof Blob) {
            data.arrayBuffer().then(ab =&gt; {
              const bytes = new Uint8Array(ab);
              const hex = Array.from(bytes).map(b =&gt; b.toString(16).padStart(2, &#39;0&#39;)).join(&#39; &#39;);
              addMessage(&quot;received&quot;, `[Binary: ${bytes.length} bytes] ${hex}`, true);
            });
          } else {
            const bytes = new Uint8Array(data);
            const hex = Array.from(bytes).map(b =&gt; b.toString(16).padStart(2, &#39;0&#39;)).join(&#39; &#39;);
            addMessage(&quot;received&quot;, `[Binary: ${bytes.length} bytes] ${hex}`, true);
          }
        } else {
          addMessage(&quot;received&quot;, String(data), false);
        }
      };

      ws.onclose = (event) =&gt; {
        setIsConnected(false);
        setConnectionStatus(&quot;disconnected&quot;);
        addMessage(&quot;received&quot;, `WebSocket closed (code: ${event.code}, reason: ${event.reason})`, false);
      };

      ws.onerror = () =&gt; {
        setConnectionError(&quot;WebSocket error occurred&quot;);
        addMessage(&quot;received&quot;, &quot;WebSocket error&quot;, false);
      };
    } catch (error) {
      setConnectionError(error instanceof Error ? error.message : &quot;Connection failed&quot;);
      setConnectionStatus(&quot;disconnected&quot;);
    }
  };

  const disconnectWebSocket = () =&gt; {
    if (websocketRef.current) {
      websocketRef.current.close();
      websocketRef.current = null;
    }
  };

  const sendMessage = () =&gt; {
    if (!websocketRef.current || websocketRef.current.readyState !== WebSocket.OPEN) {
      return;
    }

    try {
      if (isBinary) {
        // Convert hex string to binary data
        const hexString = message.replace(/\s/g, &quot;&quot;);
        const bytes = new Uint8Array(hexString.length / 2);
        for (let i = 0; i &lt; hexString.length; i += 2) {
          bytes[i / 2] = parseInt(hexString.substr(i, 2), 16);
        }
        websocketRef.current.send(bytes);
        addMessage(&quot;sent&quot;, `[Binary: ${bytes.length} bytes] ${hexString}`, true);
      } else {
        websocketRef.current.send(message);
        addMessage(&quot;sent&quot;, message, false);
      }
    } catch (error) {
      addMessage(&quot;received&quot;, `Send error: ${error instanceof Error ? error.message : String(error)}`, false);
    }
  };

  const clearMessages = () =&gt; {
    setMessages([]);
  };

  const formatTimestamp = (timestamp: number) =&gt; {
    return new Date(timestamp).toLocaleTimeString();
  };

  // Update ping message timestamp
  const updatePingTimestamp = () =&gt; {
    if (message.includes(&#39;&quot;timestamp&quot;&#39;)) {
      try {
        const parsed = JSON.parse(message);
        parsed.timestamp = Date.now();
        setMessage(JSON.stringify(parsed, null, 2));
      } catch {
        // Not JSON, ignore
      }
    }
  };

  const exampleMessages = [
    { name: &quot;Ping&quot;, data: &#39;{&quot;type&quot;: &quot;ping&quot;, &quot;timestamp&quot;: 0}&#39;, binary: false },
    { name: &quot;Echo&quot;, data: &#39;{&quot;type&quot;: &quot;echo&quot;, &quot;message&quot;: &quot;Hello WebSocket!&quot;}&#39;, binary: false },
    { name: &quot;Get Stats&quot;, data: &#39;{&quot;type&quot;: &quot;getStats&quot;}&#39;, binary: false },
    { name: &quot;Binary Data&quot;, data: &quot;48 65 6c 6c 6f&quot;, binary: true },
  ];

  const loadExample = (example: typeof exampleMessages[0]) =&gt; {
    if (example.name === &quot;Ping&quot;) {
      const pingData = JSON.parse(example.data);
      pingData.timestamp = Date.now();
      setMessage(JSON.stringify(pingData, null, 2));
    } else {
      setMessage(example.data);
    }
    setIsBinary(example.binary);
  };

  // Cleanup on unmount
  useEffect(() =&gt; {
    return () =&gt; {
      disconnectWebSocket();
    };
  }, []);

  return (
    &lt;div&gt;
      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;WebSocket Connection&lt;/h3&gt;

        &lt;div style={{ marginBottom: &quot;15px&quot; }}&gt;
          &lt;div style={{
            padding: &quot;10px&quot;,
            border: &quot;1px solid #ddd&quot;,
            borderRadius: &quot;4px&quot;,
            background: isConnected ? &quot;#d4edda&quot; : &quot;#f8d7da&quot;,
            color: isConnected ? &quot;#155724&quot; : &quot;#721c24&quot;,
            marginBottom: &quot;10px&quot;
          }}&gt;
            Status: {connectionStatus === &quot;connecting&quot; ? &quot;🟡 Connecting...&quot; :
                     isConnected ? &quot;🟢 Connected&quot; : &quot;🔴 Disconnected&quot;}
            {connectionError &amp;&amp; ` - ${connectionError}`}
          &lt;/div&gt;

          &lt;div style={{
            fontSize: &quot;13px&quot;,
            color: &quot;#666&quot;,
            marginBottom: &quot;10px&quot;
          }}&gt;
            Target: &lt;code&gt;{getWebSocketUrl()}&lt;/code&gt;
          &lt;/div&gt;

          &lt;div style={{ display: &quot;flex&quot;, gap: &quot;10px&quot; }}&gt;
            &lt;button
              className=&quot;btn btn-primary&quot;
              onClick={connectWebSocket}
              disabled={isConnected || connectionStatus === &quot;connecting&quot;}
            &gt;
              Connect
            &lt;/button&gt;
            &lt;button
              className=&quot;btn&quot;
              onClick={disconnectWebSocket}
              disabled={!isConnected}
            &gt;
              Disconnect
            &lt;/button&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Send Message&lt;/h3&gt;

        &lt;div className=&quot;form-group&quot; style={{ marginBottom: &quot;15px&quot; }}&gt;
          &lt;label&gt;Message Type:&lt;/label&gt;
          &lt;div className=&quot;toggle&quot;&gt;
            &lt;button
              className={!isBinary ? &quot;active&quot; : &quot;&quot;}
              onClick={() =&gt; setIsBinary(false)}
            &gt;
              Text/JSON
            &lt;/button&gt;
            &lt;button
              className={isBinary ? &quot;active&quot; : &quot;&quot;}
              onClick={() =&gt; setIsBinary(true)}
            &gt;
              Binary (Hex)
            &lt;/button&gt;
          &lt;/div&gt;
        &lt;/div&gt;

        &lt;div className=&quot;form-group&quot; style={{ marginBottom: &quot;15px&quot; }}&gt;
          &lt;label&gt;{isBinary ? &quot;Binary Data (Hex):&quot; : &quot;Message (Text/JSON):&quot;}&lt;/label&gt;
          &lt;textarea
            className=&quot;form-control textarea&quot;
            value={message}
            onChange={(e) =&gt; setMessage(e.target.value)}
            placeholder={isBinary ? &quot;48 65 6c 6c 6f (Hello in hex)&quot; : &#39;{&quot;type&quot;: &quot;ping&quot;}&#39;}
            rows={4}
          /&gt;
        &lt;/div&gt;

        &lt;div style={{ display: &quot;flex&quot;, gap: &quot;10px&quot;, marginBottom: &quot;15px&quot; }}&gt;
          &lt;button
            className=&quot;btn btn-primary&quot;
            onClick={sendMessage}
            disabled={!isConnected || !message.trim()}
          &gt;
            Send
          &lt;/button&gt;

          {!isBinary &amp;&amp; message.includes(&#39;&quot;timestamp&quot;&#39;) &amp;&amp; (
            &lt;button
              className=&quot;btn&quot;
              onClick={updatePingTimestamp}
            &gt;
              Update Timestamp
            &lt;/button&gt;
          )}
        &lt;/div&gt;

        &lt;div className=&quot;section&quot;&gt;
          &lt;h4&gt;Example Messages&lt;/h4&gt;
          &lt;div style={{ display: &quot;flex&quot;, gap: &quot;10px&quot;, flexWrap: &quot;wrap&quot; }}&gt;
            {exampleMessages.map(example =&gt; (
              &lt;button
                key={example.name}
                className=&quot;btn&quot;
                onClick={() =&gt; loadExample(example)}
                style={{ fontSize: &quot;12px&quot; }}
              &gt;
                {example.name}
              &lt;/button&gt;
            ))}
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Message History ({messages.length})&lt;/h3&gt;

        &lt;div style={{ marginBottom: &quot;15px&quot; }}&gt;
          &lt;button
            className=&quot;btn&quot;
            onClick={clearMessages}
          &gt;
            Clear History
          &lt;/button&gt;
        &lt;/div&gt;

        {messages.length === 0 ? (
          &lt;div style={{ textAlign: &quot;center&quot;, color: &quot;#666&quot;, padding: &quot;20px&quot; }}&gt;
            No messages yet. Connect and send a message to see the conversation.
          &lt;/div&gt;
        ) : (
          &lt;div className=&quot;event-list&quot;&gt;
            {messages.map(msg =&gt; (
              &lt;div key={msg.id} className=&quot;event-item&quot;&gt;
                &lt;span className=&quot;timestamp&quot;&gt;{formatTimestamp(msg.timestamp)}&lt;/span&gt;
                &lt;span className={`name ${msg.type === &quot;sent&quot; ? &quot;sent&quot; : &quot;received&quot;}`}&gt;
                  {msg.type === &quot;sent&quot; ? &quot;→ SENT&quot; : &quot;← RECEIVED&quot;}
                  {msg.isBinary &amp;&amp; &quot; (BINARY)&quot;}
                &lt;/span&gt;
                &lt;div style={{ marginTop: &quot;5px&quot;, color: &quot;#333&quot;, fontFamily: &quot;monospace&quot;, fontSize: &quot;12px&quot; }}&gt;
                  {msg.data}
                &lt;/div&gt;
              &lt;/div&gt;
            ))}
          &lt;/div&gt;
        )}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}">
<input type="hidden" name="project[files][src/frontend/components/tabs/ScheduleTab.tsx]" value="import { useState } from &quot;react&quot;;
import type { AppState } from &quot;../../App&quot;;

interface TabProps {
  state: AppState;
  updateState: (updates: Partial&lt;AppState&gt;) =&gt; void;
  client: any;
  actorHandle: any;
}

interface AlarmEntry {
  id: string;
  scheduledFor: number;
  data?: any;
  status: &quot;scheduled&quot; | &quot;triggered&quot;;
}

export default function ScheduleTab({ state, actorHandle }: TabProps) {
  const [atTimestamp, setAtTimestamp] = useState(&quot;&quot;);
  const [afterDelay, setAfterDelay] = useState(&quot;5000&quot;);
  const [alarmData, setAlarmData] = useState(&quot;{}&quot;);
  const [response, setResponse] = useState(&quot;&quot;);
  const [isLoading, setIsLoading] = useState(false);
  const [alarmHistory, setAlarmHistory] = useState&lt;AlarmEntry[]&gt;([]);

  const getCurrentTimestamp = () =&gt; {
    const now = new Date();
    const offset = now.getTimezoneOffset() * 60000;
    const localTime = new Date(now.getTime() - offset);
    return localTime.toISOString().slice(0, 16);
  };

  const parseAlarmData = (dataString: string) =&gt; {
    try {
      return JSON.parse(dataString);
    } catch {
      return {};
    }
  };

  const callAction = async (actionName: string, args: any[]) =&gt; {
    return await actorHandle[actionName](...args);
  };

  const handleScheduleAt = async () =&gt; {
    if (!atTimestamp) return;

    setIsLoading(true);
    setResponse(&quot;&quot;);

    try {
      const timestamp = new Date(atTimestamp).getTime();
      const data = parseAlarmData(alarmData);

      const result = await callAction(&quot;scheduleAlarmAt&quot;, [timestamp, data]);
      setResponse(JSON.stringify(result, null, 2));

      // Add to local tracking
      setAlarmHistory(prev =&gt; [...prev, {
        id: result.id,
        scheduledFor: result.scheduledFor,
        data,
        status: &quot;scheduled&quot;,
      }]);
    } catch (error) {
      setResponse(`Error: ${error instanceof Error ? error.message : String(error)}`);
    } finally {
      setIsLoading(false);
    }
  };

  const handleScheduleAfter = async () =&gt; {
    const delay = parseInt(afterDelay);
    if (isNaN(delay) || delay &lt; 0) return;

    setIsLoading(true);
    setResponse(&quot;&quot;);

    try {
      const data = parseAlarmData(alarmData);

      const result = await callAction(&quot;scheduleAlarmAfter&quot;, [delay, data]);
      setResponse(JSON.stringify(result, null, 2));

      // Add to local tracking
      setAlarmHistory(prev =&gt; [...prev, {
        id: result.id,
        scheduledFor: result.scheduledFor,
        data,
        status: &quot;scheduled&quot;,
      }]);
    } catch (error) {
      setResponse(`Error: ${error instanceof Error ? error.message : String(error)}`);
    } finally {
      setIsLoading(false);
    }
  };

  const handleGetHistory = async () =&gt; {
    setIsLoading(true);
    setResponse(&quot;&quot;);

    try {
      const result = await callAction(&quot;getAlarmHistory&quot;, []);
      setResponse(JSON.stringify(result, null, 2));

      // Update status of triggered alarms
      const triggeredIds = result.map((entry: any) =&gt; entry.id);
      setAlarmHistory(prev =&gt; prev.map(alarm =&gt; ({
        ...alarm,
        status: triggeredIds.includes(alarm.id) ? &quot;triggered&quot; : alarm.status,
      })));
    } catch (error) {
      setResponse(`Error: ${error instanceof Error ? error.message : String(error)}`);
    } finally {
      setIsLoading(false);
    }
  };

  const handleClearHistory = async () =&gt; {
    setIsLoading(true);
    setResponse(&quot;&quot;);

    try {
      const result = await callAction(&quot;clearAlarmHistory&quot;, []);
      setResponse(JSON.stringify(result, null, 2));
      setAlarmHistory([]);
    } catch (error) {
      setResponse(`Error: ${error instanceof Error ? error.message : String(error)}`);
    } finally {
      setIsLoading(false);
    }
  };

  const formatTime = (timestamp: number) =&gt; {
    return new Date(timestamp).toLocaleString();
  };

  if (state.actorName !== &quot;demo&quot;) {
    return (
      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Scheduling&lt;/h3&gt;
        &lt;p&gt;Scheduling features are only available for the &lt;code&gt;demo&lt;/code&gt; actor. Please select the demo actor in the header.&lt;/p&gt;
      &lt;/div&gt;
    );
  }

  return (
    &lt;div&gt;
      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Schedule Alarm At Specific Time&lt;/h3&gt;
        &lt;div className=&quot;form-grid&quot;&gt;
          &lt;div className=&quot;form-group&quot;&gt;
            &lt;label&gt;Timestamp:&lt;/label&gt;
            &lt;input
              className=&quot;form-control&quot;
              type=&quot;datetime-local&quot;
              value={atTimestamp}
              onChange={(e) =&gt; setAtTimestamp(e.target.value)}
              min={getCurrentTimestamp()}
            /&gt;
          &lt;/div&gt;

          &lt;div className=&quot;form-group&quot;&gt;
            &lt;label&gt;Alarm Data (JSON):&lt;/label&gt;
            &lt;input
              className=&quot;form-control&quot;
              type=&quot;text&quot;
              value={alarmData}
              onChange={(e) =&gt; setAlarmData(e.target.value)}
              placeholder=&#39;{&quot;message&quot;: &quot;Hello&quot;}&#39;
            /&gt;
          &lt;/div&gt;

          &lt;button
            className=&quot;btn btn-primary&quot;
            onClick={handleScheduleAt}
            disabled={isLoading || !atTimestamp}
          &gt;
            schedule.at()
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Schedule Alarm After Delay&lt;/h3&gt;
        &lt;div className=&quot;form-grid&quot;&gt;
          &lt;div className=&quot;form-group&quot;&gt;
            &lt;label&gt;Delay (ms):&lt;/label&gt;
            &lt;input
              className=&quot;form-control&quot;
              type=&quot;number&quot;
              value={afterDelay}
              onChange={(e) =&gt; setAfterDelay(e.target.value)}
              min=&quot;0&quot;
              step=&quot;1000&quot;
            /&gt;
          &lt;/div&gt;

          &lt;div className=&quot;form-group&quot;&gt;
            &lt;label&gt;Alarm Data (JSON):&lt;/label&gt;
            &lt;input
              className=&quot;form-control&quot;
              type=&quot;text&quot;
              value={alarmData}
              onChange={(e) =&gt; setAlarmData(e.target.value)}
              placeholder=&#39;{&quot;message&quot;: &quot;Hello&quot;}&#39;
            /&gt;
          &lt;/div&gt;

          &lt;button
            className=&quot;btn btn-primary&quot;
            onClick={handleScheduleAfter}
            disabled={isLoading}
          &gt;
            schedule.after()
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Alarm Management&lt;/h3&gt;
        &lt;div style={{ display: &quot;flex&quot;, gap: &quot;10px&quot;, marginBottom: &quot;15px&quot; }}&gt;
          &lt;button
            className=&quot;btn&quot;
            onClick={handleGetHistory}
            disabled={isLoading}
          &gt;
            Get Alarm History
          &lt;/button&gt;
          &lt;button
            className=&quot;btn&quot;
            onClick={handleClearHistory}
            disabled={isLoading}
          &gt;
            Clear History
          &lt;/button&gt;
        &lt;/div&gt;

        {alarmHistory.length &gt; 0 &amp;&amp; (
          &lt;div&gt;
            &lt;h4&gt;Scheduled Alarms&lt;/h4&gt;
            &lt;div style={{ marginBottom: &quot;15px&quot; }}&gt;
              {alarmHistory.map(alarm =&gt; (
                &lt;div
                  key={alarm.id}
                  style={{
                    padding: &quot;10px&quot;,
                    border: &quot;1px solid #ddd&quot;,
                    borderRadius: &quot;4px&quot;,
                    marginBottom: &quot;5px&quot;,
                    background: alarm.status === &quot;triggered&quot; ? &quot;#d4edda&quot; : &quot;#fff3cd&quot;,
                  }}
                &gt;
                  &lt;strong&gt;{alarm.id}&lt;/strong&gt; - {alarm.status === &quot;triggered&quot; ? &quot;✅ Triggered&quot; : &quot;⏰ Scheduled&quot;}
                  &lt;br /&gt;
                  Scheduled for: {formatTime(alarm.scheduledFor)}
                  {alarm.data &amp;&amp; Object.keys(alarm.data).length &gt; 0 &amp;&amp; (
                    &lt;&gt;
                      &lt;br /&gt;
                      Data: {JSON.stringify(alarm.data)}
                    &lt;/&gt;
                  )}
                &lt;/div&gt;
              ))}
            &lt;/div&gt;
          &lt;/div&gt;
        )}

        {response &amp;&amp; (
          &lt;div className=&quot;response&quot;&gt;
            {response}
          &lt;/div&gt;
        )}
      &lt;/div&gt;
    &lt;/div&gt;
  );
}">
<input type="hidden" name="project[files][src/frontend/components/tabs/SleepTab.tsx]" value="import { useState } from &quot;react&quot;;
import type { AppState } from &quot;../../App&quot;;

interface TabProps {
  state: AppState;
  updateState: (updates: Partial&lt;AppState&gt;) =&gt; void;
  client: any;
  actorHandle: any;
}

export default function SleepTab({ state, actorHandle }: TabProps) {
  const [response, setResponse] = useState(&quot;&quot;);
  const [isLoading, setIsLoading] = useState(false);

  const callAction = async (actionName: string, args: any[] = []) =&gt; {
    return await actorHandle[actionName](...args);
  };

  const handleTriggerSleep = async () =&gt; {
    setIsLoading(true);
    setResponse(&quot;&quot;);

    try {
      const result = await callAction(&quot;triggerSleep&quot;);
      setResponse(JSON.stringify(result, null, 2));
    } catch (error) {
      setResponse(`Error: ${error instanceof Error ? error.message : String(error)}`);
    } finally {
      setIsLoading(false);
    }
  };

  const handleGetLifecycleInfo = async () =&gt; {
    setIsLoading(true);
    setResponse(&quot;&quot;);

    try {
      const result = await callAction(&quot;getLifecycleInfo&quot;);
      setResponse(JSON.stringify(result, null, 2));
    } catch (error) {
      setResponse(`Error: ${error instanceof Error ? error.message : String(error)}`);
    } finally {
      setIsLoading(false);
    }
  };

  if (state.actorName !== &quot;demo&quot;) {
    return (
      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Sleep &amp; Lifecycle&lt;/h3&gt;
        &lt;p&gt;Sleep and lifecycle features are only available for the &lt;code&gt;demo&lt;/code&gt; actor. Please select the demo actor in the header.&lt;/p&gt;
      &lt;/div&gt;
    );
  }

  return (
    &lt;div&gt;
      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Force Sleep&lt;/h3&gt;
        &lt;p style={{ marginBottom: &quot;15px&quot; }}&gt;
          The &lt;code&gt;sleep()&lt;/code&gt; method forces an actor to go dormant immediately.
          This is useful for testing actor lifecycle and persistence.
        &lt;/p&gt;

        &lt;button
          className=&quot;btn btn-primary&quot;
          onClick={handleTriggerSleep}
          disabled={isLoading}
        &gt;
          {isLoading ? &quot;Triggering...&quot; : &quot;Trigger Sleep&quot;}
        &lt;/button&gt;
      &lt;/div&gt;

      &lt;div className=&quot;section&quot;&gt;
        &lt;h3&gt;Lifecycle Information&lt;/h3&gt;
        &lt;p style={{ marginBottom: &quot;15px&quot; }}&gt;
          View how many times the actor has been started and stopped.
          This demonstrates the actor lifecycle across sleep/wake cycles.
        &lt;/p&gt;

        &lt;button
          className=&quot;btn&quot;
          onClick={handleGetLifecycleInfo}
          disabled={isLoading}
        &gt;
          Get Lifecycle Info
        &lt;/button&gt;
      &lt;/div&gt;

      {response &amp;&amp; (
        &lt;div className=&quot;section&quot;&gt;
          &lt;h3&gt;Response&lt;/h3&gt;
          &lt;div className=&quot;response&quot;&gt;
            {response}
          &lt;/div&gt;
        &lt;/div&gt;
      )}
    &lt;/div&gt;
  );
}">
<input type="hidden" name="project[description]" value="generated by https://pkg.pr.new">
<input type="hidden" name="project[template]" value="node">
<input type="hidden" name="project[title]" value="example-kitchen-sink">
</form>
<script>document.getElementById("mainForm").submit();</script>

</body></html>