clawport-ui 0.1.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 (132) hide show
  1. package/.env.example +35 -0
  2. package/BRANDING.md +131 -0
  3. package/CLAUDE.md +252 -0
  4. package/README.md +262 -0
  5. package/SETUP.md +337 -0
  6. package/app/agents/[id]/page.tsx +727 -0
  7. package/app/api/agents/route.ts +12 -0
  8. package/app/api/chat/[id]/route.ts +139 -0
  9. package/app/api/cron-runs/route.ts +13 -0
  10. package/app/api/crons/route.ts +12 -0
  11. package/app/api/kanban/chat/[id]/route.ts +119 -0
  12. package/app/api/kanban/chat-history/[ticketId]/route.ts +36 -0
  13. package/app/api/memory/route.ts +12 -0
  14. package/app/api/transcribe/route.ts +37 -0
  15. package/app/api/tts/route.ts +42 -0
  16. package/app/chat/[id]/page.tsx +10 -0
  17. package/app/chat/page.tsx +200 -0
  18. package/app/crons/page.tsx +870 -0
  19. package/app/docs/page.tsx +399 -0
  20. package/app/favicon.ico +0 -0
  21. package/app/globals.css +692 -0
  22. package/app/kanban/page.tsx +327 -0
  23. package/app/layout.tsx +45 -0
  24. package/app/memory/page.tsx +685 -0
  25. package/app/page.tsx +817 -0
  26. package/app/providers.tsx +37 -0
  27. package/app/settings/page.tsx +901 -0
  28. package/app/settings-provider.tsx +209 -0
  29. package/components/AgentAvatar.tsx +54 -0
  30. package/components/AgentNode.tsx +122 -0
  31. package/components/Breadcrumbs.tsx +126 -0
  32. package/components/DynamicFavicon.tsx +62 -0
  33. package/components/ErrorState.tsx +97 -0
  34. package/components/FeedView.tsx +494 -0
  35. package/components/GlobalSearch.tsx +571 -0
  36. package/components/GridView.tsx +532 -0
  37. package/components/ManorMap.tsx +157 -0
  38. package/components/MobileSidebar.tsx +251 -0
  39. package/components/NavLinks.tsx +271 -0
  40. package/components/OnboardingWizard.tsx +1067 -0
  41. package/components/Sidebar.tsx +115 -0
  42. package/components/ThemeToggle.tsx +108 -0
  43. package/components/chat/AgentList.tsx +537 -0
  44. package/components/chat/ConversationView.tsx +1047 -0
  45. package/components/chat/FileAttachment.tsx +140 -0
  46. package/components/chat/MediaPreview.tsx +111 -0
  47. package/components/chat/VoiceMessage.tsx +139 -0
  48. package/components/crons/PipelineGraph.tsx +327 -0
  49. package/components/crons/WeeklySchedule.tsx +630 -0
  50. package/components/docs/AgentsSection.tsx +209 -0
  51. package/components/docs/ApiReferenceSection.tsx +256 -0
  52. package/components/docs/ArchitectureSection.tsx +221 -0
  53. package/components/docs/ComponentsSection.tsx +253 -0
  54. package/components/docs/CronSystemSection.tsx +235 -0
  55. package/components/docs/DocSection.tsx +346 -0
  56. package/components/docs/GettingStartedSection.tsx +169 -0
  57. package/components/docs/ThemingSection.tsx +257 -0
  58. package/components/docs/TroubleshootingSection.tsx +200 -0
  59. package/components/kanban/AgentPicker.tsx +321 -0
  60. package/components/kanban/CreateTicketModal.tsx +333 -0
  61. package/components/kanban/KanbanBoard.tsx +70 -0
  62. package/components/kanban/KanbanColumn.tsx +166 -0
  63. package/components/kanban/TicketCard.tsx +245 -0
  64. package/components/kanban/TicketDetailPanel.tsx +850 -0
  65. package/components/ui/badge.tsx +48 -0
  66. package/components/ui/button.tsx +64 -0
  67. package/components/ui/card.tsx +92 -0
  68. package/components/ui/dialog.tsx +158 -0
  69. package/components/ui/scroll-area.tsx +58 -0
  70. package/components/ui/separator.tsx +28 -0
  71. package/components/ui/skeleton.tsx +27 -0
  72. package/components/ui/tabs.tsx +91 -0
  73. package/components/ui/tooltip.tsx +57 -0
  74. package/components.json +23 -0
  75. package/docs/API.md +648 -0
  76. package/docs/COMPONENTS.md +1059 -0
  77. package/docs/THEMING.md +795 -0
  78. package/lib/agents-registry.ts +35 -0
  79. package/lib/agents.json +282 -0
  80. package/lib/agents.test.ts +367 -0
  81. package/lib/agents.ts +32 -0
  82. package/lib/anthropic.test.ts +422 -0
  83. package/lib/anthropic.ts +220 -0
  84. package/lib/api-error.ts +16 -0
  85. package/lib/audio-recorder.test.ts +72 -0
  86. package/lib/audio-recorder.ts +169 -0
  87. package/lib/conversations.test.ts +331 -0
  88. package/lib/conversations.ts +117 -0
  89. package/lib/cron-pipelines.test.ts +69 -0
  90. package/lib/cron-pipelines.ts +58 -0
  91. package/lib/cron-runs.test.ts +118 -0
  92. package/lib/cron-runs.ts +67 -0
  93. package/lib/cron-utils.test.ts +222 -0
  94. package/lib/cron-utils.ts +160 -0
  95. package/lib/crons.test.ts +502 -0
  96. package/lib/crons.ts +114 -0
  97. package/lib/env.test.ts +44 -0
  98. package/lib/env.ts +14 -0
  99. package/lib/kanban/automation.test.ts +245 -0
  100. package/lib/kanban/automation.ts +143 -0
  101. package/lib/kanban/chat-store.test.ts +149 -0
  102. package/lib/kanban/chat-store.ts +81 -0
  103. package/lib/kanban/store.test.ts +238 -0
  104. package/lib/kanban/store.ts +98 -0
  105. package/lib/kanban/types.ts +50 -0
  106. package/lib/kanban/useAgentWork.ts +78 -0
  107. package/lib/memory.ts +45 -0
  108. package/lib/multimodal.test.ts +219 -0
  109. package/lib/multimodal.ts +68 -0
  110. package/lib/pipeline.integration.test.ts +343 -0
  111. package/lib/sanitize.ts +194 -0
  112. package/lib/settings.test.ts +137 -0
  113. package/lib/settings.ts +94 -0
  114. package/lib/styles.ts +24 -0
  115. package/lib/themes.ts +9 -0
  116. package/lib/transcribe.test.ts +141 -0
  117. package/lib/transcribe.ts +111 -0
  118. package/lib/types.ts +66 -0
  119. package/lib/utils.ts +6 -0
  120. package/lib/validation.test.ts +132 -0
  121. package/lib/validation.ts +80 -0
  122. package/next.config.ts +7 -0
  123. package/package.json +56 -0
  124. package/postcss.config.mjs +7 -0
  125. package/public/file.svg +1 -0
  126. package/public/globe.svg +1 -0
  127. package/public/next.svg +1 -0
  128. package/public/vercel.svg +1 -0
  129. package/public/window.svg +1 -0
  130. package/scripts/setup.mjs +215 -0
  131. package/tsconfig.json +34 -0
  132. package/vitest.config.ts +17 -0
