create-fluxstack 1.13.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/LLMD/patterns/anti-patterns.md +100 -0
  2. package/LLMD/reference/routing.md +39 -39
  3. package/LLMD/resources/live-auth.md +20 -2
  4. package/LLMD/resources/live-components.md +94 -10
  5. package/LLMD/resources/live-logging.md +95 -33
  6. package/LLMD/resources/live-upload.md +59 -8
  7. package/app/client/index.html +2 -2
  8. package/app/client/public/favicon.svg +46 -0
  9. package/app/client/src/App.tsx +2 -1
  10. package/app/client/src/assets/fluxstack-static.svg +46 -0
  11. package/app/client/src/assets/fluxstack.svg +183 -0
  12. package/app/client/src/components/AppLayout.tsx +138 -9
  13. package/app/client/src/components/BackButton.tsx +13 -13
  14. package/app/client/src/components/DemoPage.tsx +4 -4
  15. package/app/client/src/live/AuthDemo.tsx +23 -21
  16. package/app/client/src/live/ChatDemo.tsx +2 -2
  17. package/app/client/src/live/CounterDemo.tsx +12 -12
  18. package/app/client/src/live/FormDemo.tsx +2 -2
  19. package/app/client/src/live/LiveDebuggerPanel.tsx +779 -0
  20. package/app/client/src/live/RoomChatDemo.tsx +24 -16
  21. package/app/client/src/main.tsx +13 -13
  22. package/app/client/src/pages/ApiTestPage.tsx +6 -6
  23. package/app/client/src/pages/HomePage.tsx +80 -52
  24. package/app/server/live/LiveAdminPanel.ts +1 -0
  25. package/app/server/live/LiveChat.ts +78 -77
  26. package/app/server/live/LiveCounter.ts +1 -1
  27. package/app/server/live/LiveForm.ts +1 -0
  28. package/app/server/live/LiveLocalCounter.ts +38 -37
  29. package/app/server/live/LiveProtectedChat.ts +1 -0
  30. package/app/server/live/LiveRoomChat.ts +1 -0
  31. package/app/server/live/LiveUpload.ts +1 -0
  32. package/app/server/live/register-components.ts +19 -19
  33. package/config/system/runtime.config.ts +4 -0
  34. package/core/build/optimizer.ts +235 -235
  35. package/core/client/components/Live.tsx +17 -11
  36. package/core/client/components/LiveDebugger.tsx +1324 -0
  37. package/core/client/hooks/AdaptiveChunkSizer.ts +215 -215
  38. package/core/client/hooks/useLiveComponent.ts +11 -1
  39. package/core/client/hooks/useLiveDebugger.ts +392 -0
  40. package/core/client/index.ts +14 -0
  41. package/core/plugins/built-in/index.ts +134 -134
  42. package/core/plugins/built-in/live-components/commands/create-live-component.ts +4 -0
  43. package/core/plugins/built-in/vite/index.ts +75 -21
  44. package/core/server/index.ts +15 -15
  45. package/core/server/live/ComponentRegistry.ts +55 -26
  46. package/core/server/live/FileUploadManager.ts +188 -24
  47. package/core/server/live/LiveDebugger.ts +462 -0
  48. package/core/server/live/LiveLogger.ts +38 -5
  49. package/core/server/live/LiveRoomManager.ts +17 -1
  50. package/core/server/live/StateSignature.ts +87 -27
  51. package/core/server/live/WebSocketConnectionManager.ts +11 -10
  52. package/core/server/live/auto-generated-components.ts +1 -1
  53. package/core/server/live/websocket-plugin.ts +233 -8
  54. package/core/server/plugins/static-files-plugin.ts +179 -69
  55. package/core/types/build.ts +219 -219
  56. package/core/types/plugin.ts +107 -107
  57. package/core/types/types.ts +145 -9
  58. package/core/utils/logger/startup-banner.ts +82 -82
  59. package/core/utils/version.ts +6 -6
  60. package/package.json +1 -1
  61. package/app/client/src/assets/react.svg +0 -1
@@ -1,56 +1,100 @@
1
1
  # Live Logging
2
2
 
3
- **Version:** 1.12.0 | **Updated:** 2025-02-12
3
+ **Version:** 1.12.1 | **Updated:** 2025-02-22
4
4
 
5
5
  ## Quick Facts
6
6
 
7
7
  - Per-component logging control — silent by default
8
- - Opt-in via `static logging` property on LiveComponent subclasses
8
+ - Two output channels: **console** (`LIVE_LOGGING`) and **debug panel** (`DEBUG_LIVE`)
9
+ - Both off by default — opt-in only
9
10
  - 6 categories: `lifecycle`, `messages`, `state`, `performance`, `rooms`, `websocket`
10
- - Global (non-component) logs controlled by `LIVE_LOGGING` env var
11
11
  - `console.error` always visible regardless of config
12
+ - All `liveLog`/`liveWarn` calls are forwarded to the Live Debugger as `LOG` events when `DEBUG_LIVE=true`
12
13
 
13
- ## Usage
14
+ ## Two Logging Channels
14
15
 
15
- ### Enable Logging on a Component
16
+ | Channel | Env Var | Default | Purpose |
17
+ |---------|---------|---------|---------|
18
+ | **Console** | `LIVE_LOGGING` | `false` | Server terminal output |
19
+ | **Debug Panel** | `DEBUG_LIVE` | `false` | Live Debugger WebSocket stream |
20
+
21
+ The debug panel receives **all** `liveLog`/`liveWarn` calls as `LOG` events (with `category`, `level`, `message`, and `details`) regardless of the `LIVE_LOGGING` console setting. This keeps the console clean while making everything visible in the debug panel.
22
+
23
+ ### Recommended Workflow
24
+
25
+ - **Normal development**: both off — clean console, no debug overhead
26
+ - **Debugging live components**: `DEBUG_LIVE=true` — open the debug panel at `/api/live/debug/ws`
27
+ - **Quick console debugging**: `LIVE_LOGGING=lifecycle,state` — targeted categories to console
28
+ - **Per-component debugging**: `static logging = true` on the specific component class
29
+
30
+ ## Console Logging
31
+
32
+ ### Per-Component (static logging)
16
33
 
