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.
- package/.env +7 -0
- package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +124 -48
- package/apps/dashboard/app/api/claude/[workItemId]/route.ts +171 -58
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +161 -10
- package/apps/dashboard/app/api/tests/run/stream/route.ts +13 -1
- package/apps/dashboard/app/api/usage/route.ts +17 -0
- package/apps/dashboard/app/api/work/[id]/route.ts +35 -0
- package/apps/dashboard/app/api/work/[id]/status/route.ts +43 -1
- package/apps/dashboard/app/connect-claude/page.tsx +24 -0
- package/apps/dashboard/app/decision/[id]/page.tsx +14 -14
- package/apps/dashboard/app/demo/gates/page.tsx +42 -42
- package/apps/dashboard/app/design-system/page.tsx +868 -0
- package/apps/dashboard/app/globals.css +6 -2
- package/apps/dashboard/app/install-claude/page.tsx +9 -7
- package/apps/dashboard/app/layout.tsx +17 -5
- package/apps/dashboard/app/login/page.tsx +250 -0
- package/apps/dashboard/app/page.tsx +11 -9
- package/apps/dashboard/app/settings/page.tsx +4 -2
- package/apps/dashboard/app/signup/page.tsx +245 -0
- package/apps/dashboard/app/subscribe/page.tsx +11 -0
- package/apps/dashboard/app/welcome/page.tsx +24 -1
- package/apps/dashboard/app/work/[id]/page.tsx +34 -50
- package/apps/dashboard/components/AppShell.tsx +95 -55
- package/apps/dashboard/components/CardMenu.tsx +56 -13
- package/apps/dashboard/components/ClaudePanel.tsx +301 -582
- package/apps/dashboard/components/ClaudePanelInput.tsx +23 -14
- package/apps/dashboard/components/ConnectClaudeScreen.tsx +210 -0
- package/apps/dashboard/components/CopyableId.tsx +3 -3
- package/apps/dashboard/components/DetailReviewActions.tsx +109 -0
- package/apps/dashboard/components/DragContext.tsx +75 -65
- package/apps/dashboard/components/DraggableCard.tsx +6 -46
- package/apps/dashboard/components/DropZone.tsx +2 -2
- package/apps/dashboard/components/EditableDetailDescription.tsx +1 -1
- package/apps/dashboard/components/EditableTitle.tsx +26 -6
- package/apps/dashboard/components/ElapsedTimer.tsx +54 -0
- package/apps/dashboard/components/EpicGroup.tsx +329 -0
- package/apps/dashboard/components/GateCard.tsx +100 -16
- package/apps/dashboard/components/GateChoiceCard.tsx +15 -17
- package/apps/dashboard/components/InstallClaudeScreen.tsx +140 -51
- package/apps/dashboard/components/JettyLoader.tsx +38 -0
- package/apps/dashboard/components/KanbanBoard.tsx +147 -766
- package/apps/dashboard/components/KanbanCard.tsx +506 -0
- package/apps/dashboard/components/LazyMarkdown.tsx +12 -0
- package/apps/dashboard/components/MainNav.tsx +20 -54
- package/apps/dashboard/components/MessageBlock.tsx +391 -0
- package/apps/dashboard/components/ModeStartCard.tsx +15 -15
- package/apps/dashboard/components/OnboardingWelcome.tsx +214 -0
- package/apps/dashboard/components/PlaceholderCard.tsx +11 -21
- package/apps/dashboard/components/ProjectSwitcher.tsx +36 -8
- package/apps/dashboard/components/PrototypeTimeline.tsx +25 -25
- package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +265 -301
- package/apps/dashboard/components/RealTimeTestsWrapper.tsx +97 -74
- package/apps/dashboard/components/ReviewFooter.tsx +141 -0
- package/apps/dashboard/components/SessionList.tsx +19 -18
- package/apps/dashboard/components/SubscribeContent.tsx +206 -0
- package/apps/dashboard/components/TestTree.tsx +15 -14
- package/apps/dashboard/components/TipCard.tsx +177 -0
- package/apps/dashboard/components/Toast.tsx +5 -5
- package/apps/dashboard/components/TypeIcon.tsx +56 -0
- package/apps/dashboard/components/UpgradeBanner.tsx +30 -0
- package/apps/dashboard/components/WaveCompletionAnimation.tsx +61 -62
- package/apps/dashboard/components/WelcomeScreen.tsx +25 -27
- package/apps/dashboard/components/WorkItemHeader.tsx +4 -4
- package/apps/dashboard/components/WorkItemTree.tsx +9 -28
- package/apps/dashboard/components/settings/AccountSection.tsx +169 -0
- package/apps/dashboard/components/settings/EnvVarsSection.tsx +54 -79
- package/apps/dashboard/components/settings/GeneralSection.tsx +26 -31
- package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -4
- package/apps/dashboard/components/ui/Button.tsx +104 -0
- package/apps/dashboard/components/ui/Input.tsx +78 -0
- package/apps/dashboard/contexts/ClaudeSessionContext.tsx +408 -105
- package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -4
- package/apps/dashboard/contexts/UsageContext.tsx +155 -0
- package/apps/dashboard/contexts/usageHelpers.js +9 -0
- package/apps/dashboard/electron/ipc-handlers.js +281 -88
- package/apps/dashboard/electron/main.js +691 -131
- package/apps/dashboard/electron/preload.js +25 -4
- package/apps/dashboard/electron/session-manager.js +163 -0
- package/apps/dashboard/electron-builder.config.js +3 -5
- package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
- package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
- package/apps/dashboard/lib/backlog-parser.ts +50 -0
- package/apps/dashboard/lib/claude-process-manager.ts +50 -11
- package/apps/dashboard/lib/constants.ts +43 -0
- package/apps/dashboard/lib/db-bridge.ts +33 -0
- package/apps/dashboard/lib/db.ts +136 -20
- package/apps/dashboard/lib/kanban-utils.ts +70 -0
- package/apps/dashboard/lib/run-migrations.js +27 -2
- package/apps/dashboard/lib/session-state-machine.ts +3 -0
- package/apps/dashboard/lib/session-stream-manager.ts +144 -38
- package/apps/dashboard/lib/shadows.ts +7 -0
- package/apps/dashboard/lib/tests.ts +3 -1
- package/apps/dashboard/lib/utils.ts +6 -0
- package/apps/dashboard/next.config.js +35 -14
- package/apps/dashboard/package.json +6 -3
- package/apps/dashboard/public/bug-icon.svg +9 -0
- package/apps/dashboard/public/buoy-icon.svg +9 -0
- package/apps/dashboard/public/fonts/Satoshi-Variable.woff2 +0 -0
- package/apps/dashboard/public/fonts/Satoshi-VariableItalic.woff2 +0 -0
- package/apps/dashboard/public/in-flight-seagull.svg +9 -0
- package/apps/dashboard/public/jetty-icon-loading-alt.svg +11 -0
- package/apps/dashboard/public/jetty-icon-loading.svg +11 -0
- package/apps/dashboard/public/jettypod_logo.png +0 -0
- package/apps/dashboard/public/pier-icon.svg +14 -0
- package/apps/dashboard/public/star-icon.svg +9 -0
- package/apps/dashboard/public/wrench-icon.svg +9 -0
- package/apps/dashboard/scripts/upload-to-r2.js +89 -0
- package/apps/dashboard/scripts/ws-server.js +191 -0
- package/apps/dashboard/tsconfig.tsbuildinfo +1 -0
- package/apps/update-server/package.json +16 -0
- package/apps/update-server/schema.sql +31 -0
- package/apps/update-server/src/index.ts +1085 -0
- package/apps/update-server/tsconfig.json +16 -0
- package/apps/update-server/wrangler.toml +35 -0
- package/cucumber.js +9 -3
- package/docs/COMMAND_REFERENCE.md +34 -0
- package/hooks/post-checkout +32 -75
- package/hooks/post-merge +111 -10
- package/jest.setup.js +1 -0
- package/jettypod.js +54 -116
- package/lib/chore-taxonomy.js +33 -10
- package/lib/database.js +36 -16
- package/lib/db-watcher.js +1 -1
- package/lib/git-hooks/pre-commit +1 -1
- package/lib/jettypod-backup.js +27 -4
- package/lib/migrations/027-plan-at-creation-column.js +33 -0
- package/lib/migrations/028-ready-for-review-column.js +27 -0
- package/lib/migrations/029-remove-autoincrement.js +307 -0
- package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
- package/lib/migrations/index.js +47 -4
- package/lib/schema.js +13 -6
- package/lib/seed-onboarding.js +101 -69
- package/lib/update-command/index.js +9 -175
- package/lib/work-commands/index.js +129 -16
- package/lib/work-tracking/index.js +86 -46
- package/lib/worktree-diagnostics.js +16 -16
- package/lib/worktree-facade.js +1 -1
- package/lib/worktree-manager.js +8 -8
- package/lib/worktree-reconciler.js +5 -5
- package/package.json +9 -2
- package/scripts/ndjson-to-cucumber-json.js +152 -0
- package/scripts/postinstall.js +25 -0
- package/skills-templates/bug-mode/SKILL.md +39 -28
- package/skills-templates/bug-planning/SKILL.md +25 -29
- package/skills-templates/chore-mode/SKILL.md +131 -68
- package/skills-templates/chore-mode/verification.js +51 -10
- package/skills-templates/chore-planning/SKILL.md +47 -18
- package/skills-templates/epic-planning/SKILL.md +68 -48
- package/skills-templates/external-transition/SKILL.md +47 -47
- package/skills-templates/feature-planning/SKILL.md +83 -73
- package/skills-templates/production-mode/SKILL.md +49 -49
- package/skills-templates/request-routing/SKILL.md +27 -14
- package/skills-templates/simple-improvement/SKILL.md +68 -44
- package/skills-templates/speed-mode/SKILL.md +209 -128
- package/skills-templates/stable-mode/SKILL.md +105 -94
- package/templates/bdd-guidance.md +139 -0
- package/templates/bdd-scaffolding/wait.js +18 -0
- package/templates/bdd-scaffolding/world.js +19 -0
- package/.jettypod-backup/work.db +0 -0
- package/apps/dashboard/app/access-code/page.tsx +0 -110
- package/lib/discovery-checkpoint.js +0 -123
- 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-
|
|
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-
|
|
211
|
+
<p className="text-base text-zinc-500 dark:text-zinc-400 mb-6">
|
|
210
212
|
No .env files found
|
|
211
213
|
</p>
|
|
212
|
-
<
|
|
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
|
-
</
|
|
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-
|
|
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
|
-
<
|
|
228
|
+
<Button
|
|
230
229
|
onClick={() => { setIsAdding(true); setFormName(''); setFormValue(''); setErrorMessage(null); }}
|
|
231
|
-
|
|
230
|
+
size="sm"
|
|
232
231
|
>
|
|
233
232
|
Add Variable
|
|
234
|
-
</
|
|
233
|
+
</Button>
|
|
235
234
|
</div>
|
|
236
235
|
|
|
237
236
|
{/* File Selector */}
|
|
238
237
|
{envFiles.length > 0 && (
|
|
239
|
-
<div className="mb-
|
|
240
|
-
<label className="block text-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
272
|
-
<div className="space-y-
|
|
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-
|
|
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
|
-
<
|
|
276
|
+
<Input
|
|
278
277
|
type="text"
|
|
279
278
|
value={formName}
|
|
280
279
|
onChange={(e) => { setFormName(e.target.value); setErrorMessage(null); }}
|
|
281
|
-
|
|
280
|
+
size="sm"
|
|
282
281
|
placeholder="API_KEY"
|
|
283
282
|
/>
|
|
284
283
|
</div>
|
|
285
284
|
<div>
|
|
286
|
-
<label className="block text-
|
|
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
|
-
<
|
|
288
|
+
<Input
|
|
290
289
|
type="text"
|
|
291
290
|
value={formValue}
|
|
292
291
|
onChange={(e) => { setFormValue(e.target.value); setErrorMessage(null); }}
|
|
293
|
-
|
|
292
|
+
size="sm"
|
|
294
293
|
placeholder="your-secret-value"
|
|
295
294
|
/>
|
|
296
295
|
</div>
|
|
297
|
-
<div className="flex gap-
|
|
298
|
-
<
|
|
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
|
-
</
|
|
304
|
-
<
|
|
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
|
-
</
|
|
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-
|
|
318
|
-
<div className="space-y-
|
|
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-
|
|
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
|
-
<
|
|
316
|
+
<Input
|
|
324
317
|
type="text"
|
|
325
318
|
value={formValue}
|
|
326
319
|
onChange={(e) => setFormValue(e.target.value)}
|
|
327
|
-
|
|
320
|
+
size="sm"
|
|
328
321
|
placeholder="new-value"
|
|
329
322
|
/>
|
|
330
323
|
</div>
|
|
331
|
-
<div className="flex gap-
|
|
332
|
-
<
|
|
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
|
-
</
|
|
338
|
-
<
|
|
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
|
-
</
|
|
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-
|
|
352
|
-
<p className="text-
|
|
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 "{deletingName}"?
|
|
354
341
|
</p>
|
|
355
|
-
<div className="flex gap-
|
|
356
|
-
<
|
|
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
|
-
</
|
|
362
|
-
<
|
|
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
|
-
</
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
394
|
-
<
|
|
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
|
-
</
|
|
400
|
-
<
|
|
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
|
-
</
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
92
|
-
<span className="font-mono text-
|
|
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-
|
|
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
|
-
<
|
|
100
|
+
<Button
|
|
99
101
|
onClick={() => setIsEditing(true)}
|
|
100
|
-
|
|
102
|
+
variant="ghost"
|
|
103
|
+
size="sm"
|
|
101
104
|
>
|
|
102
105
|
Edit
|
|
103
|
-
</
|
|
106
|
+
</Button>
|
|
104
107
|
</div>
|
|
105
108
|
)}
|
|
106
109
|
</div>
|
|
107
110
|
|
|
108
111
|
{isEditing && (
|
|
109
|
-
<div className="mt-
|
|
110
|
-
<div className="flex gap-
|
|
111
|
-
<
|
|
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
|
-
|
|
118
|
+
size="sm"
|
|
119
|
+
className="flex-1 font-mono"
|
|
116
120
|
placeholder="main"
|
|
117
121
|
/>
|
|
118
122
|
</div>
|
|
119
|
-
<div className="flex gap-
|
|
120
|
-
<
|
|
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
|
-
</
|
|
126
|
+
</Button>
|
|
126
127
|
{mainBranch.source === 'configured' && (
|
|
127
|
-
<
|
|
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
|
-
</
|
|
130
|
+
</Button>
|
|
133
131
|
)}
|
|
134
|
-
<
|
|
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
|
-
</
|
|
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-
|
|
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-
|
|
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 };
|