@@ -0,0 +1,1059 @@
1
+ # ClawPort -- Component Reference
2
+
3
+ Every React component in the codebase, grouped by area. For each component: purpose, props, key state, parent usage, and implementation notes.
4
+
5
+ ---
6
+
7
+ ## Component Tree
8
+
9
+ ```
10
+ RootLayout (app/layout.tsx)
11
+ ThemeProvider (app/providers.tsx)
12
+ SettingsProvider (app/settings-provider.tsx)
13
+ DynamicFavicon (components/DynamicFavicon.tsx)
14
+ OnboardingWizard (components/OnboardingWizard.tsx)
15
+ Sidebar (components/Sidebar.tsx)
16
+ NavLinks (components/NavLinks.tsx)
17
+ ThemeToggle (components/ThemeToggle.tsx)
18
+ MobileSidebar (components/MobileSidebar.tsx)
19
+ GlobalSearch (components/GlobalSearch.tsx)
20
+ <main> (page content)
21
+ HomePage (app/page.tsx)
22
+ OrgMap (components/OrgMap.tsx)
23
+ AgentNode (components/AgentNode.tsx)
24
+ AgentAvatar (components/AgentAvatar.tsx)
25
+ GridView (components/GridView.tsx)
26
+ AgentAvatar
27
+ FeedView (components/FeedView.tsx)
28
+ ChatPage (app/chat/page.tsx)
29
+ AgentList (components/chat/AgentList.tsx)
30
+ AgentAvatar
31
+ AgentListMobile (components/chat/AgentList.tsx)
32
+ AgentAvatar
33
+ ConversationView (components/chat/ConversationView.tsx)
34
+ VoiceMessage (components/chat/VoiceMessage.tsx)
35
+ FileAttachment (components/chat/FileAttachment.tsx)
36
+ MediaPreview (components/chat/MediaPreview.tsx)
37
+ AgentDetailPage (app/agents/[id]/page.tsx)
38
+ AgentAvatar
39
+ Breadcrumbs (components/Breadcrumbs.tsx)
40
+ KanbanPage (app/kanban/page.tsx)
41
+ KanbanBoard (components/kanban/KanbanBoard.tsx)
42
+ KanbanColumn (components/kanban/KanbanColumn.tsx)
43
+ TicketCard (components/kanban/TicketCard.tsx)
44
+ AgentAvatar
45
+ TicketDetailPanel (components/kanban/TicketDetailPanel.tsx)
46
+ CreateTicketModal (components/kanban/CreateTicketModal.tsx)
47
+ AgentPicker (components/kanban/AgentPicker.tsx)
48
+ AgentAvatar
49
+ CronsPage (app/crons/page.tsx)
50
+ WeeklySchedule (components/crons/WeeklySchedule.tsx)
51
+ PipelineGraph (components/crons/PipelineGraph.tsx)
52
+ MemoryPage (app/memory/page.tsx)
53
+ SettingsPage (app/settings/page.tsx)
54
+ AgentAvatar
55
+ ErrorState (components/ErrorState.tsx)
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Providers
61
+
62
+ ### ThemeProvider
63
+
64
+ **File:** `app/providers.tsx`
65
+ **Purpose:** Manages the active theme and applies it to the document root via `data-theme` attribute.
66
+
67
+ | Prop | Type | Required | Description |
68
+ |------|------|----------|-------------|
69
+ | children | `React.ReactNode` | Yes | App content |
70
+
71
+ **Context value:**
72
+
73
+ ```ts
74
+ { theme: ThemeId; setTheme: (t: ThemeId) => void }
75
+ ```
76
+
77
+ **Key state:**
78
+ - `theme` (useState) -- persisted to `localStorage('clawport-theme')`, defaults to `'dark'`
79
+
80
+ **Implementation:**
81
+ - On mount, reads `localStorage('clawport-theme')` and sets `data-theme` attribute on `<html>`
82
+ - `setTheme` updates state, localStorage, and the DOM attribute in one call
83
+ - Exports `useTheme()` hook for consumers
84
+
85
+ **Used by:** `app/layout.tsx` (wraps entire app)
86
+
87
+ ---
88
+
89
+ ### SettingsProvider
90
+
91
+ **File:** `app/settings-provider.tsx`
92
+ **Purpose:** Central state management for all user-configurable settings (branding, accent color, operator name, agent display overrides).
93
+
94
+ | Prop | Type | Required | Description |
95
+ |------|------|----------|-------------|
96
+ | children | `React.ReactNode` | Yes | App content |
97
+
98
+ **Context value:**
99
+
100
+ ```ts
101
+ interface SettingsContextValue {
102
+ settings: ClawPortSettings
103
+ setAccentColor: (color: string | null) => void
104
+ setPortalName: (name: string | null) => void
105
+ setPortalSubtitle: (subtitle: string | null) => void
106
+ setPortalEmoji: (emoji: string | null) => void
107
+ setPortalIcon: (icon: string | null) => void
108
+ setIconBgHidden: (hidden: boolean) => void
109
+ setEmojiOnly: (emojiOnly: boolean) => void
110
+ setOperatorName: (name: string | null) => void
111
+ setAgentOverride: (agentId: string, override: AgentDisplayOverride | null) => void
112
+ getAgentDisplay: (agent: Agent) => AgentDisplay
113
+ resetAll: () => void
114
+ }
115
+ ```
116
+
117
+ **Key state:**
118
+ - `settings` (useState) -- full `ClawPortSettings` object, persisted to localStorage via `saveSettings()`
119
+
120
+ **Implementation:**
121
+ - Applies CSS custom properties (`--accent`, `--accent-hover`, `--accent-glow`) directly to `document.documentElement.style` when accent color changes
122
+ - `getAgentDisplay(agent)` merges agent defaults with per-agent overrides (custom emoji, image, background)
123
+ - `resetAll()` clears localStorage and resets to defaults
124
+ - Each setter calls `saveSettings()` after updating state
125
+
126
+ **Used by:** `app/layout.tsx` (wraps entire app, inside ThemeProvider)
127
+
128
+ ---
129
+
130
+ ## Layout Components
131
+
132
+ ### Sidebar
133
+
134
+ **File:** `components/Sidebar.tsx`
135
+ **Purpose:** Client wrapper that coordinates the desktop sidebar, mobile header/sidebar, and Cmd+K search palette.
136
+
137
+ **Props:** None
138
+
139
+ **Key state:**
140
+ - `searchOpen` (useState) -- controls GlobalSearch visibility
141
+
142
+ **Implementation:**
143
+ - Renders `NavLinks` and `ThemeToggle` inside a fixed-width desktop sidebar (`w-[220px]`, hidden on mobile)
144
+ - Renders `MobileSidebar` for mobile viewports
145
+ - Renders `GlobalSearch` (always mounted, visibility controlled by `searchOpen`)
146
+ - Listens for custom event `clawport:open-search` to toggle search open
147
+ - Dispatches search-open from MobileSidebar's search trigger
148
+
149
+ **Used by:** `app/layout.tsx`
150
+
151
+ ---
152
+
153
+ ### NavLinks
154
+
155
+ **File:** `components/NavLinks.tsx`
156
+ **Purpose:** Sidebar navigation links with icons, badges, and operator identity footer.
157
+
158
+ **Props:** None
159
+
160
+ **Key state:**
161
+ - `agentCount` (useState) -- fetched from `/api/agents` on mount
162
+ - `cronErrorCount` (useState) -- fetched from `/api/crons` on mount
163
+
164
+ **Implementation:**
165
+ - Six navigation items: Map, Kanban, Messages, Crons, Memory, Settings
166
+ - Each uses a Lucide icon (`Map`, `Columns3`, `MessageCircle`, `Clock`, `Brain`, `Settings`)
167
+ - Badge system: agent count on Map, cron error count on Crons (red dot)
168
+ - Footer shows operator initials (computed from `operatorName` via `useSettings()`) and display name
169
+ - Falls back to "Operator" / "??" when `operatorName` is unset
170
+ - Uses `usePathname()` for active link highlighting
171
+
172
+ **Used by:** `components/Sidebar.tsx`
173
+
174
+ ---
175
+
176
+ ### MobileSidebar
177
+
178
+ **File:** `components/MobileSidebar.tsx`
179
+ **Purpose:** Fixed mobile header bar with hamburger menu and slide-out sidebar panel.
180
+
181
+ | Prop | Type | Required | Description |
182
+ |------|------|----------|-------------|
183
+ | onOpenSearch | `() => void` | No | Callback to open global search |
184
+
185
+ **Key state:**
186
+ - `isOpen` (useState) -- sidebar panel visibility
187
+
188
+ **Implementation:**
189
+ - Fixed header bar (48px) with portal emoji/name, search icon, and hamburger button
190
+ - Slide-out panel renders NavLinks and ThemeToggle
191
+ - Closes on: route change (`usePathname` effect), ESC key, click outside panel
192
+ - Prevents body scroll when open via `overflow: hidden` on body
193
+ - Only visible on mobile (`md:hidden`)
194
+
195
+ **Used by:** `components/Sidebar.tsx`
196
+
197
+ ---
198
+
199
+ ### GlobalSearch
200
+
201
+ **File:** `components/GlobalSearch.tsx`
202
+ **Purpose:** Cmd+K search palette with fuzzy search across agents, pages, and crons.
203
+
204
+ | Prop | Type | Required | Description |
205
+ |------|------|----------|-------------|
206
+ | open | `boolean` | Yes | Whether the search modal is visible |
207
+ | onClose | `() => void` | Yes | Callback to close the modal |
208
+
209
+ **Exports:** `GlobalSearch` (main modal) and `SearchTrigger` (button that dispatches `clawport:open-search`)
210
+
211
+ **Key state:**
212
+ - `query` (useState) -- search input text
213
+ - `results` (useState) -- filtered search results
214
+ - `selectedIndex` (useState) -- keyboard navigation position
215
+ - `agents` / `crons` (useState) -- fetched on open
216
+
217
+ **Implementation:**
218
+ - Fetches agents and crons from API when modal opens
219
+ - Fuzzy matching: checks if query characters appear in order within result label (case-insensitive)
220
+ - Results grouped by type: agents, pages (hardcoded list), crons
221
+ - Keyboard navigation: ArrowUp/ArrowDown to move, Enter to select, Escape to close
222
+ - Navigates via `router.push()` on selection
223
+ - Global keyboard listener for Cmd+K / Ctrl+K to open
224
+
225
+ **Used by:** `components/Sidebar.tsx`
226
+
227
+ ---
228
+
229
+ ### DynamicFavicon
230
+
231
+ **File:** `components/DynamicFavicon.tsx`
232
+ **Purpose:** Generates and applies a dynamic favicon from the portal emoji or uploaded icon image.
233
+
234
+ **Props:** None (renders `null`)
235
+
236
+ **Key state:** None (effect-only component)
237
+
238
+ **Implementation:**
239
+ - Uses Canvas API to draw favicon onto a 64x64 canvas
240
+ - If `portalIcon` (uploaded image): draws the image scaled to fill the canvas
241
+ - If `portalEmoji`: draws a colored circle background (using accent color) and renders the emoji as centered text
242
+ - If `iconBgHidden`: skips the circle background for emoji mode
243
+ - Converts canvas to PNG data URL and sets it on a `<link rel="icon">` element
244
+ - Re-runs when `portalEmoji`, `portalIcon`, `accentColor`, `iconBgHidden`, or `emojiOnly` change
245
+
246
+ **Used by:** `app/layout.tsx`
247
+
248
+ ---
249
+
250
+ ### ThemeToggle
251
+
252
+ **File:** `components/ThemeToggle.tsx`
253
+ **Purpose:** Theme selector rendered as a row of emoji buttons with radiogroup semantics.
254
+
255
+ **Props:** None
256
+
257
+ **Key state:** None (reads from `useTheme()`)
258
+
259
+ **Implementation:**
260
+ - Renders one button per theme with emoji indicators
261
+ - Uses `role="radiogroup"` and `aria-checked` for accessibility
262
+ - Arrow key navigation (left/right) cycles through themes
263
+ - Highlights active theme with accent-colored border
264
+ - Themes: dark, glass, color, light, system
265
+
266
+ **Used by:** `components/Sidebar.tsx`, `components/MobileSidebar.tsx`
267
+
268
+ ---
269
+
270
+ ### Breadcrumbs
271
+
272
+ **File:** `components/Breadcrumbs.tsx`
273
+ **Purpose:** Breadcrumb navigation bar with Lucide ChevronRight separators.
274
+
275
+ | Prop | Type | Required | Description |
276
+ |------|------|----------|-------------|
277
+ | items | `BreadcrumbItem[]` | Yes | Breadcrumb segments |
278
+
279
+ ```ts
280
+ interface BreadcrumbItem {
281
+ label: string
282
+ href?: string // If present, renders as Link; otherwise plain text
283
+ icon?: LucideIcon
284
+ }
285
+ ```
286
+
287
+ **Implementation:**
288
+ - Last item rendered as plain text (current page), all others as Next.js `Link`
289
+ - Optional Lucide icon rendered before each label
290
+ - Styled with `--text-secondary` and `--text-primary` CSS vars
291
+
292
+ **Used by:** `app/agents/[id]/page.tsx`
293
+
294
+ ---
295
+
296
+ ### ErrorState
297
+
298
+ **File:** `components/ErrorState.tsx`
299
+ **Purpose:** Full-screen error display with optional retry button.
300
+
301
+ | Prop | Type | Required | Description |
302
+ |------|------|----------|-------------|
303
+ | message | `string` | Yes | Error message to display |
304
+ | onRetry | `() => void` | No | If provided, shows a retry button |
305
+
306
+ **Implementation:**
307
+ - Centered layout with warning icon, message text, and optional retry button
308
+ - Uses CSS vars for theming
309
+
310
+ **Used by:** `app/page.tsx`, `app/chat/page.tsx`, `app/crons/page.tsx`
311
+
312
+ ---
313
+
314
+ ### AgentAvatar
315
+
316
+ **File:** `components/AgentAvatar.tsx`
317
+ **Purpose:** Renders an agent's avatar as a profile image, emoji on colored background, or emoji-only (transparent background).
318
+
319
+ | Prop | Type | Required | Description |
320
+ |------|------|----------|-------------|
321
+ | agent | `Agent` | Yes | Agent data object |
322
+ | size | `number` | Yes | Avatar dimensions in pixels |
323
+ | borderRadius | `number` | No | Border radius (default: 12) |
324
+ | style | `CSSProperties` | No | Additional inline styles |
325
+
326
+ **Key state:** None
327
+
328
+ **Implementation:**
329
+ - Uses `useSettings().getAgentDisplay(agent)` to resolve display overrides (custom emoji/image per agent)
330
+ - Three render modes:
331
+ 1. **Profile image** (agent has `profileImage` or override image): renders `<img>` with object-fit cover
332
+ 2. **Emoji on background**: renders emoji text centered on a colored circle (agent's `color` or override)
333
+ 3. **Emoji only** (`emojiOnly` setting): renders emoji with transparent background
334
+ - Background color comes from agent definition or per-agent override
335
+
336
+ **Used by:** `components/AgentNode.tsx`, `components/GridView.tsx`, `components/chat/AgentList.tsx`, `components/kanban/TicketCard.tsx`, `components/kanban/AgentPicker.tsx`, `app/agents/[id]/page.tsx`, `app/settings/page.tsx`, `app/page.tsx`
337
+
338
+ ---
339
+
340
+ ## Onboarding
341
+
342
+ ### OnboardingWizard
343
+
344
+ **File:** `components/OnboardingWizard.tsx`
345
+ **Purpose:** Five-step first-run setup wizard for configuring portal name, theme, accent color, voice chat, and feature overview.
346
+
347
+ | Prop | Type | Required | Description |
348
+ |------|------|----------|-------------|
349
+ | forceOpen | `boolean` | No | If true, opens regardless of onboarding state; pre-populates from current settings |
350
+ | onClose | `() => void` | No | Callback when wizard closes (used with forceOpen from settings page) |
351
+
352
+ **Key state:**
353
+ - `step` (useState) -- current wizard step (0-4)
354
+ - `show` (useState) -- visibility flag
355
+ - `name` / `subtitle` / `emoji` / `operatorName` (useState) -- form fields for step 0
356
+ - `selectedTheme` (useState) -- theme choice for step 1
357
+ - `selectedAccent` (useState) -- accent color for step 2
358
+ - `micStatus` (useState) -- microphone permission state for step 3 (`'idle' | 'requesting' | 'granted' | 'denied' | 'error'`)
359
+ - `micLevel` (useState) -- real-time audio level for mic test visualization
360
+
361
+ **Steps:**
362
+ 1. **Welcome** (step 0) -- Portal name, subtitle, emoji picker, operator name. Live sidebar preview showing how NavLinks will look.
363
+ 2. **Theme** (step 1) -- Theme grid with preview cards. Applies theme live via `setTheme()`.
364
+ 3. **Accent Color** (step 2) -- Color preset grid (12 colors). Applies live via `setAccentColor()`.
365
+ 4. **Voice Chat** (step 3) -- Microphone permission test. Uses Web Audio API (`AudioContext` + `AnalyserNode`) to capture real-time audio levels and display a pulsing circle visualization.
366
+ 5. **Overview** (step 4) -- Feature summary cards (Agent Map, Chat, Kanban, Crons, Memory).
367
+
368
+ **Implementation:**
369
+ - First-run detection: checks `localStorage('clawport-onboarded')`
370
+ - When `forceOpen` is true: pre-populates all fields from current settings, does NOT set `clawport-onboarded` on completion
371
+ - Normal completion: sets `clawport-onboarded` in localStorage and saves all settings
372
+ - Modal overlay with backdrop blur, step indicator dots, back/next/finish navigation
373
+ - Mic test cleans up audio stream and context on unmount or step change
374
+
375
+ **Used by:** `app/layout.tsx` (always mounted, self-hides), `app/settings/page.tsx` (via forceOpen)
376
+
377
+ ---
378
+
379
+ ## Map / Home Page Components
380
+
381
+ ### OrgMap
382
+
383
+ **File:** `components/OrgMap.tsx`
384
+ **Purpose:** React Flow org chart visualization of the agent hierarchy with interactive node selection.
385
+
386
+ | Prop | Type | Required | Description |
387
+ |------|------|----------|-------------|
388
+ | agents | `Agent[]` | Yes | All agents to render |
389
+ | crons | `CronJob[]` | Yes | Cron jobs (for status indicators) |
390
+ | selectedId | `string \| null` | Yes | Currently selected agent ID |
391
+ | onNodeClick | `(agent: Agent) => void` | Yes | Callback when a node is clicked |
392
+
393
+ **Key state:**
394
+ - `nodes` / `edges` (useState) -- React Flow node and edge arrays
395
+ - `highlightedEdges` (derived) -- edges connected to selected node
396
+
397
+ **Implementation:**
398
+ - BFS-based hierarchical layout: starts from root agent (`reportsTo` is null/undefined), assigns x/y positions by level
399
+ - Horizontal spacing: 280px, vertical spacing: 160px
400
+ - Each node rendered as custom `AgentNode` component (registered via `nodeTypes`)
401
+ - Edge highlighting: when an agent is selected, its connected edges get accent color + increased width
402
+ - Non-highlighted edges fade to 20% opacity
403
+ - Uses `ReactFlow` with `fitView`, `panOnScroll`, and zoom controls
404
+ - Dynamically imported in `app/page.tsx` with `{ ssr: false }` to avoid SSR issues with React Flow
405
+
406
+ **Used by:** `app/page.tsx` (HomePage, map view)
407
+
408
+ ---
409
+
410
+ ### AgentNode
411
+
412
+ **File:** `components/AgentNode.tsx`
413
+ **Purpose:** Custom React Flow node component displaying agent avatar, name, title, and status.
414
+
415
+ | Prop | Type | Required | Description |
416
+ |------|------|----------|-------------|
417
+ | data | `AgentNodeData` | Yes | Node data (passed by React Flow) |
418
+
419
+ ```ts
420
+ interface AgentNodeData {
421
+ agent: Agent
422
+ selected: boolean
423
+ cronHealth?: 'ok' | 'error' | 'idle'
424
+ }
425
+ ```
426
+
427
+ **Implementation:**
428
+ - Renders AgentAvatar (48px) with name, title, and optional description truncated to 2 lines
429
+ - Selected state: accent-colored border and subtle glow effect
430
+ - Cron health indicator: colored dot (green/red/gray) in top-right corner
431
+ - Uses React Flow `Handle` components for source (bottom) and target (top) connections
432
+ - Exports `nodeTypes = { agentNode: AgentNode }` for React Flow registration
433
+
434
+ **Used by:** `components/OrgMap.tsx`
435
+
436
+ ---
437
+
438
+ ### GridView
439
+
440
+ **File:** `components/GridView.tsx`
441
+ **Purpose:** Card-based grid layout with team grouping hierarchy.
442
+
443
+ | Prop | Type | Required | Description |
444
+ |------|------|----------|-------------|
445
+ | agents | `Agent[]` | Yes | All agents |
446
+ | crons | `CronJob[]` | Yes | Cron jobs for status |
447
+ | selectedId | `string \| null` | Yes | Currently selected agent |
448
+ | onSelect | `(agent: Agent) => void` | Yes | Selection callback |
449
+
450
+ **Implementation:**
451
+ - Builds team hierarchy: identifies "hero" agent (root with no `reportsTo`), groups agents by their `reportsTo` manager, separates solo operators (no reports and not reporting to anyone besides hero)
452
+ - Contains inline `AgentCard` component: renders avatar, name, title, description, cron health dot, and status badge
453
+ - Contains inline `TeamSection` component: collapsible team group with manager header
454
+ - Selected card gets accent border highlight
455
+ - Responsive grid: 1 col mobile, 2 cols md, 3 cols lg
456
+
457
+ **Used by:** `app/page.tsx` (HomePage, grid view)
458
+
459
+ ---
460
+
461
+ ### FeedView
462
+
463
+ **File:** `components/FeedView.tsx`
464
+ **Purpose:** Activity feed focused on cron job status with stat cards and filter pills.
465
+
466
+ | Prop | Type | Required | Description |
467
+ |------|------|----------|-------------|
468
+ | agents | `Agent[]` | Yes | All agents |
469
+ | crons | `CronJob[]` | Yes | Cron jobs to display |
470
+ | selectedId | `string \| null` | Yes | Currently selected agent |
471
+ | onSelect | `(agent: Agent) => void` | Yes | Selection callback |
472
+
473
+ **Key state:**
474
+ - `filter` (useState) -- `'all' | 'ok' | 'error' | 'idle'`
475
+
476
+ **Implementation:**
477
+ - Top stat cards: total crons, healthy count, errors count, idle count
478
+ - Filter pills toggle which crons are shown
479
+ - Crons sorted by status priority (errors first) then by `lastRun` descending
480
+ - Each cron row shows: agent avatar, cron name, schedule, status badge, last run time
481
+ - Contains inline `StatusBadge` and `StatCard` helper components
482
+ - Clicking a cron row selects its associated agent
483
+
484
+ **Used by:** `app/page.tsx` (HomePage, feed view)
485
+
486
+ ---
487
+
488
+ ## Chat Components
489
+
490
+ ### ConversationView
491
+
492
+ **File:** `components/chat/ConversationView.tsx`
493
+ **Purpose:** Main chat interface with message rendering, SSE streaming, file attachments, TTS playback, and markdown formatting.
494
+
495
+ | Prop | Type | Required | Description |
496
+ |------|------|----------|-------------|
497
+ | agent | `Agent` | Yes | The agent being chatted with |
498
+ | conversation | `Conversation` | Yes | Current conversation data |
499
+ | onUpdate | `(conv: Conversation) => void` | Yes | Callback when conversation changes |
500
+ | onBack | `() => void` | No | Mobile back button callback |
501
+
502
+ **Key state:**
503
+ - `input` (useState) -- text input value
504
+ - `isStreaming` (useState) -- whether an SSE response is in progress
505
+ - `streamingText` (useState) -- accumulated streaming response text
506
+ - `attachments` (useState) -- staged `MediaAttachment[]` before send
507
+ - `isRecording` (useState) -- voice recording active
508
+ - `ttsPlaying` (useState) -- message ID currently playing TTS
509
+ - `ttsLoading` (useState) -- message ID currently loading TTS
510
+
511
+ **Implementation:**
512
+ - **SSE streaming:** POST to `/api/chat/[id]` with `ReadableStream` reader. Parses `data:` lines, accumulates text, calls `onUpdate` with final message.
513
+ - **Markdown rendering:** Inline `renderMarkdown()` function handles bold, italic, inline code, code blocks with language labels and copy buttons, links, and bullet lists.
514
+ - **TTS playback:** Sends message text to `/api/tts`, receives audio blob, plays via `Audio` object. Toggle play/stop per message.
515
+ - **File attachments:** Three input methods -- paste (Cmd+V), drag-and-drop, and file picker button. Images resized to 1200px max via Canvas API (`resizeImage` helper). Files converted to base64 data URLs for persistence.
516
+ - **Voice recording:** Uses `createAudioRecorder()` from `lib/audio-recorder.ts`. Records audio, captures waveform, transcribes via `/api/transcribe`, sends transcription as message. Stores audio data URL + waveform for playback.
517
+ - **Auto-scroll:** `useEffect` scrolls to bottom on new messages or streaming updates.
518
+ - **operatorName:** Sends `operatorName` from settings context in POST body for system prompt injection.
519
+
520
+ **Used by:** `app/chat/page.tsx` (ChatPage)
521
+
522
+ ---
523
+
524
+ ### AgentList / AgentListMobile
525
+
526
+ **File:** `components/chat/AgentList.tsx`
527
+ **Purpose:** Agent selection sidebar for chat. Two exports: `AgentList` (desktop, 300px fixed sidebar) and `AgentListMobile` (full-width, shown on mobile when no agent is selected).
528
+
529
+ | Prop | Type | Required | Description |
530
+ |------|------|----------|-------------|
531
+ | agents | `Agent[]` | Yes | Available agents |
532
+ | conversations | `Map<string, Conversation>` | Yes | All conversations (for previews/timestamps) |
533
+ | activeId | `string \| null` | Yes | Currently selected agent ID |
534
+ | onSelect | `(agent: Agent) => void` | Yes | Selection callback |
535
+ | loading | `boolean` | No | Show skeleton loading state |
536
+
537
+ **Key state:**
538
+ - `search` (useState) -- filter text for agent search
539
+
540
+ **Implementation:**
541
+ - Agents sorted by last activity (most recent conversation first), then alphabetically
542
+ - Each row shows: AgentAvatar, agent name, title, last message preview (truncated), relative timestamp
543
+ - Unread badge: green dot when conversation has unread messages
544
+ - Online status dot: always green (placeholder for future live status)
545
+ - Search filters by agent name or title (case-insensitive)
546
+ - Desktop variant: fixed 300px sidebar with border-right separator
547
+ - Mobile variant: full-width list, hidden when an agent is selected
548
+
549
+ **Used by:** `app/chat/page.tsx` (ChatPage)
550
+
551
+ ---
552
+
553
+ ### VoiceMessage
554
+
555
+ **File:** `components/chat/VoiceMessage.tsx`
556
+ **Purpose:** Audio waveform playback component with play/pause toggle and animated progress visualization.
557
+
558
+ | Prop | Type | Required | Description |
559
+ |------|------|----------|-------------|
560
+ | src | `string` | Yes | Audio source URL (base64 data URL) |
561
+ | duration | `number` | Yes | Duration in seconds |
562
+ | waveform | `number[]` | Yes | Amplitude samples (40-60 values, 0-1 range) |
563
+ | isUser | `boolean` | Yes | Whether this is the user's message (affects color) |
564
+
565
+ **Key state:**
566
+ - `isPlaying` (useState) -- playback state
567
+ - `progress` (useState) -- playback progress (0-1)
568
+ - `audioRef` (useRef) -- HTMLAudioElement reference
569
+
570
+ **Implementation:**
571
+ - Renders amplitude bars as vertical `<div>` elements with heights proportional to waveform values
572
+ - Progress tracking: bars before the playback position get accent color, bars after get muted color
573
+ - Uses `timeupdate` event on `<audio>` element to update progress
574
+ - Play/pause toggle with Lucide Play/Pause icons
575
+ - Duration display formatted as `m:ss`
576
+
577
+ **Used by:** `components/chat/ConversationView.tsx`
578
+
579
+ ---
580
+
581
+ ### FileAttachment
582
+
583
+ **File:** `components/chat/FileAttachment.tsx`
584
+ **Purpose:** File attachment bubble with type-specific icon and download button.
585
+
586
+ | Prop | Type | Required | Description |
587
+ |------|------|----------|-------------|
588
+ | name | `string` | Yes | File name |
589
+ | size | `number` | No | File size in bytes |
590
+ | mimeType | `string` | No | MIME type for icon selection |
591
+ | url | `string` | Yes | Download URL (base64 data URL) |
592
+ | isUser | `boolean` | Yes | Whether this is the user's message (affects color) |
593
+
594
+ **Implementation:**
595
+ - Type-specific SVG icons: PDF (red), document (blue), text (gray), archive (yellow), generic (gray)
596
+ - Icon selected by MIME type matching
597
+ - File size formatted as human-readable (B, KB, MB)
598
+ - Download button creates a temporary `<a>` element with `download` attribute
599
+ - Styled differently for user vs assistant messages
600
+
601
+ **Used by:** `components/chat/ConversationView.tsx`
602
+
603
+ ---
604
+
605
+ ### MediaPreview
606
+
607
+ **File:** `components/chat/MediaPreview.tsx`
608
+ **Purpose:** Horizontal strip of staged attachment thumbnails shown below the chat input before sending.
609
+
610
+ | Prop | Type | Required | Description |
611
+ |------|------|----------|-------------|
612
+ | attachments | `MediaAttachment[]` | Yes | Staged attachments to preview |
613
+ | onRemove | `(index: number) => void` | Yes | Callback to remove an attachment by index |
614
+
615
+ **Implementation:**
616
+ - Horizontal scroll container with gap between items
617
+ - Images: renders thumbnail preview with object-fit cover
618
+ - Non-images: renders file icon with name and size
619
+ - Each item has an X button overlay for removal
620
+ - Thumbnails sized at 80x80px with rounded corners
621
+
622
+ **Used by:** `components/chat/ConversationView.tsx`
623
+
624
+ ---
625
+
626
+ ## Kanban Components
627
+
628
+ ### KanbanBoard
629
+
630
+ **File:** `components/kanban/KanbanBoard.tsx`
631
+ **Purpose:** Renders the kanban board as a row of columns with ticket cards.
632
+
633
+ | Prop | Type | Required | Description |
634
+ |------|------|----------|-------------|
635
+ | tickets | `KanbanTicket[]` | Yes | All tickets |
636
+ | agents | `Agent[]` | Yes | All agents (for avatar rendering) |
637
+ | onTicketClick | `(ticket: KanbanTicket) => void` | Yes | Ticket selection callback |
638
+ | onMoveTicket | `(ticketId: string, status: string) => void` | Yes | Ticket move callback |
639
+ | onCreateTicket | `(status: string) => void` | Yes | Create ticket callback |
640
+ | isWorking | `(ticketId: string) => boolean` | No | Whether a ticket has active agent work |
641
+ | filterAgentId | `string \| null` | No | Filter tickets to a specific agent |
642
+
643
+ **Implementation:**
644
+ - Four columns: Backlog, In Progress, Review, Done
645
+ - Filters tickets by `filterAgentId` if set
646
+ - Groups tickets into columns by `status` field
647
+ - Each column rendered as `KanbanColumn` with `TicketCard` children
648
+ - Horizontal scroll on mobile with `overflow-x-auto`
649
+
650
+ **Used by:** `app/kanban/page.tsx`
651
+
652
+ ---
653
+
654
+ ### KanbanColumn
655
+
656
+ **File:** `components/kanban/KanbanColumn.tsx`
657
+ **Purpose:** Single kanban column with drag-and-drop support.
658
+
659
+ | Prop | Type | Required | Description |
660
+ |------|------|----------|-------------|
661
+ | column | `{ id: string; title: string }` | Yes | Column metadata |
662
+ | tickets | `KanbanTicket[]` | Yes | Tickets in this column |
663
+ | agents | `Agent[]` | Yes | All agents |
664
+ | onTicketClick | `(ticket: KanbanTicket) => void` | Yes | Ticket click callback |
665
+ | onDrop | `(ticketId: string) => void` | Yes | Drop callback (ticket moved to this column) |
666
+ | onCreateTicket | `() => void` | No | Create ticket callback (shown in backlog column) |
667
+ | renderTicket | `(ticket: KanbanTicket) => ReactNode` | Yes | Ticket render function |
668
+
669
+ **Key state:**
670
+ - `dragOver` (useState) -- whether a ticket is being dragged over this column
671
+
672
+ **Implementation:**
673
+ - Column header with title, ticket count badge, and optional + button (backlog only)
674
+ - HTML5 drag-and-drop: `onDragOver` sets visual feedback, `onDrop` extracts ticket ID from `dataTransfer`
675
+ - Drop zone highlight: accent-colored border when dragging over
676
+ - Scrollable ticket list with vertical overflow
677
+
678
+ **Used by:** `components/kanban/KanbanBoard.tsx`
679
+
680
+ ---
681
+
682
+ ### TicketCard
683
+
684
+ **File:** `components/kanban/TicketCard.tsx`
685
+ **Purpose:** Draggable ticket card displaying ticket metadata and agent assignment.
686
+
687
+ | Prop | Type | Required | Description |
688
+ |------|------|----------|-------------|
689
+ | ticket | `KanbanTicket` | Yes | Ticket data |
690
+ | agent | `Agent \| undefined` | Yes | Assigned agent (if any) |
691
+ | onClick | `() => void` | Yes | Click callback |
692
+ | isWorking | `boolean` | No | Whether agent work is in progress |
693
+
694
+ **Implementation:**
695
+ - HTML5 draggable: sets `ticketId` in `dataTransfer` on drag start
696
+ - Visual elements: priority dot (color-coded: red=urgent, orange=high, blue=medium, gray=low), title, description preview (truncated), assigned agent avatar + name, role badge, relative timestamp
697
+ - Work state indicator: pulsing dot and "Working..." label when `isWorking` is true
698
+ - Opacity reduction during drag
699
+
700
+ **Used by:** `components/kanban/KanbanBoard.tsx` (via `KanbanColumn`)
701
+
702
+ ---
703
+
704
+ ### TicketDetailPanel
705
+
706
+ **File:** `components/kanban/TicketDetailPanel.tsx`
707
+ **Purpose:** Slide-in side panel for viewing ticket details and chatting with the assigned agent about the ticket.
708
+
709
+ | Prop | Type | Required | Description |
710
+ |------|------|----------|-------------|
711
+ | ticket | `KanbanTicket` | Yes | Ticket to display |
712
+ | agent | `Agent \| undefined` | Yes | Assigned agent |
713
+ | onClose | `() => void` | Yes | Close callback |
714
+ | onStatusChange | `(status: string) => void` | Yes | Status change callback |
715
+ | onDelete | `() => void` | Yes | Delete callback |
716
+ | onRetryWork | `() => void` | No | Retry agent work callback |
717
+
718
+ **Key state:**
719
+ - `chatMessages` (useState) -- inline chat message history
720
+ - `chatInput` (useState) -- chat input text
721
+ - `isStreaming` (useState) -- SSE response in progress
722
+ - `streamingText` (useState) -- accumulated response text
723
+ - `expanded` (useState) -- panel width toggle (narrow/wide)
724
+
725
+ **Implementation:**
726
+ - Slide-in panel from right side (400px default, 600px expanded)
727
+ - Header: ticket title, priority badge, expand/collapse toggle, close button
728
+ - Ticket details: description, status selector (dropdown), assigned agent, role, timestamps
729
+ - Work result section: shows agent work output if available, with retry button
730
+ - Inline chat: SSE streaming to `/api/chat/[id]`, markdown rendering (bold, italic, code, code blocks, links, lists)
731
+ - Status selector: dropdown with all column statuses
732
+ - Delete button with confirmation (red styling)
733
+
734
+ **Used by:** `app/kanban/page.tsx`
735
+
736
+ ---
737
+
738
+ ### CreateTicketModal
739
+
740
+ **File:** `components/kanban/CreateTicketModal.tsx`
741
+ **Purpose:** Modal dialog for creating new kanban tickets with title, description, priority, agent assignment, and role.
742
+
743
+ | Prop | Type | Required | Description |
744
+ |------|------|----------|-------------|
745
+ | open | `boolean` | Yes | Whether the modal is visible |
746
+ | onOpenChange | `(open: boolean) => void` | Yes | Visibility change callback |
747
+ | agents | `Agent[]` | Yes | Available agents for assignment |
748
+ | onSubmit | `(data: CreateTicketData) => void` | Yes | Form submission callback |
749
+
750
+ **Key state:**
751
+ - `title` / `description` (useState) -- form fields
752
+ - `priority` (useState) -- `'low' | 'medium' | 'high' | 'urgent'`
753
+ - `agentId` (useState) -- assigned agent ID
754
+ - `role` (useState) -- `'executor' | 'reviewer' | 'consultant'`
755
+
756
+ **Implementation:**
757
+ - Uses Radix Dialog (`components/ui/dialog`) for modal behavior
758
+ - Priority selector: four buttons with color-coded dots
759
+ - Agent assignment: uses `AgentPicker` component
760
+ - Role selector: three buttons (Executor, Reviewer, Consultant)
761
+ - Form resets on close
762
+ - Submit disabled when title is empty
763
+
764
+ **Used by:** `app/kanban/page.tsx`
765
+
766
+ ---
767
+
768
+ ### AgentPicker
769
+
770
+ **File:** `components/kanban/AgentPicker.tsx`
771
+ **Purpose:** Custom dropdown selector for choosing an agent, with search and keyboard navigation.
772
+
773
+ | Prop | Type | Required | Description |
774
+ |------|------|----------|-------------|
775
+ | agents | `Agent[]` | Yes | Available agents |
776
+ | value | `string \| null` | Yes | Selected agent ID |
777
+ | onChange | `(agentId: string \| null) => void` | Yes | Selection callback |
778
+
779
+ **Key state:**
780
+ - `open` (useState) -- dropdown visibility
781
+ - `search` (useState) -- filter text
782
+ - `highlightIndex` (useState) -- keyboard navigation position
783
+
784
+ **Implementation:**
785
+ - Trigger button shows selected agent's avatar + name, or "Unassigned"
786
+ - Dropdown with search input at top
787
+ - Keyboard navigation: ArrowUp/ArrowDown to move, Enter to select, Escape to close
788
+ - "Unassigned" option always shown first
789
+ - Filtered list shows agent avatar, name, and title
790
+ - Checkmark icon on selected agent
791
+ - Closes on selection or outside click
792
+
793
+ **Used by:** `components/kanban/CreateTicketModal.tsx`
794
+
795
+ ---
796
+
797
+ ## Cron Components
798
+
799
+ ### WeeklySchedule
800
+
801
+ **File:** `components/crons/WeeklySchedule.tsx`
802
+ **Purpose:** Seven-day calendar grid showing when cron jobs are scheduled to run.
803
+
804
+ | Prop | Type | Required | Description |
805
+ |------|------|----------|-------------|
806
+ | crons | `CronJob[]` | Yes | Cron jobs to display |
807
+
808
+ **Key state:**
809
+ - `hoveredCron` (useState) -- cron ID for tooltip display
810
+ - `now` (useState) -- current time, updated every 60s for the time indicator
811
+
812
+ **Implementation:**
813
+ - 7-column grid (Mon-Sun) with hour rows for active schedule range
814
+ - Parses cron schedule expressions to determine which hours/days each cron runs
815
+ - Cron pills: colored bars positioned in the appropriate day/hour cells, using the assigned agent's color
816
+ - Status dots on pills: green (ok), red (error), gray (idle)
817
+ - Tooltip on hover: shows cron name, schedule expression, last run time, status
818
+ - Current time indicator: red horizontal line at the current hour position
819
+ - Hour labels on the left axis
820
+
821
+ **Used by:** `app/crons/page.tsx` (schedule tab)
822
+
823
+ ---
824
+
825
+ ### PipelineGraph
826
+
827
+ **File:** `components/crons/PipelineGraph.tsx`
828
+ **Purpose:** React Flow visualization of cron job pipelines showing dependencies between stages.
829
+
830
+ | Prop | Type | Required | Description |
831
+ |------|------|----------|-------------|
832
+ | crons | `CronJob[]` | Yes | Cron jobs to visualize |
833
+
834
+ **Implementation:**
835
+ - Groups crons into pipelines (crons with `pipeline` field) and standalone crons
836
+ - Topological layout: arranges pipeline stages left-to-right based on dependency order
837
+ - Contains inline `CronPipelineNode` custom React Flow node: shows cron name, schedule, agent, status badge
838
+ - Contains inline `StandaloneCrons` component: grid of non-pipeline crons
839
+ - Edges connect pipeline stages with animated flow indicators
840
+ - Node colors based on status: green (ok), red (error), gray (idle)
841
+ - Uses `ReactFlow` with `fitView` and pan/zoom controls
842
+
843
+ **Used by:** `app/crons/page.tsx` (pipelines tab)
844
+
845
+ ---
846
+
847
+ ## Page Components
848
+
849
+ ### HomePage (Home)
850
+
851
+ **File:** `app/page.tsx`
852
+ **Purpose:** Main dashboard with three view modes (map, grid, feed) and agent detail side panel.
853
+
854
+ **Key state:**
855
+ - `agents` (useState) -- fetched from `/api/agents`
856
+ - `crons` (useState) -- fetched from `/api/crons`
857
+ - `selectedAgent` (useState) -- agent for detail panel
858
+ - `view` (useState) -- `'map' | 'grid' | 'feed'`
859
+ - `loading` / `error` (useState)
860
+
861
+ **Implementation:**
862
+ - View mode toggle: three icon buttons in header (Map, Grid, Activity)
863
+ - Map view: dynamically imports `OrgMap` with `{ ssr: false }`
864
+ - Grid view: renders `GridView`
865
+ - Feed view: renders `FeedView`
866
+ - Agent detail panel: slide-in from right (400px) showing avatar, name, title, description, tools list, hierarchy links, cron health
867
+ - Contains inline `StatusDot` and `MapSkeleton` helper components
868
+
869
+ **Used by:** Route `/`
870
+
871
+ ---
872
+
873
+ ### ChatPage
874
+
875
+ **File:** `app/chat/page.tsx`
876
+ **Purpose:** Full messaging interface with agent selection and conversation management.
877
+
878
+ **Key state:**
879
+ - `agents` (useState) -- fetched from `/api/agents`
880
+ - `conversations` (useState) -- `Map<string, Conversation>` from localStorage
881
+ - `activeAgent` (useState) -- currently selected agent
882
+ - `loading` / `error` (useState)
883
+
884
+ **Implementation:**
885
+ - Wraps `MessengerApp` in `Suspense` boundary
886
+ - `MessengerApp` reads `agent` query param to pre-select an agent
887
+ - Desktop: two-panel layout (AgentList sidebar + ConversationView)
888
+ - Mobile: shows AgentListMobile when no agent selected, ConversationView when agent selected (with back button)
889
+ - Conversations persisted to localStorage, keyed by agent ID
890
+ - Contains inline `EmptyState` component (shown when no agent selected on desktop)
891
+
892
+ **Used by:** Route `/chat`
893
+
894
+ ---
895
+
896
+ ### ChatRedirect
897
+
898
+ **File:** `app/chat/[id]/page.tsx`
899
+ **Purpose:** Simple redirect from `/chat/[id]` to `/chat?agent=[id]`.
900
+
901
+ **Implementation:**
902
+ - Server component that uses `redirect()` from Next.js
903
+ - Enables direct agent chat links
904
+
905
+ **Used by:** Route `/chat/[id]`
906
+
907
+ ---
908
+
909
+ ### AgentDetailPage
910
+
911
+ **File:** `app/agents/[id]/page.tsx`
912
+ **Purpose:** Full agent profile page with hero section, about card, tools, hierarchy, SOUL.md viewer, crons, and voice config.
913
+
914
+ **Key state:**
915
+ - `agent` (useState) -- fetched from `/api/agents`
916
+ - `crons` (useState) -- agent's cron jobs
917
+ - `soulContent` (useState) -- SOUL.md file contents
918
+ - `showSoul` (useState) -- SOUL.md viewer toggle
919
+
920
+ **Implementation:**
921
+ - Hero section: large avatar, name, title, description, "Chat" button linking to `/chat?agent=[id]`
922
+ - About card: agent description and metadata
923
+ - Tools card: list of agent tools/capabilities
924
+ - Hierarchy card: "Reports to" and "Direct reports" links to other agent pages
925
+ - SOUL.md viewer: collapsible panel showing agent's SOUL.md content with syntax highlighting
926
+ - Crons card: list of agent's cron jobs with status badges
927
+ - Voice config card: voice settings display
928
+ - Contains inline `SoulViewer`, `CopyButton`, `Card`, `DetailSkeleton`, `StatusDot` helper components
929
+ - Uses `Breadcrumbs` component for navigation
930
+
931
+ **Used by:** Route `/agents/[id]`
932
+
933
+ ---
934
+
935
+ ### KanbanPage
936
+
937
+ **File:** `app/kanban/page.tsx`
938
+ **Purpose:** Kanban board for managing agent work tickets with drag-and-drop, CRUD, and agent work integration.
939
+
940
+ **Key state:**
941
+ - `tickets` (useState) -- from `KanbanStore` (localStorage)
942
+ - `agents` (useState) -- fetched from `/api/agents`
943
+ - `selectedTicket` (useState) -- ticket for detail panel
944
+ - `showCreate` (useState) -- create modal visibility
945
+ - `filterAgentId` (useState) -- agent filter
946
+ - `agentWork` -- from `useAgentWork()` hook
947
+
948
+ **Implementation:**
949
+ - Agent filter bar: horizontal scroll of agent avatar buttons to filter by assignee
950
+ - Renders `KanbanBoard` with all tickets and agents
951
+ - `TicketDetailPanel` opens on ticket click
952
+ - `CreateTicketModal` for new ticket creation
953
+ - Ticket CRUD: create, move (status change), delete via `KanbanStore` methods
954
+ - Agent work: `useAgentWork()` hook manages sending tickets to agents for execution, polling for results
955
+ - Retry work: re-sends ticket to agent if previous work failed
956
+
957
+ **Used by:** Route `/kanban`
958
+
959
+ ---
960
+
961
+ ### CronsPage
962
+
963
+ **File:** `app/crons/page.tsx`
964
+ **Purpose:** Cron monitoring dashboard with three tabs (overview, schedule, pipelines).
965
+
966
+ **Key state:**
967
+ - `crons` (useState) -- fetched from `/api/crons`, auto-refreshes every 60s
968
+ - `agents` (useState) -- fetched from `/api/agents`
969
+ - `tab` (useState) -- `'overview' | 'schedule' | 'pipelines'`
970
+ - `filter` (useState) -- `'all' | 'ok' | 'error' | 'idle'`
971
+ - `loading` / `error` (useState)
972
+
973
+ **Implementation:**
974
+ - **Overview tab:** Health donut chart (SVG), attention-needed cards for errored crons, delivery stats, error banners, recent runs list (lazy-loaded)
975
+ - **Schedule tab:** `WeeklySchedule` component
976
+ - **Pipelines tab:** `PipelineGraph` component
977
+ - Contains many inline helpers:
978
+ - `HealthCard` -- SVG donut chart showing ok/error/idle proportions
979
+ - `AttentionCard` -- clickable cards for crons needing attention
980
+ - `DeliveryCard` -- delivery success rate stats
981
+ - `DeliveryBadge` -- color-coded status badge
982
+ - `ErrorsBanners` -- expandable error detail banners
983
+ - `RecentRuns` -- lazy-loaded recent run history
984
+ - Auto-refresh: `setInterval` every 60s re-fetches cron data
985
+ - Filter pills in overview tab filter the cron list
986
+
987
+ **Used by:** Route `/crons`
988
+
989
+ ---
990
+
991
+ ### MemoryPage
992
+
993
+ **File:** `app/memory/page.tsx`
994
+ **Purpose:** Two-panel memory file browser with search, navigation, and content viewer.
995
+
996
+ **Key state:**
997
+ - `files` (useState) -- fetched from `/api/memory`
998
+ - `selectedFile` (useState) -- currently viewed file
999
+ - `fileContent` (useState) -- content of selected file
1000
+ - `search` (useState) -- file search filter
1001
+ - `loading` (useState)
1002
+
1003
+ **Implementation:**
1004
+ - Left panel: file tree sidebar with search input, file list with icons, keyboard navigation (arrow keys, enter)
1005
+ - Right panel: content viewer with copy and download buttons
1006
+ - Markdown files: rendered with basic markdown formatting
1007
+ - JSON files: syntax-highlighted with color-coded tokens
1008
+ - Other files: rendered as plain preformatted text
1009
+ - Contains inline `FileIcon`, `FolderIcon`, `BackArrow` helper components
1010
+ - File icons vary by extension (JSON, MD, TXT, etc.)
1011
+
1012
+ **Used by:** Route `/memory`
1013
+
1014
+ ---
1015
+
1016
+ ### SettingsPage
1017
+
1018
+ **File:** `app/settings/page.tsx`
1019
+ **Purpose:** Settings management page for accent color, branding, agent customization, and setup wizard access.
1020
+
1021
+ **Key state:**
1022
+ - `agents` (useState) -- fetched from `/api/agents`
1023
+ - `showWizard` (useState) -- re-run onboarding wizard toggle
1024
+ - `expandedAgent` (useState) -- which agent's customization section is expanded
1025
+ - `imageUploading` (useState) -- loading state during image upload
1026
+
1027
+ **Sections:**
1028
+ 1. **Accent Color** -- preset color grid (12 colors + reset to default)
1029
+ 2. **Branding** -- portal name, subtitle, emoji picker, icon upload (with Canvas API resize to 128px)
1030
+ 3. **Agent Customization** -- expandable per-agent sections to override emoji or profile image
1031
+ 4. **Danger Zone** -- reset all settings button, re-run setup wizard button
1032
+
1033
+ **Implementation:**
1034
+ - Image uploads use Canvas API (`resizeImage` helper) to resize to 128px max and convert to base64 data URL
1035
+ - Agent customization: each agent row expands to show emoji input and image upload
1036
+ - Re-run wizard: renders `<OnboardingWizard forceOpen onClose={...} />` when triggered
1037
+ - Reset all: calls `resetAll()` from settings context
1038
+
1039
+ **Used by:** Route `/settings`
1040
+
1041
+ ---
1042
+
1043
+ ## UI Primitives
1044
+
1045
+ **Directory:** `components/ui/`
1046
+
1047
+ Radix-based primitive components used throughout the app. These are standard shadcn/ui-style wrappers and are not individually documented in detail.
1048
+
1049
+ | Component | File | Radix Primitive | Purpose |
1050
+ |-----------|------|-----------------|---------|
1051
+ | Badge | `badge.tsx` | -- | Status/label badges with variant styling |
1052
+ | Button | `button.tsx` | Slot | Button with variant and size props |
1053
+ | Card | `card.tsx` | -- | Card container with header, content, footer |
1054
+ | Dialog | `dialog.tsx` | Dialog | Modal dialog with overlay |
1055
+ | ScrollArea | `scroll-area.tsx` | ScrollArea | Custom scrollbar container |
1056
+ | Separator | `separator.tsx` | Separator | Horizontal/vertical divider |
1057
+ | Skeleton | `skeleton.tsx` | -- | Loading placeholder animation |
1058
+ | Tabs | `tabs.tsx` | Tabs | Tab navigation with content panels |
1059
+ | Tooltip | `tooltip.tsx` | Tooltip | Hover tooltip with positioning |