17
34
  ```typescript
18
- // app/server/live/LiveCounter.ts
19
- export class LiveCounter extends LiveComponent<typeof LiveCounter.defaultState> {
20
- static componentName = 'LiveCounter'
35
+ // app/server/live/LiveChat.ts
36
+ export class LiveChat extends LiveComponent<typeof LiveChat.defaultState> {
37
+ static componentName = 'LiveChat'
21
38
 
22
- // ✅ All categories
39
+ // ✅ All categories to console
23
40
  static logging = true
24
41
 
25
42
  // ✅ Specific categories only
26
- static logging = ['lifecycle', 'messages', 'state', 'rooms'] as const
43
+ static logging = ['lifecycle', 'rooms'] as const
27
44
 
28
45
  // ✅ Silent (default — omit property or set false)
29
- // static logging = false
46
+ // No static logging needed
30
47
  }
31
48
  ```
32
49
 
33
- ### Global Logs (Non-Component)
50
+ ### Global (LIVE_LOGGING env var)
34
51
 
35
- Logs not tied to a specific component (room cleanup, key rotation, etc.) are controlled by the `LIVE_LOGGING` env var:
52
+ Logs not tied to a specific component (connection cleanup, key rotation, etc.):
36
53
 
37
54
  ```bash
38
55
  # .env
39
- LIVE_LOGGING=true # All global logs
56
+ LIVE_LOGGING=true # All global logs to console
40
57
  LIVE_LOGGING=lifecycle,rooms # Specific categories only
41
58
  # (unset or 'false') # Silent (default)
42
59
  ```
43
60
 
61
+ ## Debug Panel (DEBUG_LIVE)
62
+
63
+ When `DEBUG_LIVE=true`, all `liveLog`/`liveWarn` calls emit `LOG` events to the Live Debugger, regardless of `LIVE_LOGGING` or `static logging` settings.
64
+
65
+ ```bash
66
+ # .env
67
+ DEBUG_LIVE=true # Enable debug panel events
68
+ ```
69
+
70
+ Each `LOG` event contains:
71
+
72
+ ```typescript
73
+ {
74
+ type: 'LOG',
75
+ componentId: string | null,
76
+ componentName: null,
77
+ data: {
78
+ category: 'lifecycle' | 'messages' | 'state' | 'performance' | 'rooms' | 'websocket',
79
+ level: 'info' | 'warn',
80
+ message: string,
81
+ details?: unknown // Extra args passed to liveLog/liveWarn
82
+ }
83
+ }
84
+ ```
85
+
86
+ The debug panel also receives all other debug events (`COMPONENT_MOUNT`, `STATE_CHANGE`, `ACTION_CALL`, etc.) — see [Live Components](./live-components.md) for the full event list.
87
+
44
88
  ## Categories
45
89
 
46
90
  | Category | What It Logs |
47
91
  |----------|-------------|
48
92
  | `lifecycle` | Mount, unmount, rehydration, recovery, migration |
49
- | `messages` | Received/sent WebSocket messages, file uploads |
93
+ | `messages` | Received/sent WebSocket messages, file uploads, queue operations |
50
94
  | `state` | Signing, backup, compression, encryption, validation |
51
95
  | `performance` | Monitoring init, alerts, optimization suggestions |
52
96
  | `rooms` | Room create/join/leave, emit, broadcast |
53
- | `websocket` | Connection open/close, auth |
97
+ | `websocket` | Connection open/close/cleanup, pool management, auth |
54
98
 
55
99
  ## Type Definition
56
100
 
@@ -69,12 +113,12 @@ static logging = ['lifecycle', 'messages'] as const
69
113
 
70
114
  ## API (Framework Internal)
71
115
 
72
- These functions are used by the framework — app developers only need `static logging`:
116
+ These functions are used by the framework — app developers only need `static logging` or env vars:
73
117
 
74
118
  ```typescript
75
119
  import { liveLog, liveWarn, registerComponentLogging, unregisterComponentLogging } from '@core/server/live'
76
120
 
77
- // Log gated by component config
121
+ // Log gated by component config (console) + always forwarded to debug panel
78
122
  liveLog('lifecycle', componentId, '🚀 Mounted component')
79
123
  liveLog('rooms', componentId, `📡 Joined room '${roomId}'`)
80
124
 
@@ -89,70 +133,88 @@ unregisterComponentLogging(componentId)
89
133
  ## How It Works
90
134
 
91
135
  1. **Mount**: `ComponentRegistry` reads `static logging` from the class and calls `registerComponentLogging(componentId, config)`
92
- 2. **Runtime**: All `liveLog()`/`liveWarn()` calls check the registry before emitting
136
+ 2. **Runtime**: All `liveLog()`/`liveWarn()` calls:
137
+ - Forward to the Live Debugger as `LOG` events (when `DEBUG_LIVE=true`)
138
+ - Check the registry before emitting to console (when `LIVE_LOGGING` or `static logging` is active)
93
139
  3. **Unmount**: `unregisterComponentLogging(componentId)` removes the entry
94
140
  4. **Global logs**: Fall back to `LIVE_LOGGING` env var when `componentId` is `null`
95
141
 
96
142
  ## Examples
97
143
 
98
- ### Debug a Specific Component
144
+ ### Debug via Panel (Recommended)
145
+
146
+ ```bash
147
+ # .env
148
+ DEBUG_LIVE=true
149
+ # No LIVE_LOGGING needed — console stays clean
150
+ ```
151
+
152
+ Open the debug panel WebSocket at `/api/live/debug/ws` to see all events in real-time.
153
+
154
+ ### Debug a Specific Component (Console)
99
155
 
100
156
  ```typescript
