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.
- package/.env.example +35 -0
- package/BRANDING.md +131 -0
- package/CLAUDE.md +252 -0
- package/README.md +262 -0
- package/SETUP.md +337 -0
- package/app/agents/[id]/page.tsx +727 -0
- package/app/api/agents/route.ts +12 -0
- package/app/api/chat/[id]/route.ts +139 -0
- package/app/api/cron-runs/route.ts +13 -0
- package/app/api/crons/route.ts +12 -0
- package/app/api/kanban/chat/[id]/route.ts +119 -0
- package/app/api/kanban/chat-history/[ticketId]/route.ts +36 -0
- package/app/api/memory/route.ts +12 -0
- package/app/api/transcribe/route.ts +37 -0
- package/app/api/tts/route.ts +42 -0
- package/app/chat/[id]/page.tsx +10 -0
- package/app/chat/page.tsx +200 -0
- package/app/crons/page.tsx +870 -0
- package/app/docs/page.tsx +399 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +692 -0
- package/app/kanban/page.tsx +327 -0
- package/app/layout.tsx +45 -0
- package/app/memory/page.tsx +685 -0
- package/app/page.tsx +817 -0
- package/app/providers.tsx +37 -0
- package/app/settings/page.tsx +901 -0
- package/app/settings-provider.tsx +209 -0
- package/components/AgentAvatar.tsx +54 -0
- package/components/AgentNode.tsx +122 -0
- package/components/Breadcrumbs.tsx +126 -0
- package/components/DynamicFavicon.tsx +62 -0
- package/components/ErrorState.tsx +97 -0
- package/components/FeedView.tsx +494 -0
- package/components/GlobalSearch.tsx +571 -0
- package/components/GridView.tsx +532 -0
- package/components/ManorMap.tsx +157 -0
- package/components/MobileSidebar.tsx +251 -0
- package/components/NavLinks.tsx +271 -0
- package/components/OnboardingWizard.tsx +1067 -0
- package/components/Sidebar.tsx +115 -0
- package/components/ThemeToggle.tsx +108 -0
- package/components/chat/AgentList.tsx +537 -0
- package/components/chat/ConversationView.tsx +1047 -0
- package/components/chat/FileAttachment.tsx +140 -0
- package/components/chat/MediaPreview.tsx +111 -0
- package/components/chat/VoiceMessage.tsx +139 -0
- package/components/crons/PipelineGraph.tsx +327 -0
- package/components/crons/WeeklySchedule.tsx +630 -0
- package/components/docs/AgentsSection.tsx +209 -0
- package/components/docs/ApiReferenceSection.tsx +256 -0
- package/components/docs/ArchitectureSection.tsx +221 -0
- package/components/docs/ComponentsSection.tsx +253 -0
- package/components/docs/CronSystemSection.tsx +235 -0
- package/components/docs/DocSection.tsx +346 -0
- package/components/docs/GettingStartedSection.tsx +169 -0
- package/components/docs/ThemingSection.tsx +257 -0
- package/components/docs/TroubleshootingSection.tsx +200 -0
- package/components/kanban/AgentPicker.tsx +321 -0
- package/components/kanban/CreateTicketModal.tsx +333 -0
- package/components/kanban/KanbanBoard.tsx +70 -0
- package/components/kanban/KanbanColumn.tsx +166 -0
- package/components/kanban/TicketCard.tsx +245 -0
- package/components/kanban/TicketDetailPanel.tsx +850 -0
- package/components/ui/badge.tsx +48 -0
- package/components/ui/button.tsx +64 -0
- package/components/ui/card.tsx +92 -0
- package/components/ui/dialog.tsx +158 -0
- package/components/ui/scroll-area.tsx +58 -0
- package/components/ui/separator.tsx +28 -0
- package/components/ui/skeleton.tsx +27 -0
- package/components/ui/tabs.tsx +91 -0
- package/components/ui/tooltip.tsx +57 -0
- package/components.json +23 -0
- package/docs/API.md +648 -0
- package/docs/COMPONENTS.md +1059 -0
- package/docs/THEMING.md +795 -0
- package/lib/agents-registry.ts +35 -0
- package/lib/agents.json +282 -0
- package/lib/agents.test.ts +367 -0
- package/lib/agents.ts +32 -0
- package/lib/anthropic.test.ts +422 -0
- package/lib/anthropic.ts +220 -0
- package/lib/api-error.ts +16 -0
- package/lib/audio-recorder.test.ts +72 -0
- package/lib/audio-recorder.ts +169 -0
- package/lib/conversations.test.ts +331 -0
- package/lib/conversations.ts +117 -0
- package/lib/cron-pipelines.test.ts +69 -0
- package/lib/cron-pipelines.ts +58 -0
- package/lib/cron-runs.test.ts +118 -0
- package/lib/cron-runs.ts +67 -0
- package/lib/cron-utils.test.ts +222 -0
- package/lib/cron-utils.ts +160 -0
- package/lib/crons.test.ts +502 -0
- package/lib/crons.ts +114 -0
- package/lib/env.test.ts +44 -0
- package/lib/env.ts +14 -0
- package/lib/kanban/automation.test.ts +245 -0
- package/lib/kanban/automation.ts +143 -0
- package/lib/kanban/chat-store.test.ts +149 -0
- package/lib/kanban/chat-store.ts +81 -0
- package/lib/kanban/store.test.ts +238 -0
- package/lib/kanban/store.ts +98 -0
- package/lib/kanban/types.ts +50 -0
- package/lib/kanban/useAgentWork.ts +78 -0
- package/lib/memory.ts +45 -0
- package/lib/multimodal.test.ts +219 -0
- package/lib/multimodal.ts +68 -0
- package/lib/pipeline.integration.test.ts +343 -0
- package/lib/sanitize.ts +194 -0
- package/lib/settings.test.ts +137 -0
- package/lib/settings.ts +94 -0
- package/lib/styles.ts +24 -0
- package/lib/themes.ts +9 -0
- package/lib/transcribe.test.ts +141 -0
- package/lib/transcribe.ts +111 -0
- package/lib/types.ts +66 -0
- package/lib/utils.ts +6 -0
- package/lib/validation.test.ts +132 -0
- package/lib/validation.ts +80 -0
- package/next.config.ts +7 -0
- package/package.json +56 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/scripts/setup.mjs +215 -0
- package/tsconfig.json +34 -0
- package/vitest.config.ts +17 -0
package/docs/THEMING.md
ADDED
|
@@ -0,0 +1,795 @@
|
|
|
1
|
+
# ClawPort -- Theming, Settings & Customization Guide
|
|
2
|
+
|
|
3
|
+
This document covers ClawPort's visual theming system, settings architecture, and step-by-step
|
|
4
|
+
instructions for extending both. Everything is driven by CSS custom properties and two React
|
|
5
|
+
context providers: `ThemeProvider` and `SettingsProvider`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
1. [Themes](#themes)
|
|
12
|
+
- [Available Themes](#available-themes)
|
|
13
|
+
- [How Themes Work](#how-themes-work)
|
|
14
|
+
- [CSS Custom Property Tokens](#css-custom-property-tokens)
|
|
15
|
+
- [System Theme Detection](#system-theme-detection)
|
|
16
|
+
- [Theme-Specific Overrides](#theme-specific-overrides)
|
|
17
|
+
- [How to Add a New Theme](#how-to-add-a-new-theme)
|
|
18
|
+
2. [Settings](#settings)
|
|
19
|
+
- [ClawPortSettings Interface](#clawportsettings-interface)
|
|
20
|
+
- [localStorage Persistence](#localstorage-persistence)
|
|
21
|
+
- [SettingsProvider API](#settingsprovider-api)
|
|
22
|
+
- [Accent Color CSS Variables](#accent-color-css-variables)
|
|
23
|
+
- [Agent Override System](#agent-override-system)
|
|
24
|
+
- [operatorName Flow](#operatorname-flow)
|
|
25
|
+
3. [Customization Guide](#customization-guide)
|
|
26
|
+
- [Change the Default Accent Color](#change-the-default-accent-color)
|
|
27
|
+
- [Add a New Setting Field](#add-a-new-setting-field)
|
|
28
|
+
- [Add a New Theme](#add-a-new-theme-step-by-step)
|
|
29
|
+
- [CSS Custom Property Naming Conventions](#css-custom-property-naming-conventions)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Themes
|
|
34
|
+
|
|
35
|
+
### Available Themes
|
|
36
|
+
|
|
37
|
+
ClawPort ships with five themes. Each has an ID, a human-readable label, and an emoji used in the
|
|
38
|
+
onboarding wizard and theme picker.
|
|
39
|
+
|
|
40
|
+
| ID | Label | Emoji | Description |
|
|
41
|
+
|----------|----------|-------|--------------------------------------------|
|
|
42
|
+
| `dark` | Dark | `\ud83c\udf11` | Apple Dark Mode. The default theme. |
|
|
43
|
+
| `glass` | Glass | `\ud83e\ude9f` | Frosted glass dark variant with translucent surfaces. |
|
|
44
|
+
| `color` | Color | `\ud83c\udfa8` | Vibrant purple-indigo variant. |
|
|
45
|
+
| `light` | Light | `\u2600\ufe0f` | Apple Light Mode. |
|
|
46
|
+
| `system` | System | `\u2699\ufe0f` | Follows the OS `prefers-color-scheme` setting. |
|
|
47
|
+
|
|
48
|
+
These are defined in `lib/themes.ts`:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
export type ThemeId = 'dark' | 'glass' | 'color' | 'light' | 'system';
|
|
52
|
+
|
|
53
|
+
export const THEMES: { id: ThemeId; label: string; emoji: string }[] = [
|
|
54
|
+
{ id: 'dark', label: 'Dark', emoji: '\ud83c\udf11' },
|
|
55
|
+
{ id: 'glass', label: 'Glass', emoji: '\ud83e\ude9f' },
|
|
56
|
+
{ id: 'color', label: 'Color', emoji: '\ud83c\udfa8' },
|
|
57
|
+
{ id: 'light', label: 'Light', emoji: '\u2600\ufe0f' },
|
|
58
|
+
{ id: 'system', label: 'System', emoji: '\u2699\ufe0f' },
|
|
59
|
+
];
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### How Themes Work
|
|
63
|
+
|
|
64
|
+
The theme system uses three layers:
|
|
65
|
+
|
|
66
|
+
1. **`data-theme` attribute on `<html>`** -- Each theme defines a CSS rule block scoped to
|
|
67
|
+
`[data-theme="<id>"]`. The `dark` theme also matches `:root` so it works without any
|
|
68
|
+
attribute set.
|
|
69
|
+
|
|
70
|
+
2. **CSS custom properties** -- Every color, shadow, radius, and material is expressed as a
|
|
71
|
+
CSS variable. Components consume these via inline styles (e.g., `style={{ color: 'var(--text-primary)' }}`)
|
|
72
|
+
or utility classes. No Tailwind color classes are used directly.
|
|
73
|
+
|
|
74
|
+
3. **ThemeProvider** (`app/providers.tsx`) -- A React context that manages theme state. On mount
|
|
75
|
+
it reads from `localStorage` key `clawport-theme`. When the user picks a theme, it:
|
|
76
|
+
- Updates React state
|
|
77
|
+
- Writes to `localStorage`
|
|
78
|
+
- Removes the existing `data-theme` attribute
|
|
79
|
+
- Sets the new `data-theme` attribute on `<html>`
|
|
80
|
+
- For the `system` theme, evaluates `window.matchMedia('(prefers-color-scheme: dark)')` and
|
|
81
|
+
resolves to either `dark` or `light`
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// app/providers.tsx (simplified)
|
|
85
|
+
function apply(t: ThemeId) {
|
|
86
|
+
setThemeState(t);
|
|
87
|
+
localStorage.setItem('clawport-theme', t);
|
|
88
|
+
const html = document.documentElement;
|
|
89
|
+
html.removeAttribute('data-theme');
|
|
90
|
+
if (t === 'system') {
|
|
91
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
92
|
+
html.setAttribute('data-theme', prefersDark ? 'dark' : 'light');
|
|
93
|
+
} else {
|
|
94
|
+
html.setAttribute('data-theme', t);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Consumer hook:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { useTheme } from '@/app/providers';
|
|
103
|
+
|
|
104
|
+
const { theme, setTheme } = useTheme();
|
|
105
|
+
// theme is the ThemeId ('dark', 'glass', etc.)
|
|
106
|
+
// setTheme('light') applies immediately
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### CSS Custom Property Tokens
|
|
110
|
+
|
|
111
|
+
All tokens are defined in `app/globals.css`. Every theme block defines the same full set of
|
|
112
|
+
variables, so components can always rely on them being present.
|
|
113
|
+
|
|
114
|
+
#### Backgrounds
|
|
115
|
+
|
|
116
|
+
| Token | Purpose | Dark example |
|
|
117
|
+
|------------------------|--------------------------------------------|---------------------------|
|
|
118
|
+
| `--bg` | Primary page background | `#000000` |
|
|
119
|
+
| `--bg-secondary` | Card / surface background | `rgba(28,28,30,1)` |
|
|
120
|
+
| `--bg-tertiary` | Nested surface / grouped background | `rgba(44,44,46,1)` |
|
|
121
|
+
|
|
122
|
+
#### Materials (Apple translucent surfaces)
|
|
123
|
+
|
|
124
|
+
| Token | Purpose | Dark example |
|
|
125
|
+
|-------------------------|-------------------------------------------|---------------------------|
|
|
126
|
+
| `--material-regular` | Standard material (sidebar, overlays) | `rgba(28,28,30,0.92)` |
|
|
127
|
+
| `--material-thick` | Dense material | `rgba(22,22,24,0.96)` |
|
|
128
|
+
| `--material-thin` | Light tint material | `rgba(255,255,255,0.06)` |
|
|
129
|
+
| `--material-ultra-thin` | Very subtle tint | `rgba(255,255,255,0.04)` |
|
|
130
|
+
|
|
131
|
+
#### Fills
|
|
132
|
+
|
|
133
|
+
| Token | Purpose | Dark example |
|
|
134
|
+
|----------------------|-----------------------------------------------|---------------------------|
|
|
135
|
+
| `--fill-primary` | Primary interactive fill (buttons, controls) | `rgba(120,120,128,0.36)` |
|
|
136
|
+
| `--fill-secondary` | Hover fill | `rgba(120,120,128,0.32)` |
|
|
137
|
+
| `--fill-tertiary` | Subtle fill (input backgrounds) | `rgba(118,118,128,0.24)` |
|
|
138
|
+
| `--fill-quaternary` | Most subtle fill | `rgba(118,118,128,0.18)` |
|
|
139
|
+
|
|
140
|
+
#### Separators & Borders
|
|
141
|
+
|
|
142
|
+
| Token | Purpose | Dark example |
|
|
143
|
+
|----------------------|-----------------------------------|---------------------------|
|
|
144
|
+
| `--separator` | Translucent divider line | `rgba(84,84,88,0.60)` |
|
|
145
|
+
| `--separator-opaque` | Opaque divider (non-blur contexts)| `#38383A` |
|
|
146
|
+
|
|
147
|
+
#### Text
|
|
148
|
+
|
|
149
|
+
| Token | Purpose | Dark example |
|
|
150
|
+
|----------------------|--------------------------------------|----------------------------|
|
|
151
|
+
| `--text-primary` | Headings, body text | `#FFFFFF` |
|
|
152
|
+
| `--text-secondary` | Labels, supporting text | `rgba(235,235,245,0.60)` |
|
|
153
|
+
| `--text-tertiary` | Placeholder, captions | `rgba(235,235,245,0.30)` |
|
|
154
|
+
| `--text-quaternary` | Disabled / lowest-priority text | `rgba(235,235,245,0.18)` |
|
|
155
|
+
|
|
156
|
+
#### Accent & System Colors
|
|
157
|
+
|
|
158
|
+
| Token | Purpose | Dark example |
|
|
159
|
+
|--------------------|----------------------------------------|---------------|
|
|
160
|
+
| `--accent` | Primary brand accent (buttons, active) | `#F5C518` |
|
|
161
|
+
| `--accent-fill` | Accent at 15% opacity (backgrounds) | `rgba(245,197,24,0.15)` |
|
|
162
|
+
| `--system-blue` | Links, focus rings | `#0A84FF` |
|
|
163
|
+
| `--system-green` | Success, active toggles | `#30D158` |
|
|
164
|
+
| `--system-red` | Errors, destructive actions | `#FF453A` |
|
|
165
|
+
| `--system-orange` | Warnings | `#FF9F0A` |
|
|
166
|
+
| `--system-purple` | Tags, highlights | `#BF5AF2` |
|
|
167
|
+
|
|
168
|
+
Note: `--accent` and `--accent-fill` can be overridden at runtime by the SettingsProvider when
|
|
169
|
+
the user picks a custom accent color. See [Accent Color CSS Variables](#accent-color-css-variables).
|
|
170
|
+
|
|
171
|
+
#### Shadows & Effects
|
|
172
|
+
|
|
173
|
+
| Token | Purpose | Dark example (abbreviated) |
|
|
174
|
+
|--------------------|-----------------------------------------|--------------------------------------|
|
|
175
|
+
| `--inset-shine` | Top inner highlight on cards | `inset 0 1px 0 rgba(255,255,255,0.08)` |
|
|
176
|
+
| `--shadow-subtle` | Minimal elevation | `0 1px 2px rgba(0,0,0,0.20)` |
|
|
177
|
+
| `--shadow-ambient` | Hairline border shadow | `0 0 0 0.5px rgba(0,0,0,0.20)` |
|
|
178
|
+
| `--shadow-key` | Primary directional shadow | `0 4px 16px rgba(0,0,0,0.40)` |
|
|
179
|
+
| `--shadow-card` | Full card elevation (ambient + key + shine) | _(composite)_ |
|
|
180
|
+
| `--shadow-overlay` | Modal / overlay elevation | _(composite)_ |
|
|
181
|
+
|
|
182
|
+
#### Code Blocks
|
|
183
|
+
|
|
184
|
+
| Token | Purpose | Dark example |
|
|
185
|
+
|-----------------|--------------------------|---------------------------|
|
|
186
|
+
| `--code-bg` | Code block background | `rgba(255,255,255,0.06)` |
|
|
187
|
+
| `--code-border` | Code block border | `rgba(255,255,255,0.10)` |
|
|
188
|
+
| `--code-text` | Code text color | `#e5e5ea` |
|
|
189
|
+
|
|
190
|
+
#### Sidebar
|
|
191
|
+
|
|
192
|
+
| Token | Purpose | Dark example |
|
|
193
|
+
|----------------------|----------------------------------|------------------------------------|
|
|
194
|
+
| `--sidebar-bg` | Sidebar background color | `rgba(28,28,30,0.92)` |
|
|
195
|
+
| `--sidebar-backdrop` | Sidebar backdrop-filter | `blur(40px) saturate(180%)` |
|
|
196
|
+
|
|
197
|
+
#### Border Radius
|
|
198
|
+
|
|
199
|
+
| Token | Value |
|
|
200
|
+
|----------------|---------|
|
|
201
|
+
| `--radius-sm` | `6px` |
|
|
202
|
+
| `--radius-md` | `12px` |
|
|
203
|
+
| `--radius-lg` | `16px` |
|
|
204
|
+
| `--radius-xl` | `20px` |
|
|
205
|
+
| `--radius-2xl` | `24px` |
|
|
206
|
+
|
|
207
|
+
#### Easing Functions
|
|
208
|
+
|
|
209
|
+
| Token | Value | Use case |
|
|
210
|
+
|-----------------|-------------------------------------|-----------------------------|
|
|
211
|
+
| `--ease-spring` | `cubic-bezier(0.34, 1.56, 0.64, 1)` | Bouncy interactions |
|
|
212
|
+
| `--ease-smooth` | `cubic-bezier(0.4, 0, 0.2, 1)` | General transitions |
|
|
213
|
+
| `--ease-snappy` | `cubic-bezier(0.2, 0, 0, 1)` | Quick state changes |
|
|
214
|
+
|
|
215
|
+
#### Typography Scale (Tailwind `@theme` tokens)
|
|
216
|
+
|
|
217
|
+
These are registered as Tailwind theme extensions so they work with utility classes (e.g.,
|
|
218
|
+
`text-caption1`). They follow the Apple Human Interface Guidelines type ramp.
|
|
219
|
+
|
|
220
|
+
| Token | Value |
|
|
221
|
+
|------------------------|--------|
|
|
222
|
+
| `--text-caption2` | `11px` |
|
|
223
|
+
| `--text-caption1` | `12px` |
|
|
224
|
+
| `--text-footnote` | `13px` |
|
|
225
|
+
| `--text-subheadline` | `15px` |
|
|
226
|
+
| `--text-body` | `17px` |
|
|
227
|
+
| `--text-title3` | `20px` |
|
|
228
|
+
| `--text-title2` | `22px` |
|
|
229
|
+
| `--text-title1` | `28px` |
|
|
230
|
+
| `--text-large-title` | `34px` |
|
|
231
|
+
|
|
232
|
+
#### Font Families
|
|
233
|
+
|
|
234
|
+
| Token | Value |
|
|
235
|
+
|---------------|--------------------------------------------------------------------------|
|
|
236
|
+
| `--font-sans` | `-apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", system-ui, sans-serif` |
|
|
237
|
+
| `--font-mono` | `"SF Mono", Monaco, Menlo, "Courier New", monospace` |
|
|
238
|
+
|
|
239
|
+
#### Leading (Line Height)
|
|
240
|
+
|
|
241
|
+
| Token | Value |
|
|
242
|
+
|--------------------|--------|
|
|
243
|
+
| `--leading-tight` | `1.15` |
|
|
244
|
+
| `--leading-snug` | `1.3` |
|
|
245
|
+
| `--leading-normal` | `1.47` |
|
|
246
|
+
| `--leading-relaxed`| `1.65` |
|
|
247
|
+
|
|
248
|
+
#### Tracking (Letter Spacing)
|
|
249
|
+
|
|
250
|
+
| Token | Value |
|
|
251
|
+
|--------------------|-----------|
|
|
252
|
+
| `--tracking-tight` | `-0.41px` |
|
|
253
|
+
| `--tracking-normal`| `-0.24px` |
|
|
254
|
+
| `--tracking-wide` | `0.07em` |
|
|
255
|
+
|
|
256
|
+
#### Font Weights
|
|
257
|
+
|
|
258
|
+
| Token | Value |
|
|
259
|
+
|--------------------|-------|
|
|
260
|
+
| `--weight-regular` | `400` |
|
|
261
|
+
| `--weight-medium` | `500` |
|
|
262
|
+
| `--weight-semibold`| `600` |
|
|
263
|
+
| `--weight-bold` | `700` |
|
|
264
|
+
|
|
265
|
+
#### Spacing Scale (4px grid)
|
|
266
|
+
|
|
267
|
+
| Token | Value |
|
|
268
|
+
|-------------|--------|
|
|
269
|
+
| `--space-1` | `4px` |
|
|
270
|
+
| `--space-2` | `8px` |
|
|
271
|
+
| `--space-3` | `12px` |
|
|
272
|
+
| `--space-4` | `16px` |
|
|
273
|
+
| `--space-5` | `20px` |
|
|
274
|
+
| `--space-6` | `24px` |
|
|
275
|
+
| `--space-8` | `32px` |
|
|
276
|
+
| `--space-10`| `40px` |
|
|
277
|
+
| `--space-12`| `48px` |
|
|
278
|
+
| `--space-16`| `64px` |
|
|
279
|
+
|
|
280
|
+
#### Animation Tokens (Tailwind `@theme`)
|
|
281
|
+
|
|
282
|
+
| Token | Value |
|
|
283
|
+
|-------------------------|-------------------------------------------|
|
|
284
|
+
| `--animate-fade-in` | `fadeIn 0.2s ease-out` |
|
|
285
|
+
| `--animate-slide-in` | `slideIn 0.2s ease-out` |
|
|
286
|
+
| `--animate-pulse-red` | `pulse-red 1.5s ease-in-out infinite` |
|
|
287
|
+
| `--animate-blink` | `blink-cursor 1s step-end infinite` |
|
|
288
|
+
| `--animate-float-hint` | `float-hint 2s ease-in-out infinite` |
|
|
289
|
+
|
|
290
|
+
### System Theme Detection
|
|
291
|
+
|
|
292
|
+
The `system` theme has two layers:
|
|
293
|
+
|
|
294
|
+
1. **CSS-only fallback** -- A `@media (prefers-color-scheme: light)` block defines all custom
|
|
295
|
+
properties for `[data-theme="system"]`. This ensures correct rendering before JavaScript
|
|
296
|
+
hydrates (dark mode inherits from `:root` which is already dark).
|
|
297
|
+
|
|
298
|
+
2. **JavaScript resolution** -- When the ThemeProvider applies the `system` theme, it evaluates
|
|
299
|
+
`window.matchMedia('(prefers-color-scheme: dark)')` and sets `data-theme` to either `dark`
|
|
300
|
+
or `light`. This means the actual CSS properties used are identical to the corresponding
|
|
301
|
+
explicit theme.
|
|
302
|
+
|
|
303
|
+
Note: There is currently no `matchMedia` listener for live OS theme changes. If the user
|
|
304
|
+
switches their OS theme while ClawPort is open, they need to re-select `system` or reload.
|
|
305
|
+
|
|
306
|
+
### Theme-Specific Overrides
|
|
307
|
+
|
|
308
|
+
Some themes have extra CSS rules beyond the custom property definitions:
|
|
309
|
+
|
|
310
|
+
**Glass** -- Applies a radial gradient body background and conditionally shows `.glass-orbs`
|
|
311
|
+
decorative elements:
|
|
312
|
+
|
|
313
|
+
```css
|
|
314
|
+
[data-theme="glass"] body {
|
|
315
|
+
background: radial-gradient(ellipse at 30% 20%, #1a1040 0%, #0d0d18 40%, #050510 100%);
|
|
316
|
+
}
|
|
317
|
+
[data-theme="glass"] .glass-orbs { display: block; }
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
**Color** -- Uses a linear gradient body background and applies gradient borders to React Flow
|
|
321
|
+
nodes:
|
|
322
|
+
|
|
323
|
+
```css
|
|
324
|
+
[data-theme="color"] body {
|
|
325
|
+
background: linear-gradient(135deg, #0a0814 0%, #0f0b20 50%, #0a0814 100%);
|
|
326
|
+
}
|
|
327
|
+
[data-theme="color"] .react-flow__node > div {
|
|
328
|
+
background: linear-gradient(#16112a, #16112a) padding-box,
|
|
329
|
+
linear-gradient(135deg, rgba(139,92,246,0.5), rgba(245,197,24,0.3)) border-box !important;
|
|
330
|
+
border: 1px solid transparent !important;
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Light** -- Overrides `.apple-card` to solid white, adjusts message bubble colors, and
|
|
335
|
+
modifies React Flow edge stroke:
|
|
336
|
+
|
|
337
|
+
```css
|
|
338
|
+
[data-theme="light"] .apple-card {
|
|
339
|
+
background: #ffffff !important;
|
|
340
|
+
border: 1px solid rgba(60,60,67,0.12) !important;
|
|
341
|
+
}
|
|
342
|
+
[data-theme="light"] .msg-user { background: var(--system-blue) !important; color: #ffffff !important; }
|
|
343
|
+
[data-theme="light"] .msg-assistant {
|
|
344
|
+
background: #ffffff !important;
|
|
345
|
+
color: #000000 !important;
|
|
346
|
+
border: 1px solid rgba(60,60,67,0.12) !important;
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### How to Add a New Theme
|
|
351
|
+
|
|
352
|
+
1. **Add the theme ID to `lib/themes.ts`:**
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
export type ThemeId = 'dark' | 'glass' | 'color' | 'light' | 'system' | 'midnight';
|
|
356
|
+
|
|
357
|
+
export const THEMES = [
|
|
358
|
+
// ...existing themes
|
|
359
|
+
{ id: 'midnight', label: 'Midnight', emoji: '\ud83c\udf03' },
|
|
360
|
+
];
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
2. **Add a CSS custom property block in `app/globals.css`:**
|
|
364
|
+
|
|
365
|
+
Add a `[data-theme="midnight"]` rule block that defines **every** token listed above.
|
|
366
|
+
Copy the `dark` theme block as a starting point and adjust values.
|
|
367
|
+
|
|
368
|
+
```css
|
|
369
|
+
[data-theme="midnight"] {
|
|
370
|
+
--bg: #0a0a1a;
|
|
371
|
+
--bg-secondary: rgba(15,15,35,1);
|
|
372
|
+
/* ...all other tokens */
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
3. **Optionally add theme-specific overrides** (body background gradients, component styles)
|
|
377
|
+
as `[data-theme="midnight"] ...` rules at the bottom of `globals.css`.
|
|
378
|
+
|
|
379
|
+
4. The ThemeProvider, onboarding wizard, and settings page will automatically pick up the new
|
|
380
|
+
theme from the `THEMES` array -- no additional wiring needed.
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Settings
|
|
385
|
+
|
|
386
|
+
### ClawPortSettings Interface
|
|
387
|
+
|
|
388
|
+
Defined in `lib/settings.ts`:
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
export interface AgentOverride {
|
|
392
|
+
emoji?: string
|
|
393
|
+
profileImage?: string // base64 data URL
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export interface ClawPortSettings {
|
|
397
|
+
accentColor: string | null
|
|
398
|
+
portalName: string | null
|
|
399
|
+
portalSubtitle: string | null
|
|
400
|
+
portalEmoji: string | null
|
|
401
|
+
portalIcon: string | null // base64 data URL for custom icon image
|
|
402
|
+
iconBgHidden: boolean // hide colored background on sidebar logo
|
|
403
|
+
emojiOnly: boolean // show emoji avatars without colored background
|
|
404
|
+
operatorName: string | null
|
|
405
|
+
agentOverrides: Record<string, AgentOverride>
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
| Field | Type | Default | Description |
|
|
410
|
+
|------------------|-----------------------------------|---------|-------------|
|
|
411
|
+
| `accentColor` | `string \| null` | `null` | Hex color string (e.g., `"#3B82F6"`). When `null`, the theme's built-in `--accent` is used. |
|
|
412
|
+
| `portalName` | `string \| null` | `null` | Custom name displayed in the sidebar header. Falls back to "ClawPort". |
|
|
413
|
+
| `portalSubtitle` | `string \| null` | `null` | Subtitle below the name. Falls back to "Command Centre". |
|
|
414
|
+
| `portalEmoji` | `string \| null` | `null` | Emoji displayed in the sidebar logo. Falls back to the castle emoji. |
|
|
415
|
+
| `portalIcon` | `string \| null` | `null` | Base64 JPEG data URL for a custom sidebar icon image. Overrides the emoji when set. |
|
|
416
|
+
| `iconBgHidden` | `boolean` | `false` | When `true`, removes the colored gradient background behind the sidebar logo emoji. |
|
|
417
|
+
| `emojiOnly` | `boolean` | `false` | When `true`, agent avatars show just the emoji without a colored circle background. |
|
|
418
|
+
| `operatorName` | `string \| null` | `null` | The human operator's name. Displayed in the sidebar and injected into the chat system prompt. |
|
|
419
|
+
| `agentOverrides` | `Record<string, AgentOverride>` | `{}` | Per-agent customizations keyed by agent ID. Each override can set a custom emoji and/or profile image. |
|
|
420
|
+
|
|
421
|
+
### localStorage Persistence
|
|
422
|
+
|
|
423
|
+
Settings are stored under the key `'clawport-settings'` as a JSON string.
|
|
424
|
+
|
|
425
|
+
```
|
|
426
|
+
localStorage key: 'clawport-settings'
|
|
427
|
+
Format: JSON-serialized ClawPortSettings object
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Theme is stored separately under key `'clawport-theme'` (managed by ThemeProvider).
|
|
431
|
+
|
|
432
|
+
Onboarding completion is tracked under key `'clawport-onboarded'` (value `'1'`).
|
|
433
|
+
|
|
434
|
+
**Load behavior** (`loadSettings()`):
|
|
435
|
+
|
|
436
|
+
- Returns `DEFAULTS` on server (SSR guard: `typeof window === 'undefined'`)
|
|
437
|
+
- Returns `DEFAULTS` if the key is missing or JSON parsing fails
|
|
438
|
+
- Validates each field by type during parse -- unknown/malformed fields fall back to their default
|
|
439
|
+
- `agentOverrides` is checked with `typeof parsed.agentOverrides === 'object'`
|
|
440
|
+
|
|
441
|
+
**Save behavior** (`saveSettings()`):
|
|
442
|
+
|
|
443
|
+
- Silently no-ops on server
|
|
444
|
+
- Silently catches `localStorage` write errors (e.g., quota exceeded)
|
|
445
|
+
|
|
446
|
+
### SettingsProvider API
|
|
447
|
+
|
|
448
|
+
The `SettingsProvider` (`app/settings-provider.tsx`) wraps the app and exposes all setting
|
|
449
|
+
mutations via React context. Access it with the `useSettings()` hook.
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
import { useSettings } from '@/app/settings-provider';
|
|
453
|
+
|
|
454
|
+
const {
|
|
455
|
+
settings, // ClawPortSettings (read-only snapshot)
|
|
456
|
+
setAccentColor, // (color: string | null) => void
|
|
457
|
+
setPortalName, // (name: string | null) => void
|
|
458
|
+
setPortalSubtitle, // (subtitle: string | null) => void
|
|
459
|
+
setPortalEmoji, // (emoji: string | null) => void
|
|
460
|
+
setPortalIcon, // (icon: string | null) => void
|
|
461
|
+
setIconBgHidden, // (hidden: boolean) => void
|
|
462
|
+
setEmojiOnly, // (emojiOnly: boolean) => void
|
|
463
|
+
setOperatorName, // (name: string | null) => void
|
|
464
|
+
setAgentOverride, // (agentId: string, override: AgentOverride) => void
|
|
465
|
+
clearAgentOverride, // (agentId: string) => void
|
|
466
|
+
getAgentDisplay, // (agent: Agent) => AgentDisplay
|
|
467
|
+
resetAll, // () => void
|
|
468
|
+
} = useSettings();
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**Setter details:**
|
|
472
|
+
|
|
473
|
+
| Function | Signature | Behavior |
|
|
474
|
+
|-----------------------|--------------------------------------------------|----------|
|
|
475
|
+
| `setAccentColor` | `(color: string \| null) => void` | Sets the accent color. Pass `null` to revert to the theme default. Triggers a `useEffect` that applies `--accent` and `--accent-fill` as inline styles on `<html>`. |
|
|
476
|
+
| `setPortalName` | `(name: string \| null) => void` | Sets sidebar name. Empty string coerced to `null`. |
|
|
477
|
+
| `setPortalSubtitle` | `(subtitle: string \| null) => void` | Sets sidebar subtitle. Empty string coerced to `null`. |
|
|
478
|
+
| `setPortalEmoji` | `(emoji: string \| null) => void` | Sets sidebar logo emoji. Empty string coerced to `null`. |
|
|
479
|
+
| `setPortalIcon` | `(icon: string \| null) => void` | Sets sidebar icon image (base64 data URL). Pass `null` to remove. |
|
|
480
|
+
| `setIconBgHidden` | `(hidden: boolean) => void` | Toggles the colored background behind the sidebar logo emoji. |
|
|
481
|
+
| `setEmojiOnly` | `(emojiOnly: boolean) => void` | Toggles emoji-only avatar mode across the entire app. |
|
|
482
|
+
| `setOperatorName` | `(name: string \| null) => void` | Sets the operator's name. Empty string coerced to `null`. |
|
|
483
|
+
| `setAgentOverride` | `(agentId: string, override: AgentOverride) => void` | Merges an override into the agent's existing overrides. Does not replace -- it shallow-merges. |
|
|
484
|
+
| `clearAgentOverride` | `(agentId: string) => void` | Removes all overrides for a specific agent, reverting to defaults. |
|
|
485
|
+
| `getAgentDisplay` | `(agent: Agent) => AgentDisplay` | Resolves the effective emoji, profile image, and emojiOnly flag for an agent, considering overrides. |
|
|
486
|
+
| `resetAll` | `() => void` | Resets all settings to `DEFAULTS` and persists immediately. |
|
|
487
|
+
|
|
488
|
+
**Hydration strategy:**
|
|
489
|
+
|
|
490
|
+
The provider initializes with `DEFAULTS` (not from `localStorage`) so that server and client
|
|
491
|
+
render the same HTML. A `useEffect` on mount calls `loadSettings()` to hydrate from
|
|
492
|
+
`localStorage`, causing a single re-render after first paint.
|
|
493
|
+
|
|
494
|
+
### Accent Color CSS Variables
|
|
495
|
+
|
|
496
|
+
When the user selects a custom accent color, the SettingsProvider applies it as inline styles
|
|
497
|
+
on `document.documentElement`:
|
|
498
|
+
|
|
499
|
+
```ts
|
|
500
|
+
// app/settings-provider.tsx
|
|
501
|
+
useEffect(() => {
|
|
502
|
+
const el = document.documentElement.style;
|
|
503
|
+
if (settings.accentColor) {
|
|
504
|
+
el.setProperty('--accent', settings.accentColor);
|
|
505
|
+
el.setProperty('--accent-fill', hexToAccentFill(settings.accentColor));
|
|
506
|
+
} else {
|
|
507
|
+
el.removeProperty('--accent');
|
|
508
|
+
el.removeProperty('--accent-fill');
|
|
509
|
+
}
|
|
510
|
+
}, [settings.accentColor]);
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
The `hexToAccentFill` helper converts a hex color to `rgba(r,g,b,0.15)`:
|
|
514
|
+
|
|
515
|
+
```ts
|
|
516
|
+
export function hexToAccentFill(hex: string): string {
|
|
517
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
518
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
519
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
520
|
+
return `rgba(${r},${g},${b},0.15)`;
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
This means:
|
|
525
|
+
- When `accentColor` is `null`, `--accent` and `--accent-fill` come from the active theme's CSS.
|
|
526
|
+
- When `accentColor` is set, inline styles on `<html>` override the theme's values.
|
|
527
|
+
- Every component using `var(--accent)` or `var(--accent-fill)` picks up the change automatically.
|
|
528
|
+
|
|
529
|
+
**Accent color presets** available in both the settings page and onboarding wizard:
|
|
530
|
+
|
|
531
|
+
| Label | Hex |
|
|
532
|
+
|---------|-----------|
|
|
533
|
+
| Gold | `#F5C518` |
|
|
534
|
+
| Blue | `#3B82F6` |
|
|
535
|
+
| Green | `#22C55E` |
|
|
536
|
+
| Red | `#EF4444` |
|
|
537
|
+
| Orange | `#F97316` |
|
|
538
|
+
| Purple | `#A855F7` |
|
|
539
|
+
| Pink | `#EC4899` |
|
|
540
|
+
| Teal | `#14B8A6` |
|
|
541
|
+
| Cyan | `#06B6D4` |
|
|
542
|
+
| Indigo | `#6366F1` |
|
|
543
|
+
| Rose | `#F43F5E` |
|
|
544
|
+
| Lime | `#84CC16` |
|
|
545
|
+
|
|
546
|
+
A native `<input type="color">` picker is also provided for arbitrary colors.
|
|
547
|
+
|
|
548
|
+
### Agent Override System
|
|
549
|
+
|
|
550
|
+
Each agent can have a per-agent emoji and/or profile image override, stored in
|
|
551
|
+
`settings.agentOverrides` keyed by agent ID.
|
|
552
|
+
|
|
553
|
+
```ts
|
|
554
|
+
interface AgentOverride {
|
|
555
|
+
emoji?: string // Custom emoji to replace the agent's default
|
|
556
|
+
profileImage?: string // Base64 data URL (JPEG, max 200px dimension)
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
**How it works:**
|
|
561
|
+
|
|
562
|
+
1. The Settings page fetches all agents from `/api/agents`.
|
|
563
|
+
2. Each agent row is expandable. Inside, the user can:
|
|
564
|
+
- Type a custom emoji
|
|
565
|
+
- Upload a profile image (resized to 200px max via Canvas API, saved as JPEG at 0.85 quality)
|
|
566
|
+
3. Overrides are shallow-merged: setting a new emoji does not clear an existing profile image.
|
|
567
|
+
4. A gold dot indicator appears on agent rows that have active overrides.
|
|
568
|
+
5. `clearAgentOverride(agentId)` removes the entire entry, reverting to the agent's defaults.
|
|
569
|
+
|
|
570
|
+
**Resolution via `getAgentDisplay()`:**
|
|
571
|
+
|
|
572
|
+
```ts
|
|
573
|
+
const getAgentDisplay = (agent: Agent): AgentDisplay => {
|
|
574
|
+
const override = settings.agentOverrides[agent.id];
|
|
575
|
+
return {
|
|
576
|
+
emoji: override?.emoji || agent.emoji, // Fallback to agent default
|
|
577
|
+
profileImage: override?.profileImage, // undefined if no override
|
|
578
|
+
emojiOnly: settings.emojiOnly, // Global setting
|
|
579
|
+
};
|
|
580
|
+
};
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
Components like `AgentAvatar` call `getAgentDisplay()` to resolve the effective visual for
|
|
584
|
+
each agent.
|
|
585
|
+
|
|
586
|
+
### operatorName Flow
|
|
587
|
+
|
|
588
|
+
The operator name flows through the system as follows:
|
|
589
|
+
|
|
590
|
+
1. **Settings** -- User enters their name in the onboarding wizard (step 0) or settings page.
|
|
591
|
+
Stored as `settings.operatorName`.
|
|
592
|
+
|
|
593
|
+
2. **Onboarding wizard** -- Commits the name on wizard step 0 via `setOperatorName()`. The
|
|
594
|
+
wizard shows a live preview with the user's initials rendered as a badge.
|
|
595
|
+
|
|
596
|
+
3. **Sidebar** -- Reads `settings.operatorName` to display the operator's initials in the
|
|
597
|
+
sidebar.
|
|
598
|
+
|
|
599
|
+
4. **Chat POST** -- When a message is sent to `/api/chat/[id]`, the operator name is included
|
|
600
|
+
in the request payload and injected into the system prompt so agents know who they are
|
|
601
|
+
talking to.
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## Customization Guide
|
|
606
|
+
|
|
607
|
+
### Change the Default Accent Color
|
|
608
|
+
|
|
609
|
+
The default accent color is defined per-theme in `app/globals.css`. To change it globally:
|
|
610
|
+
|
|
611
|
+
1. Edit every theme block's `--accent` and `--accent-fill` values:
|
|
612
|
+
|
|
613
|
+
```css
|
|
614
|
+
:root, [data-theme="dark"] {
|
|
615
|
+
--accent: #3B82F6; /* New default: Blue */
|
|
616
|
+
--accent-fill: rgba(59,130,246,0.15); /* Same color at 15% opacity */
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
2. Repeat for `[data-theme="glass"]`, `[data-theme="color"]`, and the `[data-theme="system"]`
|
|
621
|
+
media query block.
|
|
622
|
+
|
|
623
|
+
3. The `light` theme has a different accent (`#B8860B`) for contrast reasons -- update it with
|
|
624
|
+
a value that works on white backgrounds.
|
|
625
|
+
|
|
626
|
+
Note: This only changes the theme-level default. Users who have set a custom accent color in
|
|
627
|
+
settings will not be affected (their inline style override takes precedence).
|
|
628
|
+
|
|
629
|
+
### Add a New Setting Field
|
|
630
|
+
|
|
631
|
+
Follow this sequence to add a new boolean setting called `compactMode`:
|
|
632
|
+
|
|
633
|
+
**Step 1: Types** -- Add the field to `ClawPortSettings` in `lib/settings.ts`:
|
|
634
|
+
|
|
635
|
+
```ts
|
|
636
|
+
export interface ClawPortSettings {
|
|
637
|
+
// ...existing fields
|
|
638
|
+
compactMode: boolean
|
|
639
|
+
}
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
**Step 2: Defaults** -- Add the default value:
|
|
643
|
+
|
|
644
|
+
```ts
|
|
645
|
+
export const DEFAULTS: ClawPortSettings = {
|
|
646
|
+
// ...existing defaults
|
|
647
|
+
compactMode: false,
|
|
648
|
+
}
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
**Step 3: Parser** -- Add type-safe parsing in `loadSettings()`:
|
|
652
|
+
|
|
653
|
+
```ts
|
|
654
|
+
return {
|
|
655
|
+
// ...existing fields
|
|
656
|
+
compactMode: typeof parsed.compactMode === 'boolean' ? parsed.compactMode : false,
|
|
657
|
+
}
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
**Step 4: Provider** -- In `app/settings-provider.tsx`:
|
|
661
|
+
|
|
662
|
+
a. Add to the context interface:
|
|
663
|
+
|
|
664
|
+
```ts
|
|
665
|
+
interface SettingsContextValue {
|
|
666
|
+
// ...existing
|
|
667
|
+
setCompactMode: (compact: boolean) => void
|
|
668
|
+
}
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
b. Add to the context default:
|
|
672
|
+
|
|
673
|
+
```ts
|
|
674
|
+
const SettingsContext = createContext<SettingsContextValue>({
|
|
675
|
+
// ...existing
|
|
676
|
+
compactMode: false, // in the settings object
|
|
677
|
+
setCompactMode: () => {},
|
|
678
|
+
})
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
c. Add the setter callback:
|
|
682
|
+
|
|
683
|
+
```ts
|
|
684
|
+
const setCompactMode = useCallback(
|
|
685
|
+
(compact: boolean) => {
|
|
686
|
+
update({ ...settings, compactMode: compact })
|
|
687
|
+
},
|
|
688
|
+
[settings, update],
|
|
689
|
+
)
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
d. Include in the Provider's `value` prop:
|
|
693
|
+
|
|
694
|
+
```ts
|
|
695
|
+
value={{ ...existing, setCompactMode }}
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
e. Update `resetAll` to include the new field.
|
|
699
|
+
|
|
700
|
+
**Step 5: UI** -- Add a toggle in `app/settings/page.tsx` (follow the pattern of the existing
|
|
701
|
+
`emojiOnly` toggle -- an iOS-style switch button with `role="switch"` and `aria-checked`).
|
|
702
|
+
|
|
703
|
+
### Add a New Theme (Step by Step)
|
|
704
|
+
|
|
705
|
+
1. **Choose an ID** -- Short, lowercase, no spaces. Example: `midnight`.
|
|
706
|
+
|
|
707
|
+
2. **Update the type union** in `lib/themes.ts`:
|
|
708
|
+
|
|
709
|
+
```ts
|
|
710
|
+
export type ThemeId = 'dark' | 'glass' | 'color' | 'light' | 'system' | 'midnight';
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
3. **Add to the THEMES array** in `lib/themes.ts`:
|
|
714
|
+
|
|
715
|
+
```ts
|
|
716
|
+
{ id: 'midnight', label: 'Midnight', emoji: '\ud83c\udf03' },
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
4. **Define all CSS custom properties** in `app/globals.css`. Copy the `[data-theme="dark"]`
|
|
720
|
+
block as a template. You must define every token listed in
|
|
721
|
+
[CSS Custom Property Tokens](#css-custom-property-tokens). Missing tokens will cause
|
|
722
|
+
components to render with broken styles.
|
|
723
|
+
|
|
724
|
+
```css
|
|
725
|
+
[data-theme="midnight"] {
|
|
726
|
+
--bg: #0a0a1a;
|
|
727
|
+
--bg-secondary: ...;
|
|
728
|
+
/* Every single token from the list above */
|
|
729
|
+
}
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
5. **Optionally add body background** and component-level overrides:
|
|
733
|
+
|
|
734
|
+
```css
|
|
735
|
+
[data-theme="midnight"] body {
|
|
736
|
+
background: linear-gradient(...);
|
|
737
|
+
}
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
6. **Test** -- The theme will automatically appear in:
|
|
741
|
+
- The onboarding wizard (step 1: "Choose a Theme")
|
|
742
|
+
- The theme selector (wherever themes are listed from the `THEMES` array)
|
|
743
|
+
|
|
744
|
+
No changes to `ThemeProvider`, settings page, or onboarding wizard code are needed.
|
|
745
|
+
|
|
746
|
+
### CSS Custom Property Naming Conventions
|
|
747
|
+
|
|
748
|
+
ClawPort follows a consistent naming pattern for all CSS variables:
|
|
749
|
+
|
|
750
|
+
| Prefix | Category | Examples |
|
|
751
|
+
|---------------|--------------------------------|------------------------------------|
|
|
752
|
+
| `--bg-*` | Background colors | `--bg`, `--bg-secondary`, `--bg-tertiary` |
|
|
753
|
+
| `--material-*`| Apple translucent surfaces | `--material-regular`, `--material-thick` |
|
|
754
|
+
| `--fill-*` | Interactive fill states | `--fill-primary` through `--fill-quaternary` |
|
|
755
|
+
| `--separator*`| Dividers and borders | `--separator`, `--separator-opaque` |
|
|
756
|
+
| `--text-*` | Text colors (as theme tokens) | `--text-primary` through `--text-quaternary` |
|
|
757
|
+
| `--text-*` | Font sizes (as Tailwind theme) | `--text-caption2` through `--text-large-title` |
|
|
758
|
+
| `--accent*` | Brand accent | `--accent`, `--accent-fill` |
|
|
759
|
+
| `--system-*` | Semantic system colors | `--system-blue`, `--system-green`, etc. |
|
|
760
|
+
| `--shadow-*` | Box shadows | `--shadow-subtle`, `--shadow-card`, etc. |
|
|
761
|
+
| `--code-*` | Code block styling | `--code-bg`, `--code-border`, `--code-text` |
|
|
762
|
+
| `--sidebar-*` | Sidebar-specific | `--sidebar-bg`, `--sidebar-backdrop` |
|
|
763
|
+
| `--radius-*` | Border radii | `--radius-sm` through `--radius-2xl` |
|
|
764
|
+
| `--ease-*` | Easing curves | `--ease-spring`, `--ease-smooth`, `--ease-snappy` |
|
|
765
|
+
| `--space-*` | Spacing scale (4px grid) | `--space-1` through `--space-16` |
|
|
766
|
+
| `--weight-*` | Font weights | `--weight-regular` through `--weight-bold` |
|
|
767
|
+
| `--leading-*` | Line heights | `--leading-tight` through `--leading-relaxed` |
|
|
768
|
+
| `--tracking-*`| Letter spacing | `--tracking-tight`, `--tracking-normal`, `--tracking-wide` |
|
|
769
|
+
| `--font-*` | Font families | `--font-sans`, `--font-mono` |
|
|
770
|
+
| `--animate-*` | Tailwind animation tokens | `--animate-fade-in`, `--animate-slide-in`, etc. |
|
|
771
|
+
| `--inset-*` | Inner highlights | `--inset-shine` |
|
|
772
|
+
|
|
773
|
+
**Rules:**
|
|
774
|
+
- Theme-varying tokens (colors, shadows, materials) are defined per `[data-theme]` block.
|
|
775
|
+
- Static tokens (spacing, typography, radii, easing) are defined once in the `@theme` block
|
|
776
|
+
or the `:root` rule and shared across all themes.
|
|
777
|
+
- Components reference tokens via `var(--token-name)` in inline styles, not via Tailwind color
|
|
778
|
+
utilities.
|
|
779
|
+
- The `--text-*` namespace is shared between font-size tokens (in `@theme`) and text-color
|
|
780
|
+
tokens (in theme blocks). Context makes it unambiguous: `font-size: var(--text-body)` vs
|
|
781
|
+
`color: var(--text-primary)`.
|
|
782
|
+
|
|
783
|
+
---
|
|
784
|
+
|
|
785
|
+
## Key Files Reference
|
|
786
|
+
|
|
787
|
+
| File | Purpose |
|
|
788
|
+
|--------------------------------|----------------------------------------------|
|
|
789
|
+
| `app/globals.css` | All CSS custom properties, theme definitions, keyframes, utility classes |
|
|
790
|
+
| `lib/themes.ts` | Theme IDs, labels, emojis (`THEMES` array and `ThemeId` type) |
|
|
791
|
+
| `app/providers.tsx` | `ThemeProvider` -- manages `data-theme` attribute and localStorage |
|
|
792
|
+
| `lib/settings.ts` | `ClawPortSettings` interface, `DEFAULTS`, `loadSettings()`, `saveSettings()`, `hexToAccentFill()` |
|
|
793
|
+
| `app/settings-provider.tsx` | `SettingsProvider` -- all setter callbacks, accent color CSS variable application |
|
|
794
|
+
| `app/settings/page.tsx` | Settings UI -- accent color, branding, agent customization, reset |
|
|
795
|
+
| `components/OnboardingWizard.tsx` | First-run wizard -- applies theme, accent color, and branding settings |
|