jettypod 4.4.116 → 4.4.120

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/.env +7 -0
  2. package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +124 -48
  3. package/apps/dashboard/app/api/claude/[workItemId]/route.ts +171 -58
  4. package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +161 -10
  5. package/apps/dashboard/app/api/tests/run/stream/route.ts +13 -1
  6. package/apps/dashboard/app/api/usage/route.ts +17 -0
  7. package/apps/dashboard/app/api/work/[id]/route.ts +35 -0
  8. package/apps/dashboard/app/api/work/[id]/status/route.ts +43 -1
  9. package/apps/dashboard/app/connect-claude/page.tsx +24 -0
  10. package/apps/dashboard/app/decision/[id]/page.tsx +14 -14
  11. package/apps/dashboard/app/demo/gates/page.tsx +42 -42
  12. package/apps/dashboard/app/design-system/page.tsx +868 -0
  13. package/apps/dashboard/app/globals.css +6 -2
  14. package/apps/dashboard/app/install-claude/page.tsx +9 -7
  15. package/apps/dashboard/app/layout.tsx +17 -5
  16. package/apps/dashboard/app/login/page.tsx +250 -0
  17. package/apps/dashboard/app/page.tsx +11 -9
  18. package/apps/dashboard/app/settings/page.tsx +4 -2
  19. package/apps/dashboard/app/signup/page.tsx +245 -0
  20. package/apps/dashboard/app/subscribe/page.tsx +11 -0
  21. package/apps/dashboard/app/welcome/page.tsx +24 -1
  22. package/apps/dashboard/app/work/[id]/page.tsx +34 -50
  23. package/apps/dashboard/components/AppShell.tsx +95 -55
  24. package/apps/dashboard/components/CardMenu.tsx +56 -13
  25. package/apps/dashboard/components/ClaudePanel.tsx +301 -582
  26. package/apps/dashboard/components/ClaudePanelInput.tsx +23 -14
  27. package/apps/dashboard/components/ConnectClaudeScreen.tsx +210 -0
  28. package/apps/dashboard/components/CopyableId.tsx +3 -3
  29. package/apps/dashboard/components/DetailReviewActions.tsx +109 -0
  30. package/apps/dashboard/components/DragContext.tsx +75 -65
  31. package/apps/dashboard/components/DraggableCard.tsx +6 -46
  32. package/apps/dashboard/components/DropZone.tsx +2 -2
  33. package/apps/dashboard/components/EditableDetailDescription.tsx +1 -1
  34. package/apps/dashboard/components/EditableTitle.tsx +26 -6
  35. package/apps/dashboard/components/ElapsedTimer.tsx +54 -0
  36. package/apps/dashboard/components/EpicGroup.tsx +329 -0
  37. package/apps/dashboard/components/GateCard.tsx +100 -16
  38. package/apps/dashboard/components/GateChoiceCard.tsx +15 -17
  39. package/apps/dashboard/components/InstallClaudeScreen.tsx +140 -51
  40. package/apps/dashboard/components/JettyLoader.tsx +38 -0
  41. package/apps/dashboard/components/KanbanBoard.tsx +147 -766
  42. package/apps/dashboard/components/KanbanCard.tsx +506 -0
  43. package/apps/dashboard/components/LazyMarkdown.tsx +12 -0
  44. package/apps/dashboard/components/MainNav.tsx +20 -54
  45. package/apps/dashboard/components/MessageBlock.tsx +391 -0
  46. package/apps/dashboard/components/ModeStartCard.tsx +15 -15
  47. package/apps/dashboard/components/OnboardingWelcome.tsx +214 -0
  48. package/apps/dashboard/components/PlaceholderCard.tsx +11 -21
  49. package/apps/dashboard/components/ProjectSwitcher.tsx +36 -8
  50. package/apps/dashboard/components/PrototypeTimeline.tsx +25 -25
  51. package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +265 -301
  52. package/apps/dashboard/components/RealTimeTestsWrapper.tsx +97 -74
  53. package/apps/dashboard/components/ReviewFooter.tsx +141 -0
  54. package/apps/dashboard/components/SessionList.tsx +19 -18
  55. package/apps/dashboard/components/SubscribeContent.tsx +206 -0
  56. package/apps/dashboard/components/TestTree.tsx +15 -14
  57. package/apps/dashboard/components/TipCard.tsx +177 -0
  58. package/apps/dashboard/components/Toast.tsx +5 -5
  59. package/apps/dashboard/components/TypeIcon.tsx +56 -0
  60. package/apps/dashboard/components/UpgradeBanner.tsx +30 -0
  61. package/apps/dashboard/components/WaveCompletionAnimation.tsx +61 -62
  62. package/apps/dashboard/components/WelcomeScreen.tsx +25 -27
  63. package/apps/dashboard/components/WorkItemHeader.tsx +4 -4
  64. package/apps/dashboard/components/WorkItemTree.tsx +9 -28
  65. package/apps/dashboard/components/settings/AccountSection.tsx +169 -0
  66. package/apps/dashboard/components/settings/EnvVarsSection.tsx +54 -79
  67. package/apps/dashboard/components/settings/GeneralSection.tsx +26 -31
  68. package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -4
  69. package/apps/dashboard/components/ui/Button.tsx +104 -0
  70. package/apps/dashboard/components/ui/Input.tsx +78 -0
  71. package/apps/dashboard/contexts/ClaudeSessionContext.tsx +408 -105
  72. package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -4
  73. package/apps/dashboard/contexts/UsageContext.tsx +155 -0
  74. package/apps/dashboard/contexts/usageHelpers.js +9 -0
  75. package/apps/dashboard/electron/ipc-handlers.js +281 -88
  76. package/apps/dashboard/electron/main.js +691 -131
  77. package/apps/dashboard/electron/preload.js +25 -4
  78. package/apps/dashboard/electron/session-manager.js +163 -0
  79. package/apps/dashboard/electron-builder.config.js +3 -5
  80. package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
  81. package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
  82. package/apps/dashboard/lib/backlog-parser.ts +50 -0
  83. package/apps/dashboard/lib/claude-process-manager.ts +50 -11
  84. package/apps/dashboard/lib/constants.ts +43 -0
  85. package/apps/dashboard/lib/db-bridge.ts +33 -0
  86. package/apps/dashboard/lib/db.ts +136 -20
  87. package/apps/dashboard/lib/kanban-utils.ts +70 -0
  88. package/apps/dashboard/lib/run-migrations.js +27 -2
  89. package/apps/dashboard/lib/session-state-machine.ts +3 -0
  90. package/apps/dashboard/lib/session-stream-manager.ts +144 -38
  91. package/apps/dashboard/lib/shadows.ts +7 -0
  92. package/apps/dashboard/lib/tests.ts +3 -1
  93. package/apps/dashboard/lib/utils.ts +6 -0
  94. package/apps/dashboard/next.config.js +35 -14
  95. package/apps/dashboard/package.json +6 -3
  96. package/apps/dashboard/public/bug-icon.svg +9 -0
  97. package/apps/dashboard/public/buoy-icon.svg +9 -0
  98. package/apps/dashboard/public/fonts/Satoshi-Variable.woff2 +0 -0
  99. package/apps/dashboard/public/fonts/Satoshi-VariableItalic.woff2 +0 -0
  100. package/apps/dashboard/public/in-flight-seagull.svg +9 -0
  101. package/apps/dashboard/public/jetty-icon-loading-alt.svg +11 -0
  102. package/apps/dashboard/public/jetty-icon-loading.svg +11 -0
  103. package/apps/dashboard/public/jettypod_logo.png +0 -0
  104. package/apps/dashboard/public/pier-icon.svg +14 -0
  105. package/apps/dashboard/public/star-icon.svg +9 -0
  106. package/apps/dashboard/public/wrench-icon.svg +9 -0
  107. package/apps/dashboard/scripts/upload-to-r2.js +89 -0
  108. package/apps/dashboard/scripts/ws-server.js +191 -0
  109. package/apps/dashboard/tsconfig.tsbuildinfo +1 -0
  110. package/apps/update-server/package.json +16 -0
  111. package/apps/update-server/schema.sql +31 -0
  112. package/apps/update-server/src/index.ts +1085 -0
  113. package/apps/update-server/tsconfig.json +16 -0
  114. package/apps/update-server/wrangler.toml +35 -0
  115. package/cucumber.js +9 -3
  116. package/docs/COMMAND_REFERENCE.md +34 -0
  117. package/hooks/post-checkout +32 -75
  118. package/hooks/post-merge +111 -10
  119. package/jest.setup.js +1 -0
  120. package/jettypod.js +54 -116
  121. package/lib/chore-taxonomy.js +33 -10
  122. package/lib/database.js +36 -16
  123. package/lib/db-watcher.js +1 -1
  124. package/lib/git-hooks/pre-commit +1 -1
  125. package/lib/jettypod-backup.js +27 -4
  126. package/lib/migrations/027-plan-at-creation-column.js +33 -0
  127. package/lib/migrations/028-ready-for-review-column.js +27 -0
  128. package/lib/migrations/029-remove-autoincrement.js +307 -0
  129. package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
  130. package/lib/migrations/index.js +47 -4
  131. package/lib/schema.js +13 -6
  132. package/lib/seed-onboarding.js +101 -69
  133. package/lib/update-command/index.js +9 -175
  134. package/lib/work-commands/index.js +129 -16
  135. package/lib/work-tracking/index.js +86 -46
  136. package/lib/worktree-diagnostics.js +16 -16
  137. package/lib/worktree-facade.js +1 -1
  138. package/lib/worktree-manager.js +8 -8
  139. package/lib/worktree-reconciler.js +5 -5
  140. package/package.json +9 -2
  141. package/scripts/ndjson-to-cucumber-json.js +152 -0
  142. package/scripts/postinstall.js +25 -0
  143. package/skills-templates/bug-mode/SKILL.md +39 -28
  144. package/skills-templates/bug-planning/SKILL.md +25 -29
  145. package/skills-templates/chore-mode/SKILL.md +131 -68
  146. package/skills-templates/chore-mode/verification.js +51 -10
  147. package/skills-templates/chore-planning/SKILL.md +47 -18
  148. package/skills-templates/epic-planning/SKILL.md +68 -48
  149. package/skills-templates/external-transition/SKILL.md +47 -47
  150. package/skills-templates/feature-planning/SKILL.md +83 -73
  151. package/skills-templates/production-mode/SKILL.md +49 -49
  152. package/skills-templates/request-routing/SKILL.md +27 -14
  153. package/skills-templates/simple-improvement/SKILL.md +68 -44
  154. package/skills-templates/speed-mode/SKILL.md +209 -128
  155. package/skills-templates/stable-mode/SKILL.md +105 -94
  156. package/templates/bdd-guidance.md +139 -0
  157. package/templates/bdd-scaffolding/wait.js +18 -0
  158. package/templates/bdd-scaffolding/world.js +19 -0
  159. package/.jettypod-backup/work.db +0 -0
  160. package/apps/dashboard/app/access-code/page.tsx +0 -110
  161. package/lib/discovery-checkpoint.js +0 -123
  162. package/skills-templates/project-discovery/SKILL.md +0 -372
