CLAUDE.md: Add Cost Dashboard architecture section, /api/costs route, pricing model description README.md: Add Cost Dashboard to features list and intro docs/COMPONENTS.md: Add CostsPage to component tree and full Cost Components section CHANGELOG.md: Add Cost Dashboard to 0.6.0 entry Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
44 KiB
ClawPort -- Component Reference
Every React component in the codebase, grouped by area. For each component: purpose, props, key state, parent usage, and implementation notes.
Component Tree
RootLayout (app/layout.tsx)
ThemeProvider (app/providers.tsx)
SettingsProvider (app/settings-provider.tsx)
DynamicFavicon (components/DynamicFavicon.tsx)
OnboardingWizard (components/OnboardingWizard.tsx)
LiveStreamWidget (components/LiveStreamWidget.tsx)
Sidebar (components/Sidebar.tsx)
NavLinks (components/NavLinks.tsx)
ThemeToggle (components/ThemeToggle.tsx)
MobileSidebar (components/MobileSidebar.tsx)
GlobalSearch (components/GlobalSearch.tsx)
<main> (page content)
HomePage (app/page.tsx)
OrgMap (components/OrgMap.tsx)
AgentNode (components/AgentNode.tsx)
AgentAvatar (components/AgentAvatar.tsx)
GridView (components/GridView.tsx)
AgentAvatar
FeedView (components/FeedView.tsx)
ChatPage (app/chat/page.tsx)
AgentList (components/chat/AgentList.tsx)
AgentAvatar
AgentListMobile (components/chat/AgentList.tsx)
AgentAvatar
ConversationView (components/chat/ConversationView.tsx)
VoiceMessage (components/chat/VoiceMessage.tsx)
FileAttachment (components/chat/FileAttachment.tsx)
MediaPreview (components/chat/MediaPreview.tsx)
AgentDetailPage (app/agents/[id]/page.tsx)
AgentAvatar
Breadcrumbs (components/Breadcrumbs.tsx)
KanbanPage (app/kanban/page.tsx)
KanbanBoard (components/kanban/KanbanBoard.tsx)
KanbanColumn (components/kanban/KanbanColumn.tsx)
TicketCard (components/kanban/TicketCard.tsx)
AgentAvatar
TicketDetailPanel (components/kanban/TicketDetailPanel.tsx)
CreateTicketModal (components/kanban/CreateTicketModal.tsx)
AgentPicker (components/kanban/AgentPicker.tsx)
AgentAvatar
CronsPage (app/crons/page.tsx)
WeeklySchedule (components/crons/WeeklySchedule.tsx)
PipelineGraph (components/crons/PipelineGraph.tsx)
CostsPage (app/costs/page.tsx)
CostsPage (components/costs/CostsPage.tsx)
ActivityPage (app/activity/page.tsx)
LogBrowser (components/activity/LogBrowser.tsx)
MemoryPage (app/memory/page.tsx)
SettingsPage (app/settings/page.tsx)
AgentAvatar
ErrorState (components/ErrorState.tsx)
Providers
ThemeProvider
File: app/providers.tsx
Purpose: Manages the active theme and applies it to the document root via data-theme attribute.
| Prop | Type | Required | Description |
|---|---|---|---|
| children | React.ReactNode |
Yes | App content |
Context value:
{ theme: ThemeId; setTheme: (t: ThemeId) => void }
Key state:
theme(useState) -- persisted tolocalStorage('clawport-theme'), defaults to'dark'
Implementation:
- On mount, reads
localStorage('clawport-theme')and setsdata-themeattribute on<html> setThemeupdates state, localStorage, and the DOM attribute in one call- Exports
useTheme()hook for consumers
Used by: app/layout.tsx (wraps entire app)
SettingsProvider
File: app/settings-provider.tsx
Purpose: Central state management for all user-configurable settings (branding, accent color, operator name, agent display overrides).
| Prop | Type | Required | Description |
|---|---|---|---|
| children | React.ReactNode |
Yes | App content |
Context value:
interface SettingsContextValue {
settings: ClawPortSettings
setAccentColor: (color: string | null) => void
setPortalName: (name: string | null) => void
setPortalSubtitle: (subtitle: string | null) => void
setPortalEmoji: (emoji: string | null) => void
setPortalIcon: (icon: string | null) => void
setIconBgHidden: (hidden: boolean) => void
setEmojiOnly: (emojiOnly: boolean) => void
setOperatorName: (name: string | null) => void
setAgentOverride: (agentId: string, override: AgentDisplayOverride | null) => void
getAgentDisplay: (agent: Agent) => AgentDisplay
resetAll: () => void
}
Key state:
settings(useState) -- fullClawPortSettingsobject, persisted to localStorage viasaveSettings()
Implementation:
- Applies CSS custom properties (
--accent,--accent-hover,--accent-glow) directly todocument.documentElement.stylewhen accent color changes getAgentDisplay(agent)merges agent defaults with per-agent overrides (custom emoji, image, background)resetAll()clears localStorage and resets to defaults- Each setter calls
saveSettings()after updating state
Used by: app/layout.tsx (wraps entire app, inside ThemeProvider)
Layout Components
Sidebar
File: components/Sidebar.tsx
Purpose: Client wrapper that coordinates the desktop sidebar, mobile header/sidebar, and Cmd+K search palette.
Props: None
Key state:
searchOpen(useState) -- controls GlobalSearch visibility
Implementation:
- Renders
NavLinksandThemeToggleinside a fixed-width desktop sidebar (w-[220px], hidden on mobile) - Renders
MobileSidebarfor mobile viewports - Renders
GlobalSearch(always mounted, visibility controlled bysearchOpen) - Listens for custom event
clawport:open-searchto toggle search open - Dispatches search-open from MobileSidebar's search trigger
Used by: app/layout.tsx
NavLinks
File: components/NavLinks.tsx
Purpose: Sidebar navigation links with icons, badges, and operator identity footer.
Props: None
Key state:
agentCount(useState) -- fetched from/api/agentson mountcronErrorCount(useState) -- fetched from/api/cronson mount
Implementation:
- Six navigation items: Map, Kanban, Messages, Crons, Memory, Settings
- Each uses a Lucide icon (
Map,Columns3,MessageCircle,Clock,Brain,Settings) - Badge system: agent count on Map, cron error count on Crons (red dot)
- Footer shows operator initials (computed from
operatorNameviauseSettings()) and display name - Falls back to "Operator" / "??" when
operatorNameis unset - Uses
usePathname()for active link highlighting
Used by: components/Sidebar.tsx
MobileSidebar
File: components/MobileSidebar.tsx
Purpose: Fixed mobile header bar with hamburger menu and slide-out sidebar panel.
| Prop | Type | Required | Description |
|---|---|---|---|
| onOpenSearch | () => void |
No | Callback to open global search |
Key state:
isOpen(useState) -- sidebar panel visibility
Implementation:
- Fixed header bar (48px) with portal emoji/name, search icon, and hamburger button
- Slide-out panel renders NavLinks and ThemeToggle
- Closes on: route change (
usePathnameeffect), ESC key, click outside panel - Prevents body scroll when open via
overflow: hiddenon body - Only visible on mobile (
md:hidden)
Used by: components/Sidebar.tsx
GlobalSearch
File: components/GlobalSearch.tsx
Purpose: Cmd+K search palette with fuzzy search across agents, pages, and crons.
| Prop | Type | Required | Description |
|---|---|---|---|
| open | boolean |
Yes | Whether the search modal is visible |
| onClose | () => void |
Yes | Callback to close the modal |
Exports: GlobalSearch (main modal) and SearchTrigger (button that dispatches clawport:open-search)
Key state:
query(useState) -- search input textresults(useState) -- filtered search resultsselectedIndex(useState) -- keyboard navigation positionagents/crons(useState) -- fetched on open
Implementation:
- Fetches agents and crons from API when modal opens
- Fuzzy matching: checks if query characters appear in order within result label (case-insensitive)
- Results grouped by type: agents, pages (hardcoded list), crons
- Keyboard navigation: ArrowUp/ArrowDown to move, Enter to select, Escape to close
- Navigates via
router.push()on selection - Global keyboard listener for Cmd+K / Ctrl+K to open
Used by: components/Sidebar.tsx
DynamicFavicon
File: components/DynamicFavicon.tsx
Purpose: Generates and applies a dynamic favicon from the portal emoji or uploaded icon image.
Props: None (renders null)
Key state: None (effect-only component)
Implementation:
- Uses Canvas API to draw favicon onto a 64x64 canvas
- If
portalIcon(uploaded image): draws the image scaled to fill the canvas - If
portalEmoji: draws a colored circle background (using accent color) and renders the emoji as centered text - If
iconBgHidden: skips the circle background for emoji mode - Converts canvas to PNG data URL and sets it on a
<link rel="icon">element - Re-runs when
portalEmoji,portalIcon,accentColor,iconBgHidden, oremojiOnlychange
Used by: app/layout.tsx
ThemeToggle
File: components/ThemeToggle.tsx
Purpose: Theme selector rendered as a row of emoji buttons with radiogroup semantics.
Props: None
Key state: None (reads from useTheme())
Implementation:
- Renders one button per theme with emoji indicators
- Uses
role="radiogroup"andaria-checkedfor accessibility - Arrow key navigation (left/right) cycles through themes
- Highlights active theme with accent-colored border
- Themes: dark, glass, color, light, system
Used by: components/Sidebar.tsx, components/MobileSidebar.tsx
Breadcrumbs
File: components/Breadcrumbs.tsx
Purpose: Breadcrumb navigation bar with Lucide ChevronRight separators.
| Prop | Type | Required | Description |
|---|---|---|---|
| items | BreadcrumbItem[] |
Yes | Breadcrumb segments |
interface BreadcrumbItem {
label: string
href?: string // If present, renders as Link; otherwise plain text
icon?: LucideIcon
}
Implementation:
- Last item rendered as plain text (current page), all others as Next.js
Link - Optional Lucide icon rendered before each label
- Styled with
--text-secondaryand--text-primaryCSS vars
Used by: app/agents/[id]/page.tsx
ErrorState
File: components/ErrorState.tsx
Purpose: Full-screen error display with optional retry button.
| Prop | Type | Required | Description |
|---|---|---|---|
| message | string |
Yes | Error message to display |
| onRetry | () => void |
No | If provided, shows a retry button |
Implementation:
- Centered layout with warning icon, message text, and optional retry button
- Uses CSS vars for theming
Used by: app/page.tsx, app/chat/page.tsx, app/crons/page.tsx
AgentAvatar
File: components/AgentAvatar.tsx
Purpose: Renders an agent's avatar as a profile image, emoji on colored background, or emoji-only (transparent background).
| Prop | Type | Required | Description |
|---|---|---|---|
| agent | Agent |
Yes | Agent data object |
| size | number |
Yes | Avatar dimensions in pixels |
| borderRadius | number |
No | Border radius (default: 12) |
| style | CSSProperties |
No | Additional inline styles |
Key state: None
Implementation:
- Uses
useSettings().getAgentDisplay(agent)to resolve display overrides (custom emoji/image per agent) - Three render modes:
- Profile image (agent has
profileImageor override image): renders<img>with object-fit cover - Emoji on background: renders emoji text centered on a colored circle (agent's
coloror override) - Emoji only (
emojiOnlysetting): renders emoji with transparent background
- Profile image (agent has
- Background color comes from agent definition or per-agent override
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
Onboarding
OnboardingWizard
File: components/OnboardingWizard.tsx
Purpose: Five-step first-run setup wizard for configuring portal name, theme, accent color, voice chat, and feature overview.
| Prop | Type | Required | Description |
|---|---|---|---|
| forceOpen | boolean |
No | If true, opens regardless of onboarding state; pre-populates from current settings |
| onClose | () => void |
No | Callback when wizard closes (used with forceOpen from settings page) |
Key state:
step(useState) -- current wizard step (0-4)show(useState) -- visibility flagname/subtitle/emoji/operatorName(useState) -- form fields for step 0selectedTheme(useState) -- theme choice for step 1selectedAccent(useState) -- accent color for step 2micStatus(useState) -- microphone permission state for step 3 ('idle' | 'requesting' | 'granted' | 'denied' | 'error')micLevel(useState) -- real-time audio level for mic test visualization
Steps:
- Welcome (step 0) -- Portal name, subtitle, emoji picker, operator name. Live sidebar preview showing how NavLinks will look.
- Theme (step 1) -- Theme grid with preview cards. Applies theme live via
setTheme(). - Accent Color (step 2) -- Color preset grid (12 colors). Applies live via
setAccentColor(). - 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. - Overview (step 4) -- Feature summary cards (Agent Map, Chat, Kanban, Crons, Memory).
Implementation:
- First-run detection: checks
localStorage('clawport-onboarded') - When
forceOpenis true: pre-populates all fields from current settings, does NOT setclawport-onboardedon completion - Normal completion: sets
clawport-onboardedin localStorage and saves all settings - Modal overlay with backdrop blur, step indicator dots, back/next/finish navigation
- Mic test cleans up audio stream and context on unmount or step change
Used by: app/layout.tsx (always mounted, self-hides), app/settings/page.tsx (via forceOpen)
Map / Home Page Components
OrgMap
File: components/OrgMap.tsx
Purpose: React Flow org chart visualization of the agent hierarchy with interactive node selection.
| Prop | Type | Required | Description |
|---|---|---|---|
| agents | Agent[] |
Yes | All agents to render |
| crons | CronJob[] |
Yes | Cron jobs (for status indicators) |
| selectedId | string | null |
Yes | Currently selected agent ID |
| onNodeClick | (agent: Agent) => void |
Yes | Callback when a node is clicked |
Key state:
nodes/edges(useState) -- React Flow node and edge arrayshighlightedEdges(derived) -- edges connected to selected node
Implementation:
- BFS-based hierarchical layout: starts from root agent (
reportsTois null/undefined), assigns x/y positions by level - Horizontal spacing: 280px, vertical spacing: 160px
- Each node rendered as custom
AgentNodecomponent (registered vianodeTypes) - Edge highlighting: when an agent is selected, its connected edges get accent color + increased width
- Non-highlighted edges fade to 20% opacity
- Uses
ReactFlowwithfitView,panOnScroll, and zoom controls - Dynamically imported in
app/page.tsxwith{ ssr: false }to avoid SSR issues with React Flow
Used by: app/page.tsx (HomePage, map view)
AgentNode
File: components/AgentNode.tsx
Purpose: Custom React Flow node component displaying agent avatar, name, title, and status.
| Prop | Type | Required | Description |
|---|---|---|---|
| data | AgentNodeData |
Yes | Node data (passed by React Flow) |
interface AgentNodeData {
agent: Agent
selected: boolean
cronHealth?: 'ok' | 'error' | 'idle'
}
Implementation:
- Renders AgentAvatar (48px) with name, title, and optional description truncated to 2 lines
- Selected state: accent-colored border and subtle glow effect
- Cron health indicator: colored dot (green/red/gray) in top-right corner
- Uses React Flow
Handlecomponents for source (bottom) and target (top) connections - Exports
nodeTypes = { agentNode: AgentNode }for React Flow registration
Used by: components/OrgMap.tsx
GridView
File: components/GridView.tsx
Purpose: Card-based grid layout with team grouping hierarchy.
| Prop | Type | Required | Description |
|---|---|---|---|
| agents | Agent[] |
Yes | All agents |
| crons | CronJob[] |
Yes | Cron jobs for status |
| selectedId | string | null |
Yes | Currently selected agent |
| onSelect | (agent: Agent) => void |
Yes | Selection callback |
Implementation:
- Builds team hierarchy: identifies "hero" agent (root with no
reportsTo), groups agents by theirreportsTomanager, separates solo operators (no reports and not reporting to anyone besides hero) - Contains inline
AgentCardcomponent: renders avatar, name, title, description, cron health dot, and status badge - Contains inline
TeamSectioncomponent: collapsible team group with manager header - Selected card gets accent border highlight
- Responsive grid: 1 col mobile, 2 cols md, 3 cols lg
Used by: app/page.tsx (HomePage, grid view)
FeedView
File: components/FeedView.tsx
Purpose: Activity feed focused on cron job status with stat cards and filter pills.
| Prop | Type | Required | Description |
|---|---|---|---|
| agents | Agent[] |
Yes | All agents |
| crons | CronJob[] |
Yes | Cron jobs to display |
| selectedId | string | null |
Yes | Currently selected agent |
| onSelect | (agent: Agent) => void |
Yes | Selection callback |
Key state:
filter(useState) --'all' | 'ok' | 'error' | 'idle'
Implementation:
- Top stat cards: total crons, healthy count, errors count, idle count
- Filter pills toggle which crons are shown
- Crons sorted by status priority (errors first) then by
lastRundescending - Each cron row shows: agent avatar, cron name, schedule, status badge, last run time
- Contains inline
StatusBadgeandStatCardhelper components - Clicking a cron row selects its associated agent
Used by: app/page.tsx (HomePage, feed view)
Chat Components
ConversationView
File: components/chat/ConversationView.tsx
Purpose: Main chat interface with message rendering, SSE streaming, file attachments, TTS playback, and markdown formatting.
| Prop | Type | Required | Description |
|---|---|---|---|
| agent | Agent |
Yes | The agent being chatted with |
| conversation | Conversation |
Yes | Current conversation data |
| onUpdate | (conv: Conversation) => void |
Yes | Callback when conversation changes |
| onBack | () => void |
No | Mobile back button callback |
Key state:
input(useState) -- text input valueisStreaming(useState) -- whether an SSE response is in progressstreamingText(useState) -- accumulated streaming response textattachments(useState) -- stagedMediaAttachment[]before sendisRecording(useState) -- voice recording activettsPlaying(useState) -- message ID currently playing TTSttsLoading(useState) -- message ID currently loading TTS
Implementation:
- SSE streaming: POST to
/api/chat/[id]withReadableStreamreader. Parsesdata:lines, accumulates text, callsonUpdatewith final message. - Markdown rendering: Inline
renderMarkdown()function handles bold, italic, inline code, code blocks with language labels and copy buttons, links, and bullet lists. - TTS playback: Sends message text to
/api/tts, receives audio blob, plays viaAudioobject. Toggle play/stop per message. - File attachments: Three input methods -- paste (Cmd+V), drag-and-drop, and file picker button. Images resized to 1200px max via Canvas API (
resizeImagehelper). Files converted to base64 data URLs for persistence. - Voice recording: Uses
createAudioRecorder()fromlib/audio-recorder.ts. Records audio, captures waveform, transcribes via/api/transcribe, sends transcription as message. Stores audio data URL + waveform for playback. - Auto-scroll:
useEffectscrolls to bottom on new messages or streaming updates. - operatorName: Sends
operatorNamefrom settings context in POST body for system prompt injection.
Used by: app/chat/page.tsx (ChatPage)
AgentList / AgentListMobile
File: components/chat/AgentList.tsx
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).
| Prop | Type | Required | Description |
|---|---|---|---|
| agents | Agent[] |
Yes | Available agents |
| conversations | Map<string, Conversation> |
Yes | All conversations (for previews/timestamps) |
| activeId | string | null |
Yes | Currently selected agent ID |
| onSelect | (agent: Agent) => void |
Yes | Selection callback |
| loading | boolean |
No | Show skeleton loading state |
Key state:
search(useState) -- filter text for agent search
Implementation:
- Agents sorted by last activity (most recent conversation first), then alphabetically
- Each row shows: AgentAvatar, agent name, title, last message preview (truncated), relative timestamp
- Unread badge: green dot when conversation has unread messages
- Online status dot: always green (placeholder for future live status)
- Search filters by agent name or title (case-insensitive)
- Desktop variant: fixed 300px sidebar with border-right separator
- Mobile variant: full-width list, hidden when an agent is selected
Used by: app/chat/page.tsx (ChatPage)
VoiceMessage
File: components/chat/VoiceMessage.tsx
Purpose: Audio waveform playback component with play/pause toggle and animated progress visualization.
| Prop | Type | Required | Description |
|---|---|---|---|
| src | string |
Yes | Audio source URL (base64 data URL) |
| duration | number |
Yes | Duration in seconds |
| waveform | number[] |
Yes | Amplitude samples (40-60 values, 0-1 range) |
| isUser | boolean |
Yes | Whether this is the user's message (affects color) |
Key state:
isPlaying(useState) -- playback stateprogress(useState) -- playback progress (0-1)audioRef(useRef) -- HTMLAudioElement reference
Implementation:
- Renders amplitude bars as vertical
<div>elements with heights proportional to waveform values - Progress tracking: bars before the playback position get accent color, bars after get muted color
- Uses
timeupdateevent on<audio>element to update progress - Play/pause toggle with Lucide Play/Pause icons
- Duration display formatted as
m:ss
Used by: components/chat/ConversationView.tsx
FileAttachment
File: components/chat/FileAttachment.tsx
Purpose: File attachment bubble with type-specific icon and download button.
| Prop | Type | Required | Description |
|---|---|---|---|
| name | string |
Yes | File name |
| size | number |
No | File size in bytes |
| mimeType | string |
No | MIME type for icon selection |
| url | string |
Yes | Download URL (base64 data URL) |
| isUser | boolean |
Yes | Whether this is the user's message (affects color) |
Implementation:
- Type-specific SVG icons: PDF (red), document (blue), text (gray), archive (yellow), generic (gray)
- Icon selected by MIME type matching
- File size formatted as human-readable (B, KB, MB)
- Download button creates a temporary
<a>element withdownloadattribute - Styled differently for user vs assistant messages
Used by: components/chat/ConversationView.tsx
MediaPreview
File: components/chat/MediaPreview.tsx
Purpose: Horizontal strip of staged attachment thumbnails shown below the chat input before sending.
| Prop | Type | Required | Description |
|---|---|---|---|
| attachments | MediaAttachment[] |
Yes | Staged attachments to preview |
| onRemove | (index: number) => void |
Yes | Callback to remove an attachment by index |
Implementation:
- Horizontal scroll container with gap between items
- Images: renders thumbnail preview with object-fit cover
- Non-images: renders file icon with name and size
- Each item has an X button overlay for removal
- Thumbnails sized at 80x80px with rounded corners
Used by: components/chat/ConversationView.tsx
Kanban Components
KanbanBoard
File: components/kanban/KanbanBoard.tsx
Purpose: Renders the kanban board as a row of columns with ticket cards.
| Prop | Type | Required | Description |
|---|---|---|---|
| tickets | KanbanTicket[] |
Yes | All tickets |
| agents | Agent[] |
Yes | All agents (for avatar rendering) |
| onTicketClick | (ticket: KanbanTicket) => void |
Yes | Ticket selection callback |
| onMoveTicket | (ticketId: string, status: string) => void |
Yes | Ticket move callback |
| onCreateTicket | (status: string) => void |
Yes | Create ticket callback |
| isWorking | (ticketId: string) => boolean |
No | Whether a ticket has active agent work |
| filterAgentId | string | null |
No | Filter tickets to a specific agent |
Implementation:
- Four columns: Backlog, In Progress, Review, Done
- Filters tickets by
filterAgentIdif set - Groups tickets into columns by
statusfield - Each column rendered as
KanbanColumnwithTicketCardchildren - Horizontal scroll on mobile with
overflow-x-auto
Used by: app/kanban/page.tsx
KanbanColumn
File: components/kanban/KanbanColumn.tsx
Purpose: Single kanban column with drag-and-drop support.
| Prop | Type | Required | Description |
|---|---|---|---|
| column | { id: string; title: string } |
Yes | Column metadata |
| tickets | KanbanTicket[] |
Yes | Tickets in this column |
| agents | Agent[] |
Yes | All agents |
| onTicketClick | (ticket: KanbanTicket) => void |
Yes | Ticket click callback |
| onDrop | (ticketId: string) => void |
Yes | Drop callback (ticket moved to this column) |
| onCreateTicket | () => void |
No | Create ticket callback (shown in backlog column) |
| renderTicket | (ticket: KanbanTicket) => ReactNode |
Yes | Ticket render function |
Key state:
dragOver(useState) -- whether a ticket is being dragged over this column
Implementation:
- Column header with title, ticket count badge, and optional + button (backlog only)
- HTML5 drag-and-drop:
onDragOversets visual feedback,onDropextracts ticket ID fromdataTransfer - Drop zone highlight: accent-colored border when dragging over
- Scrollable ticket list with vertical overflow
Used by: components/kanban/KanbanBoard.tsx
TicketCard
File: components/kanban/TicketCard.tsx
Purpose: Draggable ticket card displaying ticket metadata and agent assignment.
| Prop | Type | Required | Description |
|---|---|---|---|
| ticket | KanbanTicket |
Yes | Ticket data |
| agent | Agent | undefined |
Yes | Assigned agent (if any) |
| onClick | () => void |
Yes | Click callback |
| isWorking | boolean |
No | Whether agent work is in progress |
Implementation:
- HTML5 draggable: sets
ticketIdindataTransferon drag start - 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
- Work state indicator: pulsing dot and "Working..." label when
isWorkingis true - Opacity reduction during drag
Used by: components/kanban/KanbanBoard.tsx (via KanbanColumn)
TicketDetailPanel
File: components/kanban/TicketDetailPanel.tsx
Purpose: Slide-in side panel for viewing ticket details and chatting with the assigned agent about the ticket.
| Prop | Type | Required | Description |
|---|---|---|---|
| ticket | KanbanTicket |
Yes | Ticket to display |
| agent | Agent | undefined |
Yes | Assigned agent |
| onClose | () => void |
Yes | Close callback |
| onStatusChange | (status: string) => void |
Yes | Status change callback |
| onDelete | () => void |
Yes | Delete callback |
| onRetryWork | () => void |
No | Retry agent work callback |
Key state:
chatMessages(useState) -- inline chat message historychatInput(useState) -- chat input textisStreaming(useState) -- SSE response in progressstreamingText(useState) -- accumulated response textexpanded(useState) -- panel width toggle (narrow/wide)
Implementation:
- Slide-in panel from right side (400px default, 600px expanded)
- Header: ticket title, priority badge, expand/collapse toggle, close button
- Ticket details: description, status selector (dropdown), assigned agent, role, timestamps
- Work result section: shows agent work output if available, with retry button
- Inline chat: SSE streaming to
/api/chat/[id], markdown rendering (bold, italic, code, code blocks, links, lists) - Status selector: dropdown with all column statuses
- Delete button with confirmation (red styling)
Used by: app/kanban/page.tsx
CreateTicketModal
File: components/kanban/CreateTicketModal.tsx
Purpose: Modal dialog for creating new kanban tickets with title, description, priority, agent assignment, and role.
| Prop | Type | Required | Description |
|---|---|---|---|
| open | boolean |
Yes | Whether the modal is visible |
| onOpenChange | (open: boolean) => void |
Yes | Visibility change callback |
| agents | Agent[] |
Yes | Available agents for assignment |
| onSubmit | (data: CreateTicketData) => void |
Yes | Form submission callback |
Key state:
title/description(useState) -- form fieldspriority(useState) --'low' | 'medium' | 'high' | 'urgent'agentId(useState) -- assigned agent IDrole(useState) --'executor' | 'reviewer' | 'consultant'
Implementation:
- Uses Radix Dialog (
components/ui/dialog) for modal behavior - Priority selector: four buttons with color-coded dots
- Agent assignment: uses
AgentPickercomponent - Role selector: three buttons (Executor, Reviewer, Consultant)
- Form resets on close
- Submit disabled when title is empty
Used by: app/kanban/page.tsx
AgentPicker
File: components/kanban/AgentPicker.tsx
Purpose: Custom dropdown selector for choosing an agent, with search and keyboard navigation.
| Prop | Type | Required | Description |
|---|---|---|---|
| agents | Agent[] |
Yes | Available agents |
| value | string | null |
Yes | Selected agent ID |
| onChange | (agentId: string | null) => void |
Yes | Selection callback |
Key state:
open(useState) -- dropdown visibilitysearch(useState) -- filter texthighlightIndex(useState) -- keyboard navigation position
Implementation:
- Trigger button shows selected agent's avatar + name, or "Unassigned"
- Dropdown with search input at top
- Keyboard navigation: ArrowUp/ArrowDown to move, Enter to select, Escape to close
- "Unassigned" option always shown first
- Filtered list shows agent avatar, name, and title
- Checkmark icon on selected agent
- Closes on selection or outside click
Used by: components/kanban/CreateTicketModal.tsx
Cron Components
WeeklySchedule
File: components/crons/WeeklySchedule.tsx
Purpose: Seven-day calendar grid showing when cron jobs are scheduled to run.
| Prop | Type | Required | Description |
|---|---|---|---|
| crons | CronJob[] |
Yes | Cron jobs to display |
Key state:
hoveredCron(useState) -- cron ID for tooltip displaynow(useState) -- current time, updated every 60s for the time indicator
Implementation:
- 7-column grid (Mon-Sun) with hour rows for active schedule range
- Parses cron schedule expressions to determine which hours/days each cron runs
- Cron pills: colored bars positioned in the appropriate day/hour cells, using the assigned agent's color
- Status dots on pills: green (ok), red (error), gray (idle)
- Tooltip on hover: shows cron name, schedule expression, last run time, status
- Current time indicator: red horizontal line at the current hour position
- Hour labels on the left axis
Used by: app/crons/page.tsx (schedule tab)
PipelineGraph
File: components/crons/PipelineGraph.tsx
Purpose: React Flow visualization of cron job pipelines showing dependencies between stages.
| Prop | Type | Required | Description |
|---|---|---|---|
| crons | CronJob[] |
Yes | Cron jobs to visualize |
Implementation:
- Groups crons into pipelines (crons with
pipelinefield) and standalone crons - Topological layout: arranges pipeline stages left-to-right based on dependency order
- Contains inline
CronPipelineNodecustom React Flow node: shows cron name, schedule, agent, status badge - Contains inline
StandaloneCronscomponent: grid of non-pipeline crons - Edges connect pipeline stages with animated flow indicators
- Node colors based on status: green (ok), red (error), gray (idle)
- Uses
ReactFlowwithfitViewand pan/zoom controls
Used by: app/crons/page.tsx (pipelines tab)
Cost Components
CostsPage
File: components/costs/CostsPage.tsx
Purpose: Full cost dashboard with token usage analysis, daily cost chart, per-job breakdown, model distribution, anomaly detection, and week-over-week trends.
Key state:
summary(useState) --CostSummaryfetched from/api/costscrons(useState) --CronJob[]for job name resolutionhover(useState) -- chart hover state for daily cost bars
Implementation:
- Fetches cost data from
/api/costs(computed from cron run token usage) - Summary cards: total cost, top spender, week-over-week change (with trend arrows), cache savings
- Daily cost bar chart (SVG) with hover tooltips
- Per-job cost table sorted by total cost descending
- Model breakdown showing token distribution percentages
- Anomaly alerts for runs exceeding 5x median token usage
- All cost computation in
lib/costs.ts(pricing lookup, job aggregation, daily rollup, anomaly detection)
Used by: app/costs/page.tsx
Activity Components
ActivityPage
File: app/activity/page.tsx
Purpose: Activity Console with summary cards (total events, errors, sources) and log browser. Header includes an "Open Live Stream" button that dispatches clawport:open-stream-widget to open the global floating widget.
Key state:
entries(useState) -- log entries fetched from/api/logssummary(useState) -- computed log summary (counts, sources, time range)filter(useState) -- log level filter (all,error,config,cron)
Implementation:
- Fetches historical logs from
/api/logson mount, polls every 60 seconds - Summary cards show total events, error count (with pulse animation), and source breakdown
- "Open Live Stream" button dispatches
CustomEvent('clawport:open-stream-widget')
Used by: Route /activity
LogBrowser
File: components/activity/LogBrowser.tsx
Purpose: Filterable, searchable table of historical log entries with level badges and expandable details.
Used by: app/activity/page.tsx
LiveStreamWidget
File: components/LiveStreamWidget.tsx
Purpose: Global floating widget for live log streaming via SSE. Mounted in root layout, persists across navigation.
Props: None (self-contained, listens for clawport:open-stream-widget DOM event)
Key state:
state(useState) --'hidden'|'collapsed'|'expanded'lines(useState) --LiveLogLine[](max 500, ring buffer)streaming(useState) -- whether SSE connection is activeautoScroll(useState) -- tracks manual scroll detection
Implementation:
- Three visual states: hidden (default, returns null), collapsed pill (bottom-right), expanded panel (440x400)
- SSE stream via
fetch('/api/logs/stream')withparseSSEBuffer()fromlib/sse.ts - Each log row shows time, level pill (INF/WRN/ERR/DBG), truncated message
- Click any row to expand raw JSON payload (pretty-printed)
- Header: status dot, line count, copy all, minimize, close
- Footer: play/pause toggle, scroll-to-bottom button
- Copy formats lines as
[HH:MM:SS] [level] message - Collapsing does NOT stop the stream; close stops + hides
- z-index: 50 (below OnboardingWizard)
Used by: app/layout.tsx (global mount)
Page Components
HomePage (Home)
File: app/page.tsx
Purpose: Main dashboard with three view modes (map, grid, feed) and agent detail side panel.
Key state:
agents(useState) -- fetched from/api/agentscrons(useState) -- fetched from/api/cronsselectedAgent(useState) -- agent for detail panelview(useState) --'map' | 'grid' | 'feed'loading/error(useState)
Implementation:
- View mode toggle: three icon buttons in header (Map, Grid, Activity)
- Map view: dynamically imports
OrgMapwith{ ssr: false } - Grid view: renders
GridView - Feed view: renders
FeedView - Agent detail panel: slide-in from right (400px) showing avatar, name, title, description, tools list, hierarchy links, cron health
- Contains inline
StatusDotandMapSkeletonhelper components
Used by: Route /
ChatPage
File: app/chat/page.tsx
Purpose: Full messaging interface with agent selection and conversation management.
Key state:
agents(useState) -- fetched from/api/agentsconversations(useState) --Map<string, Conversation>from localStorageactiveAgent(useState) -- currently selected agentloading/error(useState)
Implementation:
- Wraps
MessengerAppinSuspenseboundary MessengerAppreadsagentquery param to pre-select an agent- Desktop: two-panel layout (AgentList sidebar + ConversationView)
- Mobile: shows AgentListMobile when no agent selected, ConversationView when agent selected (with back button)
- Conversations persisted to localStorage, keyed by agent ID
- Contains inline
EmptyStatecomponent (shown when no agent selected on desktop)
Used by: Route /chat
ChatRedirect
File: app/chat/[id]/page.tsx
Purpose: Simple redirect from /chat/[id] to /chat?agent=[id].
Implementation:
- Server component that uses
redirect()from Next.js - Enables direct agent chat links
Used by: Route /chat/[id]
AgentDetailPage
File: app/agents/[id]/page.tsx
Purpose: Full agent profile page with hero section, about card, tools, hierarchy, SOUL.md viewer, crons, and voice config.
Key state:
agent(useState) -- fetched from/api/agentscrons(useState) -- agent's cron jobssoulContent(useState) -- SOUL.md file contentsshowSoul(useState) -- SOUL.md viewer toggle
Implementation:
- Hero section: large avatar, name, title, description, "Chat" button linking to
/chat?agent=[id] - About card: agent description and metadata
- Tools card: list of agent tools/capabilities
- Hierarchy card: "Reports to" and "Direct reports" links to other agent pages
- SOUL.md viewer: collapsible panel showing agent's SOUL.md content with syntax highlighting
- Crons card: list of agent's cron jobs with status badges
- Voice config card: voice settings display
- Contains inline
SoulViewer,CopyButton,Card,DetailSkeleton,StatusDothelper components - Uses
Breadcrumbscomponent for navigation
Used by: Route /agents/[id]
KanbanPage
File: app/kanban/page.tsx
Purpose: Kanban board for managing agent work tickets with drag-and-drop, CRUD, and agent work integration.
Key state:
tickets(useState) -- fromKanbanStore(localStorage)agents(useState) -- fetched from/api/agentsselectedTicket(useState) -- ticket for detail panelshowCreate(useState) -- create modal visibilityfilterAgentId(useState) -- agent filteragentWork-- fromuseAgentWork()hook
Implementation:
- Agent filter bar: horizontal scroll of agent avatar buttons to filter by assignee
- Renders
KanbanBoardwith all tickets and agents TicketDetailPanelopens on ticket clickCreateTicketModalfor new ticket creation- Ticket CRUD: create, move (status change), delete via
KanbanStoremethods - Agent work:
useAgentWork()hook manages sending tickets to agents for execution, polling for results - Retry work: re-sends ticket to agent if previous work failed
Used by: Route /kanban
CronsPage
File: app/crons/page.tsx
Purpose: Cron monitoring dashboard with three tabs (overview, schedule, pipelines).
Key state:
crons(useState) -- fetched from/api/crons, auto-refreshes every 60sagents(useState) -- fetched from/api/agentstab(useState) --'overview' | 'schedule' | 'pipelines'filter(useState) --'all' | 'ok' | 'error' | 'idle'loading/error(useState)
Implementation:
- Overview tab: Health donut chart (SVG), attention-needed cards for errored crons, delivery stats, error banners, recent runs list (lazy-loaded)
- Schedule tab:
WeeklySchedulecomponent - Pipelines tab:
PipelineGraphcomponent - Contains many inline helpers:
HealthCard-- SVG donut chart showing ok/error/idle proportionsAttentionCard-- clickable cards for crons needing attentionDeliveryCard-- delivery success rate statsDeliveryBadge-- color-coded status badgeErrorsBanners-- expandable error detail bannersRecentRuns-- lazy-loaded recent run history
- Auto-refresh:
setIntervalevery 60s re-fetches cron data - Filter pills in overview tab filter the cron list
Used by: Route /crons
MemoryPage
File: app/memory/page.tsx
Purpose: Two-panel memory file browser with search, navigation, and content viewer.
Key state:
files(useState) -- fetched from/api/memoryselectedFile(useState) -- currently viewed filefileContent(useState) -- content of selected filesearch(useState) -- file search filterloading(useState)
Implementation:
- Left panel: file tree sidebar with search input, file list with icons, keyboard navigation (arrow keys, enter)
- Right panel: content viewer with copy and download buttons
- Markdown files: rendered with basic markdown formatting
- JSON files: syntax-highlighted with color-coded tokens
- Other files: rendered as plain preformatted text
- Contains inline
FileIcon,FolderIcon,BackArrowhelper components - File icons vary by extension (JSON, MD, TXT, etc.)
Used by: Route /memory
SettingsPage
File: app/settings/page.tsx
Purpose: Settings management page for accent color, branding, agent customization, and setup wizard access.
Key state:
agents(useState) -- fetched from/api/agentsshowWizard(useState) -- re-run onboarding wizard toggleexpandedAgent(useState) -- which agent's customization section is expandedimageUploading(useState) -- loading state during image upload
Sections:
- Accent Color -- preset color grid (12 colors + reset to default)
- Branding -- portal name, subtitle, emoji picker, icon upload (with Canvas API resize to 128px)
- Agent Customization -- expandable per-agent sections to override emoji or profile image
- Danger Zone -- reset all settings button, re-run setup wizard button
Implementation:
- Image uploads use Canvas API (
resizeImagehelper) to resize to 128px max and convert to base64 data URL - Agent customization: each agent row expands to show emoji input and image upload
- Re-run wizard: renders
<OnboardingWizard forceOpen onClose={...} />when triggered - Reset all: calls
resetAll()from settings context
Used by: Route /settings
UI Primitives
Directory: components/ui/
Radix-based primitive components used throughout the app. These are standard shadcn/ui-style wrappers and are not individually documented in detail.
| Component | File | Radix Primitive | Purpose |
|---|---|---|---|
| Badge | badge.tsx |
-- | Status/label badges with variant styling |
| Button | button.tsx |
Slot | Button with variant and size props |
| Card | card.tsx |
-- | Card container with header, content, footer |
| Dialog | dialog.tsx |
Dialog | Modal dialog with overlay |
| ScrollArea | scroll-area.tsx |
ScrollArea | Custom scrollbar container |
| Separator | separator.tsx |
Separator | Horizontal/vertical divider |
| Skeleton | skeleton.tsx |
-- | Loading placeholder animation |
| Tabs | tabs.tsx |
Tabs | Tab navigation with content panels |
| Tooltip | tooltip.tsx |
Tooltip | Hover tooltip with positioning |