create-theokit 1.0.4 → 1.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-theokit",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
5
  "description": "Scaffold a new TheoKit project",
6
6
  "license": "Apache-2.0",
@@ -1,10 +1,11 @@
1
- /* TheoKit — global design tokens + reset */
1
+ /* TheoKit — design system */
2
2
 
3
3
  :root {
4
4
  --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
5
5
  --font-mono: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', Menlo, Consolas, monospace;
6
- --bg: #ffffff;
7
- --bg-secondary: #fafafa;
6
+
7
+ --bg: #fafafa;
8
+ --bg-content: #ffffff;
8
9
  --card: #ffffff;
9
10
  --border: #e5e5e5;
10
11
  --text: #171717;
@@ -12,6 +13,10 @@
12
13
  --text-muted: #999999;
13
14
  --accent: #6366f1;
14
15
  --accent-hover: #4f46e5;
16
+ --btn-primary-bg: #171717;
17
+ --btn-primary-hover: #383838;
18
+ --btn-secondary-hover: #f2f2f2;
19
+ --btn-secondary-border: #ebebeb;
15
20
  --green: #22c55e;
16
21
  --red: #ef4444;
17
22
  --yellow: #eab308;
@@ -20,7 +25,7 @@
20
25
  @media (prefers-color-scheme: dark) {
21
26
  :root {
22
27
  --bg: #0a0a0a;
23
- --bg-secondary: #111111;
28
+ --bg-content: #0a0a0a;
24
29
  --card: #141414;
25
30
  --border: #2a2a2a;
26
31
  --text: #ededed;
@@ -28,11 +33,14 @@
28
33
  --text-muted: #666666;
29
34
  --accent: #818cf8;
30
35
  --accent-hover: #6366f1;
36
+ --btn-primary-bg: #ededed;
37
+ --btn-primary-hover: #cccccc;
38
+ --btn-secondary-hover: #1a1a1a;
39
+ --btn-secondary-border: #2a2a2a;
31
40
  --green: #22c55e;
32
41
  --red: #ef4444;
33
42
  --yellow: #eab308;
34
43
  }
35
-
36
44
  html { color-scheme: dark; }
37
45
  }
38
46
 
@@ -50,31 +58,146 @@ body {
50
58
  min-height: 100vh;
51
59
  display: flex;
52
60
  flex-direction: column;
61
+ align-items: center;
53
62
  -webkit-font-smoothing: antialiased;
54
63
  -moz-osx-font-smoothing: grayscale;
55
64
  }
56
65
 
66
+ a { color: inherit; text-decoration: none; }
57
67
  code, pre { font-family: var(--font-mono); }
58
68
 
59
- a { color: inherit; text-decoration: none; }
69
+ /* ─── Page container ────────────────────────────────── */
60
70
 
61
- /* ─── Layout ────────────────────────────────────────── */
71
+ .page {
72
+ display: flex;
73
+ flex: 1;
74
+ flex-direction: column;
75
+ align-items: center;
76
+ width: 100%;
77
+ }
62
78
 
63
- .container {
64
- max-width: 1200px;
65
- margin: 0 auto;
66
- padding: 24px;
79
+ .main {
80
+ display: flex;
67
81
  flex: 1;
82
+ flex-direction: column;
68
83
  width: 100%;
84
+ max-width: 900px;
85
+ background: var(--bg-content);
86
+ padding: 80px 48px 48px;
69
87
  }
70
88
 
71
- /* ─── Cards ─────────────────────────────────────────── */
89
+ @media (max-width: 768px) {
90
+ .main { padding: 48px 20px 32px; }
91
+ }
72
92
 
73
- .card {
93
+ /* ─── Hero ──────────────────────────────────────────── */
94
+
95
+ .hero {
96
+ display: flex;
97
+ flex-direction: column;
98
+ align-items: center;
99
+ text-align: center;
100
+ gap: 16px;
101
+ margin-bottom: 48px;
102
+ }
103
+
104
+ .hero-logo {
105
+ border-radius: 16px;
106
+ margin-bottom: 8px;
107
+ }
108
+
109
+ .hero h1 {
110
+ font-size: 40px;
111
+ font-weight: 700;
112
+ letter-spacing: -2.4px;
113
+ line-height: 48px;
114
+ text-wrap: balance;
115
+ }
116
+
117
+ .hero .tagline {
118
+ font-size: 18px;
119
+ line-height: 28px;
120
+ color: var(--text-secondary);
121
+ max-width: 440px;
122
+ text-wrap: balance;
123
+ }
124
+
125
+ .hero .hint {
126
+ font-size: 14px;
127
+ color: var(--text-muted);
128
+ margin-top: 8px;
129
+ }
130
+
131
+ .hero .hint code {
74
132
  background: var(--card);
75
133
  border: 1px solid var(--border);
76
- border-radius: 12px;
77
- padding: 24px;
134
+ padding: 2px 8px;
135
+ border-radius: 6px;
136
+ font-size: 13px;
137
+ }
138
+
139
+ @media (max-width: 768px) {
140
+ .hero h1 { font-size: 32px; line-height: 40px; letter-spacing: -1.92px; }
141
+ .hero .tagline { font-size: 16px; }
142
+ }
143
+
144
+ /* ─── CTA Buttons ───────────────────────────────────── */
145
+
146
+ .ctas {
147
+ display: flex;
148
+ gap: 12px;
149
+ margin-top: 8px;
150
+ }
151
+
152
+ .btn {
153
+ display: inline-flex;
154
+ justify-content: center;
155
+ align-items: center;
156
+ height: 40px;
157
+ padding: 0 20px;
158
+ border-radius: 128px;
159
+ border: 1px solid transparent;
160
+ font-size: 14px;
161
+ font-weight: 500;
162
+ cursor: pointer;
163
+ transition: background 0.2s, border-color 0.2s;
164
+ text-decoration: none;
165
+ }
166
+
167
+ .btn.primary {
168
+ background: var(--btn-primary-bg);
169
+ color: var(--bg);
170
+ }
171
+
172
+ .btn.secondary {
173
+ border-color: var(--btn-secondary-border);
174
+ color: var(--text);
175
+ }
176
+
177
+ @media (hover: hover) and (pointer: fine) {
178
+ .btn.primary:hover { background: var(--btn-primary-hover); }
179
+ .btn.secondary:hover { background: var(--btn-secondary-hover); border-color: transparent; }
180
+ }
181
+
182
+ /* ─── Role selector ─────────────────────────────────── */
183
+
184
+ .role-bar {
185
+ display: flex;
186
+ justify-content: center;
187
+ align-items: center;
188
+ gap: 8px;
189
+ margin-bottom: 32px;
190
+ font-size: 14px;
191
+ color: var(--text-muted);
192
+ }
193
+
194
+ .role-bar select {
195
+ padding: 6px 12px;
196
+ background: var(--card);
197
+ border: 1px solid var(--border);
198
+ border-radius: 6px;
199
+ color: var(--text);
200
+ font-size: 13px;
78
201
  }
79
202
 
80
203
  /* ─── Grid ──────────────────────────────────────────── */
@@ -83,24 +206,35 @@ a { color: inherit; text-decoration: none; }
83
206
  display: grid;
84
207
  grid-template-columns: 1fr 1fr;
85
208
  gap: 24px;
209
+ width: 100%;
86
210
  }
87
211
 
88
212
  @media (max-width: 768px) {
89
213
  .grid { grid-template-columns: 1fr; }
90
214
  }
91
215
 
92
- /* ─── Typography ────────────────────────────────────── */
216
+ /* ─── Cards ─────────────────────────────────────────── */
93
217
 
94
- h1 { font-size: 1.75rem; font-weight: 700; letter-spacing: -0.02em; line-height: 1.2; }
95
- h2 { font-size: 1rem; font-weight: 600; margin-bottom: 16px; display: flex; align-items: center; gap: 8px; }
218
+ .card {
219
+ background: var(--card);
220
+ border: 1px solid var(--border);
221
+ border-radius: 12px;
222
+ padding: 24px;
223
+ }
96
224
 
97
- .accent { color: var(--accent); }
98
- .subtitle { color: var(--text-secondary); font-size: 0.9rem; margin-top: 4px; line-height: 1.5; }
225
+ .card h2 {
226
+ font-size: 15px;
227
+ font-weight: 600;
228
+ margin-bottom: 16px;
229
+ display: flex;
230
+ align-items: center;
231
+ gap: 8px;
232
+ }
99
233
 
100
234
  /* ─── Badges ────────────────────────────────────────── */
101
235
 
102
236
  .badge {
103
- font-size: 0.65rem;
237
+ font-size: 11px;
104
238
  padding: 2px 10px;
105
239
  border-radius: 99px;
106
240
  font-weight: 500;
@@ -115,87 +249,88 @@ h2 { font-size: 1rem; font-weight: 600; margin-bottom: 16px; display: flex; alig
115
249
 
116
250
  /* ─── Table ─────────────────────────────────────────── */
117
251
 
118
- table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
119
- th { text-align: left; padding: 10px 8px; color: var(--text-muted); border-bottom: 1px solid var(--border); font-weight: 500; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; }
120
- td { padding: 10px 8px; border-bottom: 1px solid var(--border); }
121
- tr.done td { opacity: 0.45; text-decoration: line-through; }
252
+ table { width: 100%; border-collapse: collapse; font-size: 13px; }
253
+ th {
254
+ text-align: left; padding: 8px 6px;
255
+ color: var(--text-muted); border-bottom: 1px solid var(--border);
256
+ font-weight: 500; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em;
257
+ }
258
+ td { padding: 8px 6px; border-bottom: 1px solid var(--border); }
259
+ tr.done td { opacity: 0.4; text-decoration: line-through; }
122
260
 
123
- .prio { font-size: 0.7rem; padding: 2px 10px; border-radius: 99px; font-weight: 500; }
261
+ .prio { font-size: 11px; padding: 2px 8px; border-radius: 99px; font-weight: 500; }
124
262
  .prio-high { background: color-mix(in srgb, var(--red) 12%, transparent); color: var(--red); }
125
- .prio-med { background: color-mix(in srgb, var(--yellow) 12%, transparent); color: var(--yellow); }
263
+ .prio-medium { background: color-mix(in srgb, var(--yellow) 12%, transparent); color: var(--yellow); }
126
264
  .prio-low { background: color-mix(in srgb, var(--green) 12%, transparent); color: var(--green); }
127
265
 
128
266
  /* ─── Forms ─────────────────────────────────────────── */
129
267
 
130
268
  .create-bar { display: flex; gap: 8px; margin-top: 16px; }
131
269
  .create-bar input {
132
- flex: 1; padding: 10px 14px;
133
- background: var(--bg-secondary); border: 1px solid var(--border);
134
- border-radius: 8px; color: var(--text); font-size: 0.85rem;
270
+ flex: 1; padding: 8px 12px;
271
+ background: var(--bg); border: 1px solid var(--border);
272
+ border-radius: 8px; color: var(--text); font-size: 13px;
135
273
  outline: none; transition: border-color 0.2s;
136
274
  }
137
275
  .create-bar input:focus { border-color: var(--accent); }
138
276
  .create-bar select {
139
- padding: 10px 12px; background: var(--bg-secondary);
277
+ padding: 8px 10px; background: var(--bg);
140
278
  border: 1px solid var(--border); border-radius: 8px;
141
- color: var(--text); font-size: 0.8rem;
279
+ color: var(--text); font-size: 13px;
142
280
  }
143
281
  .create-bar button, .chat-bar button {
144
- padding: 10px 20px; background: var(--accent); color: white;
282
+ padding: 8px 16px; background: var(--accent); color: white;
145
283
  border: none; border-radius: 8px; cursor: pointer;
146
- font-weight: 600; font-size: 0.85rem; transition: background 0.2s;
284
+ font-weight: 600; font-size: 13px; transition: background 0.2s;
147
285
  }
148
-
149
286
  @media (hover: hover) and (pointer: fine) {
150
287
  .create-bar button:hover, .chat-bar button:hover { background: var(--accent-hover); }
151
288
  }
152
-
153
289
  .create-bar button:disabled, .chat-bar button:disabled { opacity: 0.4; cursor: not-allowed; }
154
- .error { color: var(--red); font-size: 0.8rem; margin-top: 4px; }
155
-
156
- /* ─── Role selector ─────────────────────────────────── */
157
-
158
- .role-bar { margin-top: 14px; display: flex; align-items: center; gap: 8px; }
159
- .role-bar label { font-size: 0.8rem; color: var(--text-muted); }
160
- .role-bar select {
161
- padding: 6px 12px; background: var(--bg-secondary);
162
- border: 1px solid var(--border); border-radius: 6px;
163
- color: var(--text); font-size: 0.8rem;
164
- }
290
+ .error { color: var(--red); font-size: 13px; margin-top: 6px; }
165
291
 
166
292
  /* ─── Chat ──────────────────────────────────────────── */
167
293
 
168
294
  .chat-box {
169
- height: 420px; overflow-y: auto; padding: 16px;
170
- background: var(--bg-secondary); border: 1px solid var(--border);
295
+ height: 320px; overflow-y: auto; padding: 14px;
296
+ background: var(--bg); border: 1px solid var(--border);
171
297
  border-radius: 10px; margin-bottom: 12px;
172
- font-size: 0.85rem; line-height: 1.7;
298
+ font-size: 13px; line-height: 1.6;
173
299
  }
174
300
 
175
- .msg { margin-bottom: 10px; padding: 10px 14px; border-radius: 10px; }
301
+ .msg { margin-bottom: 8px; padding: 8px 12px; border-radius: 8px; }
176
302
  .msg.user { background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--accent); }
177
303
  .msg.agent { background: var(--card); border: 1px solid var(--border); }
178
- .msg.tool { background: color-mix(in srgb, var(--yellow) 8%, transparent); color: var(--yellow); font-size: 0.78rem; font-family: var(--font-mono); }
179
- .msg.system { color: var(--text-muted); font-size: 0.78rem; font-style: italic; }
180
- .msg.error { color: var(--red); font-size: 0.8rem; }
304
+ .msg.tool { background: color-mix(in srgb, var(--yellow) 8%, transparent); color: var(--yellow); font-size: 12px; font-family: var(--font-mono); }
305
+ .msg.system { color: var(--text-muted); font-size: 12px; font-style: italic; }
306
+ .msg.error { color: var(--red); font-size: 13px; }
181
307
 
182
308
  .chat-bar { display: flex; gap: 8px; }
183
309
  .chat-bar input {
184
- flex: 1; padding: 12px 16px;
185
- background: var(--bg-secondary); border: 1px solid var(--border);
186
- border-radius: 10px; color: var(--text); font-size: 0.9rem;
310
+ flex: 1; padding: 10px 14px;
311
+ background: var(--bg); border: 1px solid var(--border);
312
+ border-radius: 10px; color: var(--text); font-size: 14px;
187
313
  outline: none; transition: border-color 0.2s;
188
314
  }
189
315
  .chat-bar input:focus { border-color: var(--accent); }
190
316
 
191
- .cost { color: var(--text-muted); font-size: 0.75rem; margin-top: 8px; text-align: right; }
317
+ /* ─── Footer ────────────────────────────────────────── */
318
+
319
+ .footer {
320
+ text-align: center;
321
+ padding: 40px 24px 32px;
322
+ font-size: 13px;
323
+ color: var(--text-muted);
324
+ }
325
+
326
+ .footer a { color: var(--text-secondary); transition: color 0.2s; }
327
+ .footer a:hover { color: var(--accent); }
192
328
 
193
329
  /* ─── Loading ───────────────────────────────────────── */
194
330
 
195
331
  .loading-spinner {
196
- width: 40px;
197
- height: 40px;
198
- border: 3px solid var(--border);
332
+ width: 24px; height: 24px;
333
+ border: 2px solid var(--border);
199
334
  border-top-color: var(--accent);
200
335
  border-radius: 50%;
201
336
  animation: spin 0.8s linear infinite;
@@ -204,93 +339,3 @@ tr.done td { opacity: 0.45; text-decoration: line-through; }
204
339
  @keyframes spin {
205
340
  to { transform: rotate(360deg); }
206
341
  }
207
-
208
- /* ─── Hero ──────────────────────────────────────────── */
209
-
210
- .hero {
211
- text-align: center;
212
- padding: 48px 24px 32px;
213
- }
214
-
215
- .hero img {
216
- margin-bottom: 16px;
217
- border-radius: 16px;
218
- }
219
-
220
- .hero h1 {
221
- font-size: 2.5rem;
222
- font-weight: 800;
223
- letter-spacing: -0.03em;
224
- margin-bottom: 4px;
225
- }
226
-
227
- .hero .tagline {
228
- color: var(--text-secondary);
229
- font-size: 1.1rem;
230
- margin-bottom: 24px;
231
- }
232
-
233
- .hero-links {
234
- display: flex;
235
- justify-content: center;
236
- gap: 12px;
237
- margin-bottom: 24px;
238
- }
239
-
240
- .btn {
241
- display: inline-flex;
242
- align-items: center;
243
- padding: 10px 24px;
244
- border-radius: 99px;
245
- font-weight: 600;
246
- font-size: 0.9rem;
247
- text-decoration: none;
248
- transition: background 0.2s, border-color 0.2s;
249
- }
250
-
251
- .btn.primary {
252
- background: var(--accent);
253
- color: white;
254
- }
255
-
256
- .btn.secondary {
257
- background: transparent;
258
- border: 1px solid var(--border);
259
- color: var(--text);
260
- }
261
-
262
- @media (hover: hover) and (pointer: fine) {
263
- .btn.primary:hover { background: var(--accent-hover); }
264
- .btn.secondary:hover { background: var(--bg-secondary); }
265
- }
266
-
267
- .hero .hint {
268
- color: var(--text-muted);
269
- font-size: 0.8rem;
270
- }
271
-
272
- .hero .hint code {
273
- font-family: var(--font-mono);
274
- background: var(--bg-secondary);
275
- padding: 2px 6px;
276
- border-radius: 4px;
277
- font-size: 0.75rem;
278
- }
279
-
280
- /* ─── Footer ────────────────────────────────────────── */
281
-
282
- .footer {
283
- text-align: center;
284
- padding: 32px 24px;
285
- color: var(--text-muted);
286
- font-size: 0.8rem;
287
- }
288
-
289
- .footer a {
290
- color: var(--accent);
291
- text-decoration: none;
292
- }
293
-
294
- .footer a:hover {
295
- text-decoration: underline;
296
- }
@@ -2,199 +2,260 @@
2
2
 
3
3
  import { useState, useEffect, useCallback, useRef, type FormEvent } from 'react'
4
4
 
5
- // ── Types ──
6
-
7
5
  interface Task {
8
6
  id: number
9
7
  title: string
10
8
  priority: 'high' | 'medium' | 'low'
11
9
  done: boolean
12
10
  }
13
-
14
11
  type Role = '' | 'user' | 'admin'
15
-
16
12
  interface ChatMsg {
17
13
  role: 'user' | 'agent' | 'tool' | 'system' | 'error'
18
14
  text: string
19
15
  }
20
16
 
21
- // ── Page ──
22
-
23
17
  export default function Page() {
24
18
  const [tasks, setTasks] = useState<Task[]>([])
25
19
  const [role, setRole] = useState<Role>('user')
26
20
  const [title, setTitle] = useState('')
27
21
  const [priority, setPriority] = useState<Task['priority']>('medium')
28
22
  const [formError, setFormError] = useState('')
29
- const [chat, setChat] = useState<ChatMsg[]>([{ role: 'system', text: 'Ask me to list, create, or complete tasks...' }])
23
+ const [chat, setChat] = useState<ChatMsg[]>([
24
+ { role: 'system', text: 'Ask me to list, create, or complete tasks...' },
25
+ ])
30
26
  const [chatInput, setChatInput] = useState('')
31
27
  const [chatBusy, setChatBusy] = useState(false)
32
28
  const chatRef = useRef<HTMLDivElement>(null)
33
29
 
34
- const headers = useCallback((): Record<string, string> => {
30
+ const hdrs = useCallback((): Record<string, string> => {
35
31
  const h: Record<string, string> = { 'Content-Type': 'application/json' }
36
32
  if (role) h['x-role'] = role
37
33
  return h
38
34
  }, [role])
39
35
 
40
- // Fetch tasks on mount + when role changes
41
36
  const loadTasks = useCallback(async () => {
42
37
  const res = await fetch('/api/tasks')
43
38
  if (res.ok) setTasks(await res.json())
44
39
  }, [])
45
40
 
46
- useEffect(() => { loadTasks() }, [loadTasks])
41
+ useEffect(() => {
42
+ loadTasks()
43
+ }, [loadTasks])
47
44
 
48
- // Create task
49
45
  const createTask = async (e: FormEvent) => {
50
46
  e.preventDefault()
51
47
  setFormError('')
52
48
  if (!title.trim()) return
53
- const res = await fetch('/api/tasks', { method: 'POST', headers: headers(), body: JSON.stringify({ title, priority }) })
54
- if (res.status === 403) { setFormError('403 — Need User role'); return }
55
- if (!res.ok) { const b = await res.json(); setFormError(b.error?.issues?.[0]?.message ?? `Error ${res.status}`); return }
49
+ const res = await fetch('/api/tasks', {
50
+ method: 'POST',
51
+ headers: hdrs(),
52
+ body: JSON.stringify({ title, priority }),
53
+ })
54
+ if (res.status === 403) {
55
+ setFormError('403 — Need User role')
56
+ return
57
+ }
58
+ if (!res.ok) {
59
+ const b = await res.json()
60
+ setFormError(b.error?.issues?.[0]?.message ?? `Error ${res.status}`)
61
+ return
62
+ }
56
63
  setTitle('')
57
64
  loadTasks()
58
65
  }
59
66
 
60
- // AI Chat
61
67
  const sendChat = async () => {
62
68
  const msg = chatInput.trim()
63
69
  if (!msg || chatBusy) return
64
70
  setChatInput('')
65
- setChat(c => [...c, { role: 'user', text: msg }])
71
+ setChat((c) => [...c, { role: 'user', text: msg }])
66
72
  setChatBusy(true)
67
-
68
73
  try {
69
74
  const res = await fetch('/api/agents/assistant/chat', {
70
- method: 'POST', headers: headers(),
75
+ method: 'POST',
76
+ headers: hdrs(),
71
77
  body: JSON.stringify({ message: msg, sessionId: 'session-' + Date.now() }),
72
78
  })
73
- if (res.status === 403) { setChat(c => [...c, { role: 'error', text: '403 — Need User role' }]); return }
74
-
79
+ if (res.status === 403) {
80
+ setChat((c) => [...c, { role: 'error', text: '403 — Need User role' }])
81
+ return
82
+ }
75
83
  const reader = res.body?.getReader()
76
84
  if (!reader) return
77
85
  const decoder = new TextDecoder()
78
- let buf = '', agentText = ''
79
-
86
+ let buf = '',
87
+ agentText = ''
80
88
  while (true) {
81
89
  const { done, value } = await reader.read()
82
90
  if (done) break
83
91
  buf += decoder.decode(value, { stream: true })
84
- const lines = buf.split('\n'); buf = lines.pop() ?? ''
92
+ const lines = buf.split('\n')
93
+ buf = lines.pop() ?? ''
85
94
  for (const line of lines) {
86
95
  if (!line.startsWith('data: ')) continue
87
96
  try {
88
97
  const ev = JSON.parse(line.slice(6))
89
98
  if (ev.type === 'text_delta') agentText += ev.content
90
- else if (ev.type === 'tool_call') setChat(c => [...c, { role: 'tool', text: `🔧 ${ev.toolName}` }])
91
- else if (ev.type === 'tool_result') setChat(c => [...c, { role: 'tool', text: `✅ ${(ev.output ?? '').slice(0, 80)}` }])
92
- else if (ev.type === 'error') setChat(c => [...c, { role: 'error', text: ev.message }])
93
- } catch { /* partial JSON */ }
99
+ else if (ev.type === 'tool_call')
100
+ setChat((c) => [...c, { role: 'tool', text: `🔧 ${ev.toolName}` }])
101
+ else if (ev.type === 'tool_result')
102
+ setChat((c) => [...c, { role: 'tool', text: `✅ ${(ev.output ?? '').slice(0, 80)}` }])
103
+ else if (ev.type === 'error')
104
+ setChat((c) => [...c, { role: 'error', text: ev.message }])
105
+ } catch {
106
+ /* partial */
107
+ }
94
108
  }
95
109
  }
96
- if (agentText) setChat(c => [...c, { role: 'agent', text: agentText }])
110
+ if (agentText) setChat((c) => [...c, { role: 'agent', text: agentText }])
97
111
  loadTasks()
98
112
  } catch (err) {
99
- setChat(c => [...c, { role: 'error', text: `Error: ${err instanceof Error ? err.message : String(err)}` }])
113
+ setChat((c) => [
114
+ ...c,
115
+ { role: 'error', text: `Error: ${err instanceof Error ? err.message : String(err)}` },
116
+ ])
100
117
  } finally {
101
118
  setChatBusy(false)
102
119
  }
103
120
  }
104
121
 
105
- useEffect(() => { chatRef.current?.scrollTo(0, chatRef.current.scrollHeight) }, [chat])
122
+ useEffect(() => {
123
+ chatRef.current?.scrollTo(0, chatRef.current.scrollHeight)
124
+ }, [chat])
106
125
 
107
126
  return (
108
- <>
109
- {/* Hero */}
110
- <header className="hero">
111
- <img src="/logo.png" alt="TheoKit" width={80} height={80} />
112
- <h1>TheoKit</h1>
113
- <p className="tagline">Build the app your agent lives in.</p>
114
- <nav className="hero-links">
115
- <a href="https://usetheo.dev" target="_blank" rel="noopener noreferrer" className="btn primary">
116
- usetheo.dev
117
- </a>
118
- <a href="https://github.com/usetheodev/theokit" target="_blank" rel="noopener noreferrer" className="btn secondary">
119
- Documentation
120
- </a>
121
- </nav>
122
- <p className="hint">
123
- Edit <code>app/page.tsx</code> to get started. Changes hot-reload instantly.
124
- </p>
125
- </header>
127
+ <div className="page">
128
+ <div className="main">
129
+ {/* Hero */}
130
+ <header className="hero">
131
+ <img src="/logo.png" alt="TheoKit" width={72} height={72} className="hero-logo" />
132
+ <h1>TheoKit</h1>
133
+ <p className="tagline">Build the app your agent lives in.</p>
134
+ <nav className="ctas">
135
+ <a
136
+ href="https://usetheo.dev"
137
+ target="_blank"
138
+ rel="noopener noreferrer"
139
+ className="btn primary"
140
+ >
141
+ Get Started
142
+ </a>
143
+ <a
144
+ href="https://github.com/usetheodev/theokit"
145
+ target="_blank"
146
+ rel="noopener noreferrer"
147
+ className="btn secondary"
148
+ >
149
+ Documentation
150
+ </a>
151
+ </nav>
152
+ <p className="hint">
153
+ Edit <code>app/page.tsx</code> to get started.
154
+ </p>
155
+ </header>
126
156
 
127
- {/* Role selector */}
128
- <div className="role-bar">
129
- <label htmlFor="role">Role:</label>
130
- <select id="role" value={role} onChange={e => setRole(e.target.value as Role)}>
131
- <option value="">None (public only)</option>
132
- <option value="user">User</option>
133
- <option value="admin">Admin</option>
134
- </select>
135
- </div>
157
+ {/* Role */}
158
+ <div className="role-bar">
159
+ <label htmlFor="role">Role:</label>
160
+ <select id="role" value={role} onChange={(e) => setRole(e.target.value as Role)}>
161
+ <option value="">None (public)</option>
162
+ <option value="user">User</option>
163
+ <option value="admin">Admin</option>
164
+ </select>
165
+ </div>
136
166
 
137
- {/* Main grid */}
138
- <main className="grid">
139
- {/* Tasks CRUD */}
140
- <section className="card">
141
- <h2>Tasks <span className="badge">@Controller</span></h2>
142
- <table>
143
- <thead><tr><th>Task</th><th>Priority</th><th>Status</th></tr></thead>
144
- <tbody>
145
- {tasks.map(t => (
146
- <tr key={t.id} className={t.done ? 'done' : ''}>
147
- <td>{t.done ? '✅ ' : '○ '}{t.title}</td>
148
- <td><span className={`prio prio-${t.priority}`}>{t.priority}</span></td>
149
- <td>{t.done ? 'Done' : 'To do'}</td>
167
+ {/* Content */}
168
+ <div className="grid">
169
+ <section className="card">
170
+ <h2>
171
+ Tasks <span className="badge">@Controller</span>
172
+ </h2>
173
+ <table>
174
+ <thead>
175
+ <tr>
176
+ <th>Task</th>
177
+ <th>Priority</th>
178
+ <th>Status</th>
150
179
  </tr>
151
- ))}
152
- </tbody>
153
- </table>
154
- <form onSubmit={createTask} className="create-bar">
155
- <input value={title} onChange={e => setTitle(e.target.value)} placeholder="New task..." required minLength={3} />
156
- <select value={priority} onChange={e => setPriority(e.target.value as Task['priority'])}>
157
- <option value="medium">Medium</option>
158
- <option value="high">High</option>
159
- <option value="low">Low</option>
160
- </select>
161
- <button type="submit">Add</button>
162
- </form>
163
- {formError && <p className="error">{formError}</p>}
164
- </section>
180
+ </thead>
181
+ <tbody>
182
+ {tasks.map((t) => (
183
+ <tr key={t.id} className={t.done ? 'done' : ''}>
184
+ <td>
185
+ {t.done ? '✅ ' : ''}
186
+ {t.title}
187
+ </td>
188
+ <td>
189
+ <span className={`prio prio-${t.priority}`}>{t.priority}</span>
190
+ </td>
191
+ <td>{t.done ? 'Done' : 'To do'}</td>
192
+ </tr>
193
+ ))}
194
+ </tbody>
195
+ </table>
196
+ <form onSubmit={createTask} className="create-bar">
197
+ <input
198
+ value={title}
199
+ onChange={(e) => setTitle(e.target.value)}
200
+ placeholder="New task..."
201
+ required
202
+ minLength={3}
203
+ />
204
+ <select
205
+ value={priority}
206
+ onChange={(e) => setPriority(e.target.value as Task['priority'])}
207
+ >
208
+ <option value="medium">Medium</option>
209
+ <option value="high">High</option>
210
+ <option value="low">Low</option>
211
+ </select>
212
+ <button type="submit">Add</button>
213
+ </form>
214
+ {formError && <p className="error">{formError}</p>}
215
+ </section>
165
216
 
166
- {/* AI Chat */}
167
- <section className="card">
168
- <h2>AI Assistant <span className="badge badge-ai">@Agent + SSE</span></h2>
169
- <div ref={chatRef} className="chat-box">
170
- {chat.map((m, i) => (
171
- <div key={i} className={`msg ${m.role}`}>{m.role === 'user' ? `You: ${m.text}` : m.text}</div>
172
- ))}
173
- </div>
174
- <div className="chat-bar">
175
- <input
176
- value={chatInput}
177
- onChange={e => setChatInput(e.target.value)}
178
- onKeyDown={e => e.key === 'Enter' && sendChat()}
179
- placeholder="Message the AI assistant..."
180
- disabled={chatBusy}
181
- />
182
- <button type="button" onClick={sendChat} disabled={chatBusy}>Send</button>
183
- </div>
184
- </section>
185
- </main>
217
+ <section className="card">
218
+ <h2>
219
+ AI Assistant <span className="badge badge-ai">@Agent + SSE</span>
220
+ </h2>
221
+ <div ref={chatRef} className="chat-box">
222
+ {chat.map((m, i) => (
223
+ <div key={i} className={`msg ${m.role}`}>
224
+ {m.role === 'user' ? `You: ${m.text}` : m.text}
225
+ </div>
226
+ ))}
227
+ </div>
228
+ <div className="chat-bar">
229
+ <input
230
+ value={chatInput}
231
+ onChange={(e) => setChatInput(e.target.value)}
232
+ onKeyDown={(e) => e.key === 'Enter' && sendChat()}
233
+ placeholder="Message the AI assistant..."
234
+ disabled={chatBusy}
235
+ />
236
+ <button type="button" onClick={sendChat} disabled={chatBusy}>
237
+ Send
238
+ </button>
239
+ </div>
240
+ </section>
241
+ </div>
186
242
 
187
- {/* Footer */}
188
- <footer className="footer">
189
- <p>
243
+ {/* Footer */}
244
+ <footer className="footer">
190
245
  Powered by{' '}
191
- <a href="https://usetheo.dev" target="_blank" rel="noopener noreferrer">TheoKit</a>
246
+ <a href="https://usetheo.dev" target="_blank" rel="noopener noreferrer">
247
+ TheoKit
248
+ </a>
192
249
  {' · '}
193
- <a href="https://github.com/usetheodev/theokit" target="_blank" rel="noopener noreferrer">GitHub</a>
250
+ <a href="https://github.com/usetheodev/theokit" target="_blank" rel="noopener noreferrer">
251
+ GitHub
252
+ </a>
194
253
  {' · '}
195
- <a href="https://discord.usetheo.dev" target="_blank" rel="noopener noreferrer">Discord</a>
196
- </p>
197
- </footer>
198
- </>
254
+ <a href="https://discord.usetheo.dev" target="_blank" rel="noopener noreferrer">
255
+ Discord
256
+ </a>
257
+ </footer>
258
+ </div>
259
+ </div>
199
260
  )
200
261
  }
@@ -14,7 +14,7 @@
14
14
  "typecheck": "tsc --noEmit"
15
15
  },
16
16
  "dependencies": {
17
- "theokit": "^0.5.2",
17
+ "theokit": "^0.5.4",
18
18
  "react": "^19.0.0",
19
19
  "react-dom": "^19.0.0",
20
20
  "react-router": "^7.0.0",