@@ -1,6 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { useState } from 'react';
4
+ import { Input } from '@/components/ui/Input';
5
+ import { Button } from '@/components/ui/Button';
4
6
 
5
7
  interface EnvVar {
6
8
  name: string;
@@ -200,21 +202,18 @@ export function EnvVarsSection({ initialEnvVars, envFiles: initialEnvFiles, sele
200
202
  if (!hasFiles) {
201
203
  return (
202
204
  <section id="env-vars">
203
- <div className="flex items-center justify-between mb-4">
205
+ <div className="flex items-center justify-between mb-6">
204
206
  <h2 className="text-lg font-medium text-zinc-900 dark:text-zinc-100">
205
207
  Environment Variables
206
208
  </h2>
207
209
  </div>
208
210
  <div className="text-center py-8">
209
- <p className="text-sm text-zinc-500 dark:text-zinc-400 mb-4">
211
+ <p className="text-base text-zinc-500 dark:text-zinc-400 mb-6">
210
212
  No .env files found
211
213
  </p>
212
- <button
213
- onClick={handleCreateEnvFile}
214
- className="px-4 py-2 text-sm font-medium bg-blue-600 hover:bg-blue-500 text-white rounded transition-colors"
215
- >
214
+ <Button onClick={handleCreateEnvFile} size="sm">
216
215
  Create .env file
217
- </button>
216
+ </Button>
218
217
  </div>
219
218
  </section>
220
219
  );
@@ -222,28 +221,28 @@ export function EnvVarsSection({ initialEnvVars, envFiles: initialEnvFiles, sele
222
221
 
223
222
  return (
224
223
  <section id="env-vars">
225
- <div className="flex items-center justify-between mb-4">
224
+ <div className="flex items-center justify-between mb-6">
226
225
  <h2 className="text-lg font-medium text-zinc-900 dark:text-zinc-100">
227
226
  Environment Variables
228
227
  </h2>
229
- <button
228
+ <Button
230
229
  onClick={() => { setIsAdding(true); setFormName(''); setFormValue(''); setErrorMessage(null); }}
231
- className="px-3 py-1.5 text-sm font-medium bg-blue-600 hover:bg-blue-500 text-white rounded transition-colors"
230
+ size="sm"
232
231
  >
233
232
  Add Variable
234
- </button>
233
+ </Button>
235
234
  </div>
236
235
 
237
236
  {/* File Selector */}
238
237
  {envFiles.length > 0 && (
239
- <div className="mb-4">
240
- <label className="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1">
238
+ <div className="mb-6">
239
+ <label className="block text-base font-medium text-zinc-700 dark:text-zinc-300 mb-1.5">
241
240
  File
242
241
  </label>
243
242
  <select
244
243
  value={currentFile || ''}
245
244
  onChange={(e) => handleFileChange(e.target.value)}
246
- className="w-full px-3 py-2 border border-zinc-300 dark:border-zinc-600 rounded-lg bg-white dark:bg-zinc-800 text-zinc-900 dark:text-zinc-100 text-sm"
245
+ className="w-full px-4 py-3 border-2 border-zinc-300 dark:border-zinc-600 rounded-lg bg-white dark:bg-zinc-800 text-zinc-900 dark:text-zinc-100 text-base"
247
246
  >
248
247
  {envFiles.map((file) => (
249
248
  <option key={file} value={file}>
@@ -255,58 +254,52 @@ export function EnvVarsSection({ initialEnvVars, envFiles: initialEnvFiles, sele
255
254
  )}
256
255
 
257
256
  {successMessage && (
258
- <div className="mb-4 px-4 py-2 bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300 rounded-lg text-sm">
257
+ <div className="mb-6 px-6 py-3 bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300 rounded-lg text-base">
259
258
  {successMessage}
260
259
  </div>
261
260
  )}
262
261
 
263
262
  {errorMessage && (
264
- <div className="mb-4 px-4 py-2 bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300 rounded-lg text-sm">
263
+ <div className="mb-6 px-6 py-3 bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300 rounded-lg text-base">
265
264
  {errorMessage}
266
265
  </div>
267
266
  )}
268
267
 
269
268
  {/* Add Form */}
270
269
  {isAdding && (
271
- <div className="mb-4 p-4 bg-zinc-50 dark:bg-zinc-800/50 rounded-lg">
272
- <div className="space-y-3">
270
+ <div className="mb-6 p-6 bg-zinc-50 dark:bg-zinc-800/50 rounded-lg">
271
+ <div className="space-y-4">
273
272
  <div>
274
- <label className="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1">
273
+ <label className="block text-base font-medium text-zinc-700 dark:text-zinc-300 mb-1.5">
275
274
  Variable Name
276
275
  </label>
277
- <input
276
+ <Input
278
277
  type="text"
279
278
  value={formName}
280
279
  onChange={(e) => { setFormName(e.target.value); setErrorMessage(null); }}
281
- className="w-full px-3 py-2 border border-zinc-300 dark:border-zinc-600 rounded-lg bg-white dark:bg-zinc-800 text-zinc-900 dark:text-zinc-100"
280
+ size="sm"
282
281
  placeholder="API_KEY"
283
282
  />
284
283
  </div>
285
284
  <div>
286
- <label className="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1">
285
+ <label className="block text-base font-medium text-zinc-700 dark:text-zinc-300 mb-1.5">
287
286
  Value
288
287
  </label>
289
- <input
288
+ <Input
290
289
  type="text"
291
290
  value={formValue}
292
291
  onChange={(e) => { setFormValue(e.target.value); setErrorMessage(null); }}
293
- className="w-full px-3 py-2 border border-zinc-300 dark:border-zinc-600 rounded-lg bg-white dark:bg-zinc-800 text-zinc-900 dark:text-zinc-100"
292
+ size="sm"
294
293
  placeholder="your-secret-value"
295
294
  />
296
295
  </div>
297
- <div className="flex gap-2">
298
- <button
299
- onClick={handleAdd}
300
- className="px-3 py-1.5 text-sm font-medium bg-blue-600 hover:bg-blue-500 text-white rounded transition-colors"
301
- >
296
+ <div className="flex gap-3">
297
+ <Button onClick={handleAdd} size="sm">
302
298
  Save
303
- </button>
304
- <button
305
- onClick={() => { setIsAdding(false); setErrorMessage(null); }}
306
- className="px-3 py-1.5 text-sm font-medium text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100 transition-colors"
307
- >
299
+ </Button>
300
+ <Button onClick={() => { setIsAdding(false); setErrorMessage(null); }} variant="ghost" size="sm">
308
301
  Cancel
309
- </button>
302
+ </Button>
310
303
  </div>
311
304
  </div>
312
305
  </div>
@@ -314,33 +307,27 @@ export function EnvVarsSection({ initialEnvVars, envFiles: initialEnvFiles, sele
314
307
 
315
308
  {/* Edit Form */}
316
309
  {editingName && (
317
- <div className="mb-4 p-4 bg-zinc-50 dark:bg-zinc-800/50 rounded-lg">
318
- <div className="space-y-3">
310
+ <div className="mb-6 p-6 bg-zinc-50 dark:bg-zinc-800/50 rounded-lg">
311
+ <div className="space-y-4">
319
312
  <div>
320
- <label className="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1">
313
+ <label className="block text-base font-medium text-zinc-700 dark:text-zinc-300 mb-1.5">
321
314
  Editing: {editingName}
322
315
  </label>
323
- <input
316
+ <Input
324
317
  type="text"
325
318
  value={formValue}
326
319
  onChange={(e) => setFormValue(e.target.value)}
327
- className="w-full px-3 py-2 border border-zinc-300 dark:border-zinc-600 rounded-lg bg-white dark:bg-zinc-800 text-zinc-900 dark:text-zinc-100"
320
+ size="sm"
328
321
  placeholder="new-value"
329
322
  />
330
323
  </div>
331
- <div className="flex gap-2">
332
- <button
333
- onClick={handleEdit}
334
- className="px-3 py-1.5 text-sm font-medium bg-blue-600 hover:bg-blue-500 text-white rounded transition-colors"
335
- >
324
+ <div className="flex gap-3">
325
+ <Button onClick={handleEdit} size="sm">
336
326
  Save
337
- </button>
338
- <button
339
- onClick={() => setEditingName(null)}
340
- className="px-3 py-1.5 text-sm font-medium text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100 transition-colors"
341
- >
327
+ </Button>
328
+ <Button onClick={() => setEditingName(null)} variant="ghost" size="sm">
342
329
  Cancel
343
- </button>
330
+ </Button>
344
331
  </div>
345
332
  </div>
346
333
  </div>
@@ -348,30 +335,24 @@ export function EnvVarsSection({ initialEnvVars, envFiles: initialEnvFiles, sele
348
335
 
349
336
  {/* Delete Confirmation */}
350
337
  {deletingName && (
351
- <div className="mb-4 p-4 bg-red-50 dark:bg-red-900/20 rounded-lg">
352
- <p className="text-sm text-red-700 dark:text-red-300 mb-3">
338
+ <div className="mb-6 p-6 bg-red-50 dark:bg-red-900/20 rounded-lg">
339
+ <p className="text-base text-red-700 dark:text-red-300 mb-3">
353
340
  Are you sure you want to delete &quot;{deletingName}&quot;?
354
341
  </p>
355
- <div className="flex gap-2">
356
- <button
357
- onClick={handleDelete}
358
- className="px-3 py-1.5 text-sm font-medium bg-red-600 hover:bg-red-500 text-white rounded transition-colors"
359
- >
342
+ <div className="flex gap-3">
343
+ <Button onClick={handleDelete} variant="destructive" size="sm">
360
344
  Delete
361
- </button>
362
- <button
363
- onClick={() => setDeletingName(null)}
364
- className="px-3 py-1.5 text-sm font-medium text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100 transition-colors"
365
- >
345
+ </Button>
346
+ <Button onClick={() => setDeletingName(null)} variant="ghost" size="sm">
366
347
  Cancel
367
- </button>
348
+ </Button>
368
349
  </div>
369
350
  </div>
370
351
  )}
371
352
 
372
353
  {/* Env Vars List */}
373
354
  {envVars.length === 0 ? (
374
- <p className="text-sm text-zinc-500 dark:text-zinc-400">
355
+ <p className="text-base text-zinc-500 dark:text-zinc-400">
375
356
  No environment variables in this file yet.
376
357
  </p>
377
358
  ) : (
@@ -379,30 +360,24 @@ export function EnvVarsSection({ initialEnvVars, envFiles: initialEnvFiles, sele
379
360
  {envVars.map((envVar) => (
380
361
  <div
381
362
  key={envVar.name}
382
- className="flex items-center justify-between p-3 bg-zinc-50 dark:bg-zinc-800/50 rounded-lg"
363
+ className="flex items-center justify-between p-4 bg-zinc-50 dark:bg-zinc-800/50 rounded-lg"
383
364
  >
384
365
  <div>
385
- <span className="font-mono text-sm font-medium text-zinc-900 dark:text-zinc-100">
366
+ <span className="font-mono text-base font-medium text-zinc-900 dark:text-zinc-100">
386
367
  {envVar.name}
387
368
  </span>
388
369
  <span className="mx-2 text-zinc-400">=</span>
389
- <span className="font-mono text-sm text-zinc-500 dark:text-zinc-400">
370
+ <span className="font-mono text-base text-zinc-500 dark:text-zinc-400">
390
371
  {maskValue(envVar.value)}
391
372
  </span>
392
373
  </div>
393
- <div className="flex gap-2">
394
- <button
395
- onClick={() => startEdit(envVar)}
396
- className="px-2 py-1 text-xs text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100 transition-colors"
397
- >
374
+ <div className="flex gap-3">
375
+ <Button onClick={() => startEdit(envVar)} variant="ghost" size="sm">
398
376
  Edit
399
- </button>
400
- <button
401
- onClick={() => setDeletingName(envVar.name)}
402
- className="px-2 py-1 text-xs text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 transition-colors"
403
- >
377
+ </Button>
378
+ <Button onClick={() => setDeletingName(envVar.name)} variant="destructive" size="sm">
404
379
  Delete
405
- </button>
380
+ </Button>
406
381
  </div>
407
382
  </div>
408
383
  ))}
@@ -1,6 +1,8 @@
1
1
  'use client';
2
2
 
3
3
  import { useState } from 'react';
4
+ import { Input } from '@/components/ui/Input';
5
+ import { Button } from '@/components/ui/Button';
4
6
 
5
7
  interface MainBranchInfo {
6
8
  branch: string;
@@ -64,79 +66,72 @@ export function GeneralSection({ initialMainBranch }: GeneralSectionProps) {
64
66
 
65
67
  return (
66
68
  <section id="general">
67
- <div className="flex items-center justify-between mb-4">
69
+ <div className="flex items-center justify-between mb-6">
68
70
  <h2 className="text-lg font-medium text-zinc-900 dark:text-zinc-100">
69
71
  General
70
72
  </h2>
71
73
  </div>
72
74
 
73
75
  {successMessage && (
74
- <div className="mb-4 px-4 py-2 bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300 rounded-lg text-sm">
76
+ <div className="mb-4 px-6 py-3 bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300 rounded-lg text-base">
75
77
  {successMessage}
76
78
  </div>
77
79
  )}
78
80
 
79
81
  {/* Main Branch Setting */}
80
- <div className="p-4 bg-zinc-50 dark:bg-zinc-800/50 rounded-lg">
82
+ <div className="p-6 bg-zinc-50 dark:bg-zinc-800/50 rounded-lg">
81
83
  <div className="flex items-center justify-between">
82
84
  <div>
83
- <label className="block text-sm font-medium text-zinc-900 dark:text-zinc-100">
85
+ <label className="block text-base font-medium text-zinc-900 dark:text-zinc-100">
84
86
  Main Branch
85
87
  </label>
86
- <p className="text-xs text-zinc-500 dark:text-zinc-400 mt-0.5">
88
+ <p className="text-base text-zinc-500 dark:text-zinc-400 mt-1">
87
89
  The branch used as the base for merges and worktrees.
88
90
  </p>
89
91
  </div>
90
92
  {!isEditing && (
91
- <div className="flex items-center gap-2">
92
- <span className="font-mono text-sm text-zinc-900 dark:text-zinc-100">
93
+ <div className="flex items-center gap-3">
94
+ <span className="font-mono text-base text-zinc-900 dark:text-zinc-100">
93
95
  {mainBranch.branch}
94
96
  </span>
95
- <span className="px-1.5 py-0.5 text-xs rounded bg-zinc-200 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-400">
97
+ <span className="px-2 py-1 text-xs rounded bg-zinc-200 dark:bg-zinc-700 text-zinc-600 dark:text-zinc-400">
96
98
  {mainBranch.source}
97
99
  </span>
98
- <button
100
+ <Button
99
101
  onClick={() => setIsEditing(true)}
100
- className="px-2 py-1 text-xs text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100 transition-colors"
102
+ variant="ghost"
103
+ size="sm"
101
104
  >
102
105
  Edit
103
- </button>
106
+ </Button>
104
107
  </div>
105
108
  )}
106
109
  </div>
107
110
 
108
111
  {isEditing && (
109
- <div className="mt-3 space-y-3">
110
- <div className="flex gap-2">
111
- <input
112
+ <div className="mt-4 space-y-4">
113
+ <div className="flex gap-3">
114
+ <Input
112
115
  type="text"
113
116
  value={inputValue}
114
117
  onChange={(e) => setInputValue(e.target.value)}
115
- className="flex-1 px-3 py-2 border border-zinc-300 dark:border-zinc-600 rounded-lg bg-white dark:bg-zinc-800 text-zinc-900 dark:text-zinc-100 font-mono text-sm"
118
+ size="sm"
119
+ className="flex-1 font-mono"
116
120
  placeholder="main"
117
121
  />
118
122
  </div>
119
- <div className="flex gap-2">
120
- <button
121
- onClick={handleSave}
122
- className="px-3 py-1.5 text-sm font-medium bg-blue-600 hover:bg-blue-500 text-white rounded transition-colors"
123
- >
123
+ <div className="flex gap-3">
124
+ <Button onClick={handleSave} size="sm">
124
125
  Save
125
- </button>
126
+ </Button>
126
127
  {mainBranch.source === 'configured' && (
127
- <button
128
- onClick={handleReset}
129
- className="px-3 py-1.5 text-sm font-medium text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100 transition-colors"
130
- >
128
+ <Button onClick={handleReset} variant="ghost" size="sm">
131
129
  Reset to Auto-detect
132
- </button>
130
+ </Button>
133
131
  )}
134
- <button
135
- onClick={handleCancel}
136
- className="px-3 py-1.5 text-sm font-medium text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100 transition-colors"
137
- >
132
+ <Button onClick={handleCancel} variant="ghost" size="sm">
138
133
  Cancel
139
- </button>
134
+ </Button>
140
135
  </div>
141
136
  </div>
142
137
  )}
@@ -12,17 +12,17 @@ export function SettingsLayout({ tabs }: { tabs: SettingsTab[] }) {
12
12
  const [activeTab, setActiveTab] = useState(tabs[0]?.id || '');
13
13
 
14
14
  return (
15
- <div className="flex gap-6">
15
+ <div className="flex gap-8">
16
16
  <nav className="w-48 flex-shrink-0">
17
- <ul className="space-y-1">
17
+ <ul className="space-y-1.5">
18
18
  {tabs.map((tab) => (
19
19
  <li key={tab.id}>
20
20
  <button
21
21
  onClick={() => setActiveTab(tab.id)}
22
- className={`block w-full text-left px-3 py-2 text-sm font-medium rounded-lg ${
22
+ className={`block w-full text-left px-4 py-3 text-base font-medium rounded-lg ${
23
23
  activeTab === tab.id
24
24
  ? 'text-zinc-900 dark:text-zinc-100 bg-zinc-100 dark:bg-zinc-800'
25
- : 'text-zinc-600 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800'
25
+ : 'text-zinc-600 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors duration-200 ease-out'
26
26
  }`}
27
27
  >
28
28
  {tab.label}
@@ -0,0 +1,104 @@
1
+ import * as React from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { cn } from '@/lib/utils';
4
+
5
+ const primaryStyle: React.CSSProperties = {
6
+ backgroundColor: '#819D9F',
7
+ color: '#ffffff',
8
+ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.06), 0 4px 12px rgba(129, 157, 159, 0.2)',
9
+ };
10
+
11
+ const accentStyle: React.CSSProperties = {
12
+ backgroundColor: '#e57a44',
13
+ color: '#ffffff',
14
+ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.06), 0 4px 12px rgba(229, 122, 68, 0.2)',
15
+ };
16
+
17
+ const secondaryStyle: React.CSSProperties = {
18
+ border: '2px solid #18181b',
19
+ };
20
+
21
+ const destructiveStyle: React.CSSProperties = {
22
+ border: '2px solid #dc2626',
23
+ };
24
+
25
+ const buttonVariants = cva(
26
+ 'inline-flex items-center justify-center font-medium transition-[color,background-color,border-color,box-shadow,transform,opacity] duration-200 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400 dark:focus-visible:ring-zinc-500 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:opacity-40 disabled:grayscale disabled:pointer-events-none cursor-pointer',
27
+ {
28
+ variants: {
29
+ variant: {
30
+ primary: 'rounded-xl text-white hover:brightness-105 active:scale-[0.98]',
31
+ secondary:
32
+ 'rounded-xl text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 active:scale-[0.98]',
33
+ ghost:
34
+ 'rounded-xl text-zinc-600 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800 hover:text-zinc-900 dark:hover:text-zinc-100 active:scale-[0.98]',
35
+ destructive:
36
+ 'rounded-xl text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-950 active:scale-[0.98]',
37
+ accent:
38
+ 'rounded-xl text-white hover:brightness-105 active:scale-[0.98]',
39
+ menu:
40
+ 'w-full text-left rounded-lg text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-700',
41
+ },
42
+ size: {
43
+ xs: 'px-2.5 py-0.5 text-xs',
44
+ sm: 'px-4 py-2 text-sm',
45
+ default: 'px-5 py-3 text-base',
46
+ lg: 'py-4 px-8 text-base',
47
+ icon: 'p-1.5',
48
+ },
49
+ },
50
+ defaultVariants: {
51
+ variant: 'primary',
52
+ size: 'default',
53
+ },
54
+ }
55
+ );
56
+
57
+ interface ButtonProps
58
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
59
+ VariantProps<typeof buttonVariants> {
60
+ fullWidth?: boolean;
61
+ loading?: boolean;
62
+ }
63
+
64
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
65
+ ({ className, variant, size, fullWidth, loading, style, children, ...props }, ref) => {
66
+ const isPrimary = variant === 'primary' || variant === undefined;
67
+ const isAccent = variant === 'accent';
68
+ const isSecondary = variant === 'secondary';
69
+ const isDestructive = variant === 'destructive';
70
+ const inlineStyle = isPrimary
71
+ ? primaryStyle
72
+ : isAccent
73
+ ? accentStyle
74
+ : isSecondary
75
+ ? secondaryStyle
76
+ : isDestructive
77
+ ? destructiveStyle
78
+ : undefined;
79
+ return (
80
+ <button
81
+ className={cn(
82
+ buttonVariants({ variant, size }),
83
+ fullWidth && 'w-full',
84
+ className
85
+ )}
86
+ style={inlineStyle ? { ...inlineStyle, ...style } : style}
87
+ disabled={loading || props.disabled}
88
+ ref={ref}
89
+ {...props}
90
+ >
91
+ {loading ? (
92
+ <span className="flex items-center gap-2">
93
+ <span className="inline-block w-4 h-4 rounded-full animate-spin" style={{ border: '2px solid currentColor', borderTopColor: 'transparent' }} />
94
+ {children}
95
+ </span>
96
+ ) : children}
97
+ </button>
98
+ );
99
+ }
100
+ );
101
+ Button.displayName = 'Button';
102
+
103
+ export { Button, buttonVariants };
104
+ export type { ButtonProps };
@@ -0,0 +1,78 @@
1
+ import * as React from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { cn } from '@/lib/utils';
4
+
5
+ const baseStyle: React.CSSProperties = {
6
+ border: '2px solid #27272a',
7
+ borderRadius: '12px',
8
+ };
9
+
10
+ const focusStyle: React.CSSProperties = {
11
+ border: '2px solid #819D9F',
12
+ boxShadow: '0 0 0 3px rgba(129, 157, 159, 0.15)',
13
+ outline: 'none',
14
+ };
15
+
16
+ const errorBaseStyle: React.CSSProperties = {
17
+ border: '2px solid #dc2626',
18
+ borderRadius: '12px',
19
+ };
20
+
21
+ const errorFocusStyle: React.CSSProperties = {
22
+ border: '2px solid #dc2626',
23
+ boxShadow: '0 0 0 3px rgba(220, 38, 38, 0.15)',
24
+ outline: 'none',
25
+ };
26
+
27
+ const inputVariants = cva(
28
+ 'w-full bg-white dark:bg-zinc-800 text-zinc-900 dark:text-zinc-100 placeholder:text-zinc-400 dark:placeholder:text-zinc-500 transition-[border-color,box-shadow] duration-200 ease-out focus:outline-none disabled:opacity-40 disabled:grayscale disabled:pointer-events-none',
29
+ {
30
+ variants: {
31
+ size: {
32
+ sm: 'px-4 py-2 text-sm',
33
+ default: 'px-6 py-5 text-base',
34
+ lg: 'px-8 py-6 text-lg',
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ size: 'default',
39
+ },
40
+ }
41
+ );
42
+
43
+ interface InputProps
44
+ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>,
45
+ VariantProps<typeof inputVariants> {
46
+ error?: boolean;
47
+ }
48
+
49
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
50
+ ({ className, size, error, style, onFocus, onBlur, ...props }, ref) => {
51
+ const [isFocused, setIsFocused] = React.useState(false);
52
+
53
+ const inlineStyle = error
54
+ ? isFocused ? { ...errorFocusStyle, ...style } : { ...errorBaseStyle, ...style }
55
+ : isFocused ? { ...focusStyle, ...style } : { ...baseStyle, ...style };
56
+
57
+ return (
58
+ <input
59
+ ref={ref}
60
+ className={cn(inputVariants({ size }), className)}
61
+ style={inlineStyle}
62
+ onFocus={(e) => {
63
+ setIsFocused(true);
64
+ onFocus?.(e);
65
+ }}
66
+ onBlur={(e) => {
67
+ setIsFocused(false);
68
+ onBlur?.(e);
69
+ }}
70
+ {...props}
71
+ />
72
+ );
73
+ }
74
+ );
75
+ Input.displayName = 'Input';
76
+
77
+ export { Input, inputVariants };
78
+ export type { InputProps };