101
- // Only this component will show logs
157
+ // Only this component will show console logs
102
158
  export class LiveChat extends LiveComponent<typeof LiveChat.defaultState> {
103
159
  static componentName = 'LiveChat'
104
- static logging = true // See everything for this component
160
+ static logging = true // See everything for this component in console
105
161
  }
106
162
 
107
- // All other components remain silent
163
+ // All other components remain silent in console
108
164
  export class LiveCounter extends LiveComponent<typeof LiveCounter.defaultState> {
109
165
  static componentName = 'LiveCounter'
110
- // No static logging → silent
166
+ // No static logging → silent in console
111
167
  }
112
168
  ```
113
169
 
114
- ### Monitor Only Room Activity
170
+ ### Monitor Only Room Activity (Console)
115
171
 
116
172
  ```typescript
117
173
  export class LiveChat extends LiveComponent<typeof LiveChat.defaultState> {
118
174
  static componentName = 'LiveChat'
119
- static logging = ['rooms'] as const // Only room events
175
+ static logging = ['rooms'] as const // Only room events in console
120
176
  }
121
177
  ```
122
178
 
123
179
  ### Production: Silent Everywhere
124
180
 
125
181
  ```bash
126
- # .env (no LIVE_LOGGING set)
127
- # All components without static logging → silent
128
- # Components with static logging still log (remove for production)
182
+ # .env (no LIVE_LOGGING, no DEBUG_LIVE)
183
+ # Console: silent
184
+ # Debug panel: disabled
129
185
  ```
130
186
 
131
187
  ## Files Reference
132
188
 
133
189
  | File | Purpose |
134
190
  |------|---------|
135
- | `core/server/live/LiveLogger.ts` | Logger implementation, registry, shouldLog logic |
136
- | `core/server/live/ComponentRegistry.ts` | Reads `static logging` on mount/unmount |
191
+ | `core/server/live/LiveLogger.ts` | Logger implementation, registry, shouldLog logic, debugger forwarding |
192
+ | `core/server/live/LiveDebugger.ts` | Debug event bus, `LOG` event type, debug client management |
193
+ | `core/server/live/ComponentRegistry.ts` | Reads `static logging` on mount/unmount, uses `liveLog` |
137
194
  | `core/server/live/websocket-plugin.ts` | Uses `liveLog` for WebSocket events |
195
+ | `core/server/live/WebSocketConnectionManager.ts` | Uses `liveLog`/`liveWarn` for connection pool management |
196
+ | `core/server/live/FileUploadManager.ts` | Uses `liveLog`/`liveWarn` for upload operations |
138
197
  | `core/server/live/StateSignature.ts` | Uses `liveLog`/`liveWarn` for state operations |
139
198
  | `core/server/live/LiveRoomManager.ts` | Uses `liveLog` for room lifecycle |
140
199
  | `core/server/live/LiveComponentPerformanceMonitor.ts` | Uses `liveLog`/`liveWarn` for perf |
200
+ | `config/system/runtime.config.ts` | `DEBUG_LIVE` env var config |
141
201
  | `core/types/types.ts` | `LiveComponent` base class with `static logging` property |
142
202
 
143
203
  ## Critical Rules
144
204
 
145
205
  **ALWAYS:**
146
206
  - Use `as const` on logging arrays for type safety
147
- - Keep components silent by default in production
207
+ - Keep components silent by default (no `static logging`)
208
+ - Use `DEBUG_LIVE=true` for debugging instead of `static logging` on components
148
209
  - Use specific categories instead of `true` when possible
149
210
 
150
211
  **NEVER:**
151
212
  - Use `console.log` directly in Live Component code — use `liveLog()`
152
213
  - Forget that `console.error` is always visible (not gated)
214
+ - Enable `LIVE_LOGGING` or `DEBUG_LIVE` in production
153
215
 
154
216
  ## Related
155
217
 
156
218
  - [Live Components](./live-components.md) - Base component system
157
219
  - [Live Rooms](./live-rooms.md) - Room system (logged under `rooms` category)
158
- - [Environment Variables](../config/environment-vars.md) - `LIVE_LOGGING` reference
220
+ - [Environment Variables](../config/environment-vars.md) - `LIVE_LOGGING` and `DEBUG_LIVE` reference
@@ -1,6 +1,6 @@
1
1
  # Live Upload (Chunked Upload via WebSocket)
2
2
 
3
- **Version:** 1.11.0 | **Updated:** 2025-02-08
3
+ **Version:** 1.14.0 | **Updated:** 2025-02-21
4
4
 
5
5
  ## Overview
6
6
 
@@ -8,6 +8,19 @@ FluxStack supports chunked file upload over the Live Components WebSocket. The
8
8
  server tracks progress and assembles the file in `uploads/`. The client streams
9
9
  chunks without loading the entire file into memory.
10
10
 
11
+ ## Security Features
12
+
13
+ The upload system includes multiple layers of security:
14
+
15
+ - **MIME type allowlist** - Only safe file types accepted (images, PDF, text, JSON, archives)
16
+ - **Extension blocklist** - 31 dangerous extensions blocked (.exe, .bat, .sh, .dll, etc.)
17
+ - **Double extension prevention** - Detects `malware.exe.jpg` style attacks
18
+ - **Magic bytes validation** - Verifies actual file content matches claimed MIME type
19
+ - **Per-user upload quota** - 500MB/day per user to prevent disk exhaustion
20
+ - **File size limit** - 50MB max per file
21
+ - **Filename sanitization** - Path traversal prevention via `path.basename()`
22
+ - **Stale upload cleanup** - Abandoned uploads removed after 60 seconds
23
+
11
24
  ## Server: LiveUpload Component
12
25
 
13
26
  ```typescript
@@ -18,6 +31,8 @@ import { liveUploadDefaultState, type LiveUploadState } from '@app/shared'
18
31
  export const defaultState: LiveUploadState = liveUploadDefaultState
19
32
 
20
33
  export class LiveUpload extends LiveComponent<LiveUploadState> {
34
+ static componentName = 'LiveUpload'
35
+ static publicActions = ['startUpload', 'updateProgress', 'completeUpload', 'failUpload', 'reset'] as const
21
36
  static defaultState = defaultState
22
37
 
23
38
  constructor(initialState: Partial<typeof defaultState>, ws: any, options?: { room?: string; userId?: string }) {
@@ -109,22 +124,58 @@ export function UploadDemo() {
109
124
 
110
125
  1. Client calls `startUpload()` (Live Component action).
111
126
  2. Client streams file chunks over WebSocket with `useChunkedUpload`.
112
- 3. Server assembles file in `uploads/` and returns `/uploads/...`.
113
- 4. Client maps to `/api/uploads/...` for access.
127
+ 3. Server receives chunks and validates size/count.
128
+ 4. On completion, server validates **magic bytes** against claimed MIME type.
129
+ 5. Server assembles file in `uploads/` with UUID filename and returns `/uploads/...`.
130
+ 6. Client maps to `/api/uploads/...` for access.
131
+
132
+ ## Magic Bytes Validation
133
+
134
+ The server validates actual file content against known magic byte signatures before assembling:
135
+
136
+ | MIME Type | Magic Bytes |
137
+ |-----------|-------------|
138
+ | `image/jpeg` | `FF D8 FF` |
139
+ | `image/png` | `89 50 4E 47 0D 0A 1A 0A` |
140
+ | `image/gif` | `47 49 46 38 37 61` or `47 49 46 38 39 61` |
141
+ | `image/webp` | `52 49 46 46` (RIFF header) |
142
+ | `application/pdf` | `25 50 44 46` (%PDF) |
143
+ | `application/zip` | `50 4B 03 04` or `50 4B 05 06` |
144
+ | `application/gzip` | `1F 8B` |
145
+
146
+ Text-based types (text/plain, text/csv, application/json, image/svg+xml) skip binary validation.
147
+
148
+ ## Per-User Upload Quotas
149
+
150
+ Each authenticated user has a daily upload quota (default: 500MB/day):
151
+
152
+ - Quota is checked before upload starts
153
+ - Quota is reserved when upload begins (even if upload doesn't complete)
154
+ - Quotas reset daily
155
+ - Anonymous uploads (no userId) bypass quota checks
156
+
157
+ ```typescript
158
+ // Check user's remaining quota
159
+ const usage = fileUploadManager.getUserUploadUsage(userId)
160
+ // { used: 104857600, limit: 524288000, remaining: 419430400 }
161
+ ```
114
162
 
115
163
  ## Error Handling
116
164
 
117
165
  - If an action throws, the error surfaces in `live.$error` on the client.
118
166
  - The widget shows `localError || state.error || $error`.
167
+ - Magic bytes validation failure: `"File content does not match claimed type 'image/jpeg'"`
168
+ - Quota exceeded: `"Upload quota exceeded for user"`
169
+ - Double extension: `"Suspicious double extension detected: .exe in malware.exe.jpg"`
119
170
 
120
171
  ## Files Involved
121
172
 
122
173
  **Server**
123
174
  - `app/server/live/LiveUpload.ts`
124
- - `core/server/live/FileUploadManager.ts`
125
- - `core/server/live/websocket-plugin.ts`
175
+ - `core/server/live/FileUploadManager.ts` (chunk handling, magic bytes, quotas, file assembly)
176
+ - `core/server/live/websocket-plugin.ts` (upload message routing, userId passthrough)
126
177
 
127
178
  **Client**
128
- - `core/client/hooks/useChunkedUpload.ts`
129
- - `core/client/hooks/useLiveUpload.ts`
130
- - `app/client/src/components/LiveUploadWidget.tsx`
179
+ - `core/client/hooks/useChunkedUpload.ts` (streaming chunks)
180
+ - `core/client/hooks/useLiveUpload.ts` (Live Component wrapper)
181
+ - `app/client/src/components/LiveUploadWidget.tsx` (UI)
@@ -2,9 +2,9 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
- <link rel="icon" type="image/svg+xml" href="/vite.svg" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>Vite + React + TS</title>
7
+ <title>FluxStack</title>
8
8
  </head>
9
9
  <body>
10
10
  <div id="root"></div>
@@ -0,0 +1,46 @@
1
+ <svg viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <linearGradient id="outer" x1="50%" y1="100%" x2="50%" y2="0%">
4
+ <stop stop-color="#4338CA"/>
5
+ <stop offset=".25" stop-color="#7C3AED"/>
6
+ <stop offset=".5" stop-color="#A855F7"/>
7
+ <stop offset=".75" stop-color="#C084FC"/>
8
+ <stop offset="1" stop-color="#22D3EE"/>
9
+ </linearGradient>
10
+ <linearGradient id="mid" x1="50%" y1="100%" x2="50%" y2="0%">
11
+ <stop stop-color="#6D28D9"/>
12
+ <stop offset=".3" stop-color="#A855F7"/>
13
+ <stop offset=".7" stop-color="#C4B5FD"/>
14
+ <stop offset="1" stop-color="#67E8F9"/>
15
+ </linearGradient>
16
+ <linearGradient id="inner" x1="50%" y1="100%" x2="50%" y2="0%">
17
+ <stop stop-color="#A78BFA"/>
18
+ <stop offset=".4" stop-color="#C4B5FD"/>
19
+ <stop offset=".8" stop-color="#E0E7FF"/>
20
+ <stop offset="1" stop-color="#ECFEFF"/>
21
+ </linearGradient>
22
+ <linearGradient id="core" x1="50%" y1="100%" x2="50%" y2="0%">
23
+ <stop stop-color="#DDD6FE"/>
24
+ <stop offset="1" stop-color="#F0F9FF"/>
25
+ </linearGradient>
26
+ </defs>
27
+
28
+ <!-- Main outer flame -->
29
+ <path fill="url(#outer)" opacity=".92"
30
+ d="M64 10C62 10 54 26 50 36C46 46 36 48 34 60C32 72 35 82 40 90C46 98 54 106 64 108C74 106 82 98 88 90C93 82 96 72 94 60C92 48 82 46 78 36C74 26 84 20 86 14C82 22 76 30 74 38C72 48 78 56 76 64C74 54 70 40 68 30C66 20 64 10 64 10Z"/>
31
+
32
+ <!-- Middle flame -->
33
+ <path fill="url(#mid)" opacity=".88"
34
+ d="M64 30C62 30 52 46 48 56C44 66 46 78 50 86C54 94 58 100 64 102C70 100 74 94 78 86C82 78 84 66 80 56C78 48 82 38 82 30C80 38 76 46 74 54C72 44 68 36 64 30Z"/>
35
+
36
+ <!-- Inner flame -->
37
+ <path fill="url(#inner)" opacity=".85"
38
+ d="M64 50C60 56 54 66 54 76C54 86 58 94 64 100C70 94 74 86 74 76C74 66 68 56 64 50Z"/>
39
+
40
+ <!-- Hot core -->
41
+ <path fill="url(#core)" opacity=".7"
42
+ d="M64 72C60 78 56 84 58 92C60 97 62 100 64 101C66 100 68 97 70 92C72 84 68 78 64 72Z"/>
43
+
44
+ <!-- White hot center -->
45
+ <ellipse cx="64" cy="92" rx="5" ry="7" fill="#F0F9FF" opacity=".5"/>
46
+ </svg>
@@ -1,7 +1,7 @@
1
1
  import { useState, useEffect } from 'react'
2
2
  import { Routes, Route } from 'react-router'
3
3
  import { api } from './lib/eden-api'
4
- import { LiveComponentsProvider } from '@/core/client'
4
+ import { LiveComponentsProvider, LiveDebugger } from '@/core/client'
5
5
  import { FormDemo } from './live/FormDemo'
6
6
  import { CounterDemo } from './live/CounterDemo'
7
7
  import { UploadDemo } from './live/UploadDemo'
@@ -153,6 +153,7 @@ function App() {
153
153
  debug={false}
154
154
  >
155
155
  <AppContent />
156
+ {import.meta.env.DEV && <LiveDebugger />}
156
157
  </LiveComponentsProvider>
157
158
  )
158
159
  }
@@ -0,0 +1,46 @@
1
+ <svg viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <linearGradient id="outer" x1="50%" y1="100%" x2="50%" y2="0%">
4
+ <stop stop-color="#4338CA"/>
5
+ <stop offset=".25" stop-color="#7C3AED"/>
6
+ <stop offset=".5" stop-color="#A855F7"/>
7
+ <stop offset=".75" stop-color="#C084FC"/>
8
+ <stop offset="1" stop-color="#22D3EE"/>
9
+ </linearGradient>
10
+ <linearGradient id="mid" x1="50%" y1="100%" x2="50%" y2="0%">
11
+ <stop stop-color="#6D28D9"/>
12
+ <stop offset=".3" stop-color="#A855F7"/>
13
+ <stop offset=".7" stop-color="#C4B5FD"/>
14
+ <stop offset="1" stop-color="#67E8F9"/>
15
+ </linearGradient>
16
+ <linearGradient id="inner" x1="50%" y1="100%" x2="50%" y2="0%">
17
+ <stop stop-color="#A78BFA"/>
18
+ <stop offset=".4" stop-color="#C4B5FD"/>
19
+ <stop offset=".8" stop-color="#E0E7FF"/>
20
+ <stop offset="1" stop-color="#ECFEFF"/>
21
+ </linearGradient>
22
+ <linearGradient id="core" x1="50%" y1="100%" x2="50%" y2="0%">
23
+ <stop stop-color="#DDD6FE"/>
24
+ <stop offset="1" stop-color="#F0F9FF"/>
25
+ </linearGradient>
26
+ </defs>
27
+
28
+ <!-- Main outer flame -->
29
+ <path fill="url(#outer)" opacity=".92"
30
+ d="M64 10C62 10 54 26 50 36C46 46 36 48 34 60C32 72 35 82 40 90C46 98 54 106 64 108C74 106 82 98 88 90C93 82 96 72 94 60C92 48 82 46 78 36C74 26 84 20 86 14C82 22 76 30 74 38C72 48 78 56 76 64C74 54 70 40 68 30C66 20 64 10 64 10Z"/>
31
+
32
+ <!-- Middle flame -->
33
+ <path fill="url(#mid)" opacity=".88"
34
+ d="M64 30C62 30 52 46 48 56C44 66 46 78 50 86C54 94 58 100 64 102C70 100 74 94 78 86C82 78 84 66 80 56C78 48 82 38 82 30C80 38 76 46 74 54C72 44 68 36 64 30Z"/>
35
+
36
+ <!-- Inner flame -->
37
+ <path fill="url(#inner)" opacity=".85"
38
+ d="M64 50C60 56 54 66 54 76C54 86 58 94 64 100C70 94 74 86 74 76C74 66 68 56 64 50Z"/>
39
+
40
+ <!-- Hot core -->
41
+ <path fill="url(#core)" opacity=".7"
42
+ d="M64 72C60 78 56 84 58 92C60 97 62 100 64 101C66 100 68 97 70 92C72 84 68 78 64 72Z"/>
43
+
44
+ <!-- White hot center -->
45
+ <ellipse cx="64" cy="92" rx="5" ry="7" fill="#F0F9FF" opacity=".5"/>
46
+ </svg>
@@ -0,0 +1,183 @@
1
+ <svg viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <linearGradient id="outer" x1="50%" y1="100%" x2="50%" y2="0%">
4
+ <stop stop-color="#4338CA"/>
5
+ <stop offset=".25" stop-color="#7C3AED"/>
6
+ <stop offset=".5" stop-color="#A855F7"/>
7
+ <stop offset=".75" stop-color="#C084FC"/>
8
+ <stop offset="1" stop-color="#22D3EE"/>
9
+ </linearGradient>
10
+ <linearGradient id="mid" x1="50%" y1="100%" x2="50%" y2="0%">
11
+ <stop stop-color="#6D28D9"/>
12
+ <stop offset=".3" stop-color="#A855F7"/>
13
+ <stop offset=".7" stop-color="#C4B5FD"/>
14
+ <stop offset="1" stop-color="#67E8F9"/>
15
+ </linearGradient>
16
+ <linearGradient id="inner" x1="50%" y1="100%" x2="50%" y2="0%">
17
+ <stop stop-color="#A78BFA"/>
18
+ <stop offset=".4" stop-color="#C4B5FD"/>
19
+ <stop offset=".8" stop-color="#E0E7FF"/>
20
+ <stop offset="1" stop-color="#ECFEFF"/>
21
+ </linearGradient>
22
+ <linearGradient id="core" x1="50%" y1="100%" x2="50%" y2="0%">
23
+ <stop stop-color="#DDD6FE"/>
24
+ <stop offset="1" stop-color="#F0F9FF"/>
25
+ </linearGradient>
26
+ <linearGradient id="tongue" x1="50%" y1="100%" x2="50%" y2="0%">
27
+ <stop stop-color="#7C3AED"/>
28
+ <stop offset="1" stop-color="#38BDF8"/>
29
+ </linearGradient>
30
+ <linearGradient id="spark" x1="50%" y1="100%" x2="50%" y2="0%">
31
+ <stop stop-color="#A78BFA"/>
32
+ <stop offset="1" stop-color="#67E8F9"/>
33
+ </linearGradient>
34
+ </defs>
35
+
36
+ <!-- ============ OUTER GLOW / HEAT HAZE ============ -->
37
+ <ellipse cx="64" cy="60" rx="38" ry="50" fill="#7C3AED" opacity=".04">
38
+ <animate attributeName="rx" dur="2s" repeatCount="indefinite" values="38;42;36;38"/>
39
+ <animate attributeName="opacity" dur="2s" repeatCount="indefinite" values=".04;.06;.03;.04"/>
40
+ </ellipse>
41
+
42
+ <!-- ============ LEFT TONGUE (far) ============ -->
43
+ <path fill="url(#tongue)" opacity=".5">
44
+ <animate attributeName="d" dur="0.6s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.6 1;0.4 0 0.6 1;0.4 0 0.6 1"
45
+ values="
46
+ M44 74C40 62 36 52 38 38C39 30 43 24 43 24C44 34 45 44 48 52C50 60 43 68 44 74Z;
47
+ M42 74C38 64 33 54 36 36C37 28 41 22 41 22C43 32 43 46 46 54C48 62 41 69 42 74Z;
48
+ M45 74C41 60 38 50 40 40C41 32 44 26 44 26C45 36 46 42 49 50C51 58 44 67 45 74Z;
49
+ M44 74C40 62 36 52 38 38C39 30 43 24 43 24C44 34 45 44 48 52C50 60 43 68 44 74Z"/>
50
+ <animate attributeName="opacity" dur="0.7s" repeatCount="indefinite" values=".5;.3;.6;.4;.5"/>
51
+ </path>
52
+
53
+ <!-- ============ RIGHT TONGUE (far) ============ -->
54
+ <path fill="url(#tongue)" opacity=".45">
55
+ <animate attributeName="d" dur="0.7s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.6 1;0.4 0 0.6 1;0.4 0 0.6 1"
56
+ values="
57
+ M84 72C88 58 92 46 89 30C88 22 84 16 84 16C85 28 83 40 81 50C79 58 83 66 84 72Z;
58
+ M86 72C90 56 94 44 91 28C90 20 86 14 86 14C87 26 84 38 82 48C80 56 85 65 86 72Z;
59
+ M83 72C87 60 90 48 87 32C86 24 83 18 83 18C84 30 82 42 80 52C78 60 82 67 83 72Z;
60
+ M84 72C88 58 92 46 89 30C88 22 84 16 84 16C85 28 83 40 81 50C79 58 83 66 84 72Z"/>
61
+ <animate attributeName="opacity" dur="0.8s" repeatCount="indefinite" values=".45;.6;.3;.5;.45"/>
62
+ </path>
63
+
64
+ <!-- ============ LEFT WISP (small) ============ -->
65
+ <path fill="url(#tongue)" opacity=".35">
66
+ <animate attributeName="d" dur="0.5s" repeatCount="indefinite"
67
+ values="
68
+ M38 80C35 72 32 64 34 54C35 48 38 44 38 44C38 50 40 58 41 64C42 70 37 76 38 80Z;
69
+ M36 80C33 74 30 66 33 52C34 46 36 42 36 42C37 48 38 60 40 66C41 72 35 77 36 80Z;
70
+ M39 80C36 71 34 62 36 56C37 50 39 46 39 46C39 52 41 56 42 62C43 68 38 75 39 80Z;
71
+ M38 80C35 72 32 64 34 54C35 48 38 44 38 44C38 50 40 58 41 64C42 70 37 76 38 80Z"/>
72
+ </path>
73
+
74
+ <!-- ============ RIGHT WISP (small) ============ -->
75
+ <path fill="url(#tongue)" opacity=".3">
76
+ <animate attributeName="d" dur="0.55s" repeatCount="indefinite"
77
+ values="
78
+ M90 78C93 70 96 60 93 50C92 44 89 40 89 40C90 48 88 56 87 62C86 68 89 74 90 78Z;
79
+ M92 78C95 68 97 58 95 48C94 42 91 38 91 38C92 46 89 54 88 60C87 66 91 73 92 78Z;
80
+ M89 78C92 72 94 62 92 52C91 46 88 42 88 42C89 50 87 58 86 64C85 70 88 75 89 78Z;
81
+ M90 78C93 70 96 60 93 50C92 44 89 40 89 40C90 48 88 56 87 62C86 68 89 74 90 78Z"/>
82
+ </path>
83
+
84
+ <!-- ============ MAIN OUTER FLAME ============ -->
85
+ <path fill="url(#outer)" opacity=".92">
86
+ <animate attributeName="d" dur="1.2s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.6 1;0.4 0 0.6 1;0.4 0 0.6 1;0.4 0 0.6 1"
87
+ values="
88
+ M64 10C62 10 54 26 50 36C46 46 36 48 34 60C32 72 35 82 40 90C46 98 54 106 64 108C74 106 82 98 88 90C93 82 96 72 94 60C92 48 82 46 78 36C74 26 84 20 86 14C82 22 76 30 74 38C72 48 78 56 76 64C74 54 70 40 68 30C66 20 64 10 64 10Z;
89
+ M64 8C62 8 52 24 48 35C44 46 34 50 32 62C30 74 34 84 38 92C44 100 53 107 64 109C75 107 84 100 90 92C94 84 98 74 96 62C94 50 84 44 80 35C76 24 86 18 88 12C84 20 78 28 76 36C74 46 80 54 78 62C76 52 72 38 70 28C68 18 64 8 64 8Z;
90
+ M64 12C62 12 56 28 52 37C48 46 38 46 36 58C34 70 36 80 41 88C46 96 55 105 64 107C73 105 82 96 87 88C92 80 94 70 92 58C90 46 80 48 76 37C72 28 82 22 84 16C80 24 74 32 72 40C70 50 76 58 74 66C72 56 68 42 66 32C64 24 64 12 64 12Z;
91
+ M64 9C62 9 53 25 49 34C45 44 35 49 33 61C31 73 34 83 39 91C45 99 53 106 64 108C75 106 83 99 89 91C94 83 97 73 95 61C93 49 83 45 79 34C75 25 85 19 87 13C83 21 77 29 75 37C73 47 79 55 77 63C75 53 71 39 69 29C67 19 64 9 64 9Z;
92
+ M64 10C62 10 54 26 50 36C46 46 36 48 34 60C32 72 35 82 40 90C46 98 54 106 64 108C74 106 82 98 88 90C93 82 96 72 94 60C92 48 82 46 78 36C74 26 84 20 86 14C82 22 76 30 74 38C72 48 78 56 76 64C74 54 70 40 68 30C66 20 64 10 64 10Z"/>
93
+ </path>
94
+
95
+ <!-- ============ MIDDLE FLAME ============ -->
96
+ <path fill="url(#mid)" opacity=".88">
97
+ <animate attributeName="d" dur="0.9s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.6 1;0.4 0 0.6 1;0.4 0 0.6 1;0.4 0 0.6 1"
98
+ values="
99
+ M64 30C62 30 52 46 48 56C44 66 46 78 50 86C54 94 58 100 64 102C70 100 74 94 78 86C82 78 84 66 80 56C78 48 82 38 82 30C80 38 76 46 74 54C72 44 68 36 64 30Z;
100
+ M64 28C62 28 50 44 46 55C42 66 45 80 49 88C53 96 57 101 64 103C71 101 75 96 79 88C83 80 86 66 82 55C80 46 84 36 84 28C82 36 77 44 75 52C73 42 69 34 64 28Z;
101
+ M64 32C62 32 53 48 49 57C45 67 47 77 51 85C55 93 59 99 64 101C69 99 73 93 77 85C81 77 83 67 79 57C77 50 81 40 81 32C79 40 75 48 73 55C71 46 67 38 64 32Z;
102
+ M64 29C62 29 51 45 47 54C43 64 46 79 50 87C54 95 58 100 64 102C70 100 74 95 78 87C82 79 85 64 81 54C79 47 83 37 83 29C81 37 76 45 74 53C72 43 68 35 64 29Z;
103
+ M64 30C62 30 52 46 48 56C44 66 46 78 50 86C54 94 58 100 64 102C70 100 74 94 78 86C82 78 84 66 80 56C78 48 82 38 82 30C80 38 76 46 74 54C72 44 68 36 64 30Z"/>
104
+ </path>
105
+
106
+ <!-- ============ INNER FLAME ============ -->
107
+ <path fill="url(#inner)" opacity=".85">
108
+ <animate attributeName="d" dur="0.7s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.6 1;0.4 0 0.6 1;0.4 0 0.6 1"
109
+ values="
110
+ M64 50C60 56 54 66 54 76C54 86 58 94 64 100C70 94 74 86 74 76C74 66 68 56 64 50Z;
111
+ M64 48C60 54 52 64 52 75C52 86 57 95 64 101C71 95 76 86 76 75C76 64 68 54 64 48Z;
112
+ M64 52C60 58 55 68 55 77C55 86 59 93 64 99C69 93 73 86 73 77C73 68 68 58 64 52Z;
113
+ M64 50C60 56 54 66 54 76C54 86 58 94 64 100C70 94 74 86 74 76C74 66 68 56 64 50Z"/>
114
+ </path>
115
+
116
+ <!-- ============ HOT CORE ============ -->
117
+ <path fill="url(#core)" opacity=".7">
118
+ <animate attributeName="d" dur="0.5s" repeatCount="indefinite"
119
+ values="
120
+ M64 72C60 78 56 84 58 92C60 97 62 100 64 101C66 100 68 97 70 92C72 84 68 78 64 72Z;
121
+ M64 70C60 76 55 83 57 91C59 97 61 100 64 102C67 100 69 97 71 91C73 83 68 76 64 70Z;
122
+ M64 74C60 80 57 85 59 93C61 97 63 100 64 100C65 100 67 97 69 93C71 85 68 80 64 74Z;
123
+ M64 72C60 78 56 84 58 92C60 97 62 100 64 101C66 100 68 97 70 92C72 84 68 78 64 72Z"/>
124
+ <animate attributeName="opacity" dur="0.4s" repeatCount="indefinite" values=".7;.55;.85;.65;.7"/>
125
+ </path>
126
+
127
+ <!-- ============ WHITE HOT CENTER ============ -->
128
+ <ellipse cx="64" cy="92" fill="#F0F9FF">
129
+ <animate attributeName="rx" dur="0.4s" repeatCount="indefinite" values="5;3.5;6;4;5"/>
130
+ <animate attributeName="ry" dur="0.4s" repeatCount="indefinite" values="7;5;8;6;7"/>
131
+ <animate attributeName="opacity" dur="0.35s" repeatCount="indefinite" values=".5;.3;.6;.35;.5"/>
132
+ <animate attributeName="cy" dur="0.6s" repeatCount="indefinite" values="92;90;93;91;92"/>
133
+ </ellipse>
134
+
135
+ <!-- ============ SPARKS / EMBERS ============ -->
136
+ <!-- Spark 1 - rises left -->
137
+ <circle r="1.2" fill="url(#spark)">
138
+ <animate attributeName="cx" dur="1.8s" repeatCount="indefinite" values="56;52;48;44"/>
139
+ <animate attributeName="cy" dur="1.8s" repeatCount="indefinite" values="40;28;16;4"/>
140
+ <animate attributeName="opacity" dur="1.8s" repeatCount="indefinite" values="0;.7;.5;0"/>
141
+ <animate attributeName="r" dur="1.8s" repeatCount="indefinite" values="1.2;1;.6;.2"/>
142
+ </circle>
143
+
144
+ <!-- Spark 2 - rises right -->
145
+ <circle r="1" fill="url(#spark)">
146
+ <animate attributeName="cx" dur="2s" repeatCount="indefinite" values="72;76;80;82"/>
147
+ <animate attributeName="cy" dur="2s" repeatCount="indefinite" values="36;22;10;0"/>
148
+ <animate attributeName="opacity" dur="2s" repeatCount="indefinite" values="0;.6;.4;0"/>
149
+ <animate attributeName="r" dur="2s" repeatCount="indefinite" values="1;.8;.5;.1"/>
150
+ </circle>
151
+
152
+ <!-- Spark 3 - rises center -->
153
+ <circle r="1.5" fill="#E0E7FF">
154
+ <animate attributeName="cx" dur="1.5s" repeatCount="indefinite" values="64;62;60;58"/>
155
+ <animate attributeName="cy" dur="1.5s" repeatCount="indefinite" values="32;18;6;-4"/>
156
+ <animate attributeName="opacity" dur="1.5s" repeatCount="indefinite" values="0;.8;.4;0"/>
157
+ <animate attributeName="r" dur="1.5s" repeatCount="indefinite" values="1.5;1.2;.7;.2"/>
158
+ </circle>
159
+
160
+ <!-- Spark 4 - rises far left -->
161
+ <circle r=".8" fill="#C4B5FD">
162
+ <animate attributeName="cx" dur="2.2s" repeatCount="indefinite" values="50;46;42;40"/>
163
+ <animate attributeName="cy" dur="2.2s" repeatCount="indefinite" values="46;32;18;6"/>
164
+ <animate attributeName="opacity" dur="2.2s" repeatCount="indefinite" values="0;.5;.3;0"/>
165
+ <animate attributeName="r" dur="2.2s" repeatCount="indefinite" values=".8;.6;.4;.1"/>
166
+ </circle>
167
+
168
+ <!-- Spark 5 - rises far right -->
169
+ <circle r=".9" fill="#67E8F9">
170
+ <animate attributeName="cx" dur="1.7s" repeatCount="indefinite" values="76;80;84;86" begin="0.3s"/>
171
+ <animate attributeName="cy" dur="1.7s" repeatCount="indefinite" values="42;26;12;0" begin="0.3s"/>
172
+ <animate attributeName="opacity" dur="1.7s" repeatCount="indefinite" values="0;.6;.3;0" begin="0.3s"/>
173
+ <animate attributeName="r" dur="1.7s" repeatCount="indefinite" values=".9;.7;.4;.1" begin="0.3s"/>
174
+ </circle>
175
+
176
+ <!-- Spark 6 - tiny center delayed -->
177
+ <circle r=".7" fill="#F0F9FF">
178
+ <animate attributeName="cx" dur="1.4s" repeatCount="indefinite" values="66;68;70;72" begin="0.6s"/>
179
+ <animate attributeName="cy" dur="1.4s" repeatCount="indefinite" values="28;14;4;-6" begin="0.6s"/>
180
+ <animate attributeName="opacity" dur="1.4s" repeatCount="indefinite" values="0;.7;.3;0" begin="0.6s"/>
181
+ <animate attributeName="r" dur="1.4s" repeatCount="indefinite" values=".7;.5;.3;0" begin="0.6s"/>
182
+ </circle>
183
+ </svg>