jettypod 4.4.118 → 4.4.121
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 +4 -3
- package/Cargo.lock +6450 -0
- package/Cargo.toml +35 -0
- package/README.md +5 -1
- package/TAURI-MIGRATION-PLAN.md +840 -0
- package/apps/dashboard/app/connect-claude/page.tsx +5 -6
- package/apps/dashboard/app/decision/[id]/page.tsx +63 -58
- package/apps/dashboard/app/demo/gates/page.tsx +43 -45
- package/apps/dashboard/app/design-system/page.tsx +868 -0
- package/apps/dashboard/app/globals.css +80 -4
- package/apps/dashboard/app/install-claude/page.tsx +4 -6
- package/apps/dashboard/app/login/page.tsx +72 -54
- package/apps/dashboard/app/page.tsx +101 -48
- package/apps/dashboard/app/settings/page.tsx +61 -13
- package/apps/dashboard/app/signup/page.tsx +242 -0
- package/apps/dashboard/app/subscribe/page.tsx +0 -2
- package/apps/dashboard/app/tests/page.tsx +37 -4
- package/apps/dashboard/app/welcome/page.tsx +13 -16
- package/apps/dashboard/app/work/[id]/page.tsx +117 -118
- package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
- package/apps/dashboard/components/AppShell.tsx +92 -85
- package/apps/dashboard/components/CardMenu.tsx +45 -12
- package/apps/dashboard/components/ClaudePanel.tsx +771 -850
- package/apps/dashboard/components/ClaudePanelInput.tsx +43 -15
- package/apps/dashboard/components/ConnectClaudeScreen.tsx +17 -34
- package/apps/dashboard/components/CopyableId.tsx +3 -4
- package/apps/dashboard/components/DetailReviewActions.tsx +100 -0
- package/apps/dashboard/components/DragContext.tsx +134 -63
- package/apps/dashboard/components/DraggableCard.tsx +3 -5
- package/apps/dashboard/components/DropZone.tsx +6 -7
- package/apps/dashboard/components/EditableDetailDescription.tsx +7 -13
- package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
- package/apps/dashboard/components/EditableTitle.tsx +26 -7
- package/apps/dashboard/components/ElapsedTimer.tsx +66 -0
- package/apps/dashboard/components/EpicGroup.tsx +359 -0
- package/apps/dashboard/components/GateCard.tsx +79 -17
- package/apps/dashboard/components/GateChoiceCard.tsx +15 -18
- package/apps/dashboard/components/InstallClaudeScreen.tsx +15 -32
- package/apps/dashboard/components/JettyLoader.tsx +37 -0
- package/apps/dashboard/components/KanbanBoard.tsx +368 -958
- package/apps/dashboard/components/KanbanCard.tsx +740 -0
- package/apps/dashboard/components/LazyCard.tsx +62 -0
- package/apps/dashboard/components/LazyMarkdown.tsx +11 -0
- package/apps/dashboard/components/MainNav.tsx +38 -73
- package/apps/dashboard/components/MessageBlock.tsx +468 -0
- package/apps/dashboard/components/ModeStartCard.tsx +15 -16
- package/apps/dashboard/components/OnboardingWelcome.tsx +213 -0
- package/apps/dashboard/components/PlaceholderCard.tsx +3 -4
- package/apps/dashboard/components/ProjectSwitcher.tsx +30 -30
- package/apps/dashboard/components/PrototypeTimeline.tsx +72 -51
- package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +406 -388
- package/apps/dashboard/components/RealTimeTestsWrapper.tsx +373 -235
- package/apps/dashboard/components/ReviewFooter.tsx +139 -0
- package/apps/dashboard/components/SessionList.tsx +19 -19
- package/apps/dashboard/components/SubscribeContent.tsx +91 -47
- package/apps/dashboard/components/TestTree.tsx +16 -16
- package/apps/dashboard/components/TipCard.tsx +16 -17
- package/apps/dashboard/components/Toast.tsx +5 -6
- package/apps/dashboard/components/TypeIcon.tsx +55 -0
- package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
- package/apps/dashboard/components/WaveCompletionAnimation.tsx +52 -65
- package/apps/dashboard/components/WelcomeScreen.tsx +19 -35
- package/apps/dashboard/components/WorkItemHeader.tsx +4 -5
- package/apps/dashboard/components/WorkItemTree.tsx +11 -32
- package/apps/dashboard/components/settings/AccountSection.tsx +55 -35
- package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
- package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
- package/apps/dashboard/components/settings/EnvVarsSection.tsx +74 -152
- package/apps/dashboard/components/settings/GeneralSection.tsx +162 -56
- package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
- package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -5
- package/apps/dashboard/components/ui/Button.tsx +104 -0
- package/apps/dashboard/components/ui/Input.tsx +78 -0
- package/apps/dashboard/components.json +1 -1
- package/apps/dashboard/contexts/ClaudeSessionContext.tsx +711 -418
- package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -5
- package/apps/dashboard/contexts/UsageContext.tsx +87 -32
- package/apps/dashboard/dev.sh +35 -0
- package/apps/dashboard/eslint.config.mjs +9 -9
- package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
- package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
- package/apps/dashboard/hooks/useWebSocket.ts +138 -83
- package/apps/dashboard/index.html +73 -0
- package/apps/dashboard/lib/constants.ts +43 -0
- package/apps/dashboard/lib/data-bridge.ts +722 -0
- package/apps/dashboard/lib/db.ts +69 -1265
- package/apps/dashboard/lib/environment-config.ts +173 -0
- package/apps/dashboard/lib/environment-verification.ts +119 -0
- package/apps/dashboard/lib/kanban-utils.ts +270 -0
- package/apps/dashboard/lib/proof-run.ts +495 -0
- package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
- package/apps/dashboard/lib/run-migrations.js +27 -2
- package/apps/dashboard/lib/service-recovery.ts +326 -0
- package/apps/dashboard/lib/session-state-machine.ts +1 -0
- package/apps/dashboard/lib/session-state-utils.ts +0 -164
- package/apps/dashboard/lib/session-stream-manager.ts +308 -134
- package/apps/dashboard/lib/shadows.ts +7 -0
- package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
- package/apps/dashboard/lib/tauri-bridge.ts +102 -0
- package/apps/dashboard/lib/tauri.ts +106 -0
- package/apps/dashboard/lib/utils.ts +6 -0
- package/apps/dashboard/next-env.d.ts +1 -1
- package/apps/dashboard/package.json +21 -32
- package/apps/dashboard/public/bug-icon.png +0 -0
- package/apps/dashboard/public/buoy-icon.png +0 -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.png +0 -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.png +0 -0
- package/apps/dashboard/public/star-icon.png +0 -0
- package/apps/dashboard/public/wrench-icon.png +0 -0
- package/apps/dashboard/scripts/tauri-build.js +228 -0
- package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
- package/apps/dashboard/scripts/ws-server.js +191 -0
- package/apps/dashboard/src/main.tsx +12 -0
- package/apps/dashboard/src/router.tsx +107 -0
- package/apps/dashboard/src/vite-env.d.ts +1 -0
- package/apps/dashboard/tsconfig.json +7 -12
- package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
- package/apps/dashboard/vite.config.ts +33 -0
- package/apps/update-server/src/index.ts +228 -80
- package/claude-hooks/global-guardrails.js +14 -13
- package/crates/jettypod-cli/Cargo.toml +19 -0
- package/crates/jettypod-cli/src/commands.rs +1249 -0
- package/crates/jettypod-cli/src/main.rs +595 -0
- package/crates/jettypod-core/Cargo.toml +26 -0
- package/crates/jettypod-core/build.rs +98 -0
- package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
- package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
- package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
- package/crates/jettypod-core/src/auth.rs +294 -0
- package/crates/jettypod-core/src/config.rs +397 -0
- package/crates/jettypod-core/src/db/mod.rs +507 -0
- package/crates/jettypod-core/src/db/recovery.rs +114 -0
- package/crates/jettypod-core/src/db/startup.rs +101 -0
- package/crates/jettypod-core/src/db/validate.rs +149 -0
- package/crates/jettypod-core/src/error.rs +76 -0
- package/crates/jettypod-core/src/git.rs +458 -0
- package/crates/jettypod-core/src/lib.rs +20 -0
- package/crates/jettypod-core/src/sessions.rs +625 -0
- package/crates/jettypod-core/src/skills.rs +556 -0
- package/crates/jettypod-core/src/work.rs +1086 -0
- package/crates/jettypod-core/src/worktree.rs +628 -0
- package/crates/jettypod-core/src/ws.rs +767 -0
- package/cucumber-test.cjs +6 -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 +145 -116
- package/lib/bdd-preflight.js +96 -0
- 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/merge-lock.js +111 -253
- package/lib/migrations/027-plan-at-creation-column.js +3 -1
- package/lib/migrations/029-remove-autoincrement.js +307 -0
- package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
- package/lib/migrations/030-rejection-round-columns.js +54 -0
- package/lib/migrations/031-session-isolation-index.js +17 -0
- package/lib/migrations/index.js +47 -4
- package/lib/schema.js +10 -5
- package/lib/seed-onboarding.js +1 -1
- package/lib/update-command/index.js +9 -175
- package/lib/work-commands/index.js +144 -19
- package/lib/work-tracking/index.js +148 -27
- 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 +79 -20
- package/skills-templates/bug-planning/SKILL.md +25 -29
- package/skills-templates/chore-mode/SKILL.md +171 -69
- package/skills-templates/chore-mode/verification.js +51 -10
- package/skills-templates/chore-planning/SKILL.md +47 -18
- package/skills-templates/design-system-selection/SKILL.md +273 -0
- package/skills-templates/epic-planning/SKILL.md +82 -48
- package/skills-templates/external-transition/SKILL.md +47 -47
- package/skills-templates/feature-planning/SKILL.md +173 -74
- package/skills-templates/production-mode/SKILL.md +69 -49
- package/skills-templates/request-routing/SKILL.md +4 -4
- package/skills-templates/simple-improvement/SKILL.md +74 -29
- package/skills-templates/speed-mode/SKILL.md +217 -141
- package/skills-templates/stable-mode/SKILL.md +148 -89
- package/apps/dashboard/README.md +0 -36
- package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -386
- package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -167
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -378
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
- package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
- package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
- package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
- package/apps/dashboard/app/api/kanban/route.ts +0 -15
- package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
- package/apps/dashboard/app/api/settings/general/route.ts +0 -21
- package/apps/dashboard/app/api/tests/route.ts +0 -9
- package/apps/dashboard/app/api/tests/run/route.ts +0 -82
- package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
- package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
- package/apps/dashboard/app/api/usage/route.ts +0 -17
- package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
- package/apps/dashboard/app/layout.tsx +0 -43
- package/apps/dashboard/components/UpgradeBanner.tsx +0 -29
- package/apps/dashboard/electron/ipc-handlers.js +0 -1028
- package/apps/dashboard/electron/main.js +0 -2124
- package/apps/dashboard/electron/preload.js +0 -123
- package/apps/dashboard/electron/session-manager.js +0 -141
- package/apps/dashboard/electron-builder.config.js +0 -357
- package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
- package/apps/dashboard/lib/claude-process-manager.ts +0 -492
- package/apps/dashboard/lib/db-bridge.ts +0 -282
- package/apps/dashboard/lib/prototypes.ts +0 -202
- package/apps/dashboard/lib/test-results-db.ts +0 -307
- package/apps/dashboard/lib/tests.ts +0 -282
- package/apps/dashboard/next.config.js +0 -50
- package/apps/dashboard/postcss.config.mjs +0 -7
- package/apps/dashboard/public/file.svg +0 -1
- package/apps/dashboard/public/globe.svg +0 -1
- package/apps/dashboard/public/next.svg +0 -1
- package/apps/dashboard/public/vercel.svg +0 -1
- package/apps/dashboard/public/window.svg +0 -1
- package/apps/dashboard/scripts/download-node.js +0 -104
- package/apps/dashboard/scripts/upload-to-r2.js +0 -89
- package/docs/bdd-guidance.md +0 -390
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
'use client';
|
|
2
1
|
|
|
3
|
-
import { useState, useRef, useCallback, KeyboardEvent, ChangeEvent } from 'react';
|
|
4
|
-
import {
|
|
2
|
+
import { useState, useRef, useCallback, useEffect, KeyboardEvent, ChangeEvent } from 'react';
|
|
3
|
+
import { m, AnimatePresence } from 'framer-motion';
|
|
5
4
|
|
|
6
5
|
export interface AttachedImage {
|
|
7
6
|
id: string;
|
|
@@ -19,6 +18,7 @@ interface ClaudePanelInputProps {
|
|
|
19
18
|
placeholder?: string;
|
|
20
19
|
attachedImages?: AttachedImage[];
|
|
21
20
|
onImagesChange?: (images: AttachedImage[]) => void;
|
|
21
|
+
activeSessionId?: string | null;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const DOUBLE_ESCAPE_THRESHOLD_MS = 500;
|
|
@@ -31,6 +31,7 @@ export function ClaudePanelInput({
|
|
|
31
31
|
placeholder = 'Type a message...',
|
|
32
32
|
attachedImages: externalImages,
|
|
33
33
|
onImagesChange,
|
|
34
|
+
activeSessionId,
|
|
34
35
|
}: ClaudePanelInputProps) {
|
|
35
36
|
const [message, setMessage] = useState('');
|
|
36
37
|
const [isFocused, setIsFocused] = useState(false);
|
|
@@ -38,10 +39,37 @@ export function ClaudePanelInput({
|
|
|
38
39
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
39
40
|
const lastEscapeTimeRef = useRef<number>(0);
|
|
40
41
|
|
|
42
|
+
// Per-session draft map: save/restore draft text when switching tabs
|
|
43
|
+
const draftsRef = useRef(new Map<string, string>());
|
|
44
|
+
const prevSessionRef = useRef(activeSessionId);
|
|
45
|
+
const messageRef = useRef(message);
|
|
46
|
+
messageRef.current = message;
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const prevId = prevSessionRef.current;
|
|
50
|
+
if (prevId && prevId !== activeSessionId) {
|
|
51
|
+
draftsRef.current.set(prevId, messageRef.current);
|
|
52
|
+
}
|
|
53
|
+
const restored = activeSessionId ? draftsRef.current.get(activeSessionId) ?? '' : '';
|
|
54
|
+
setMessage(restored);
|
|
55
|
+
if (textareaRef.current) {
|
|
56
|
+
textareaRef.current.style.height = 'auto';
|
|
57
|
+
}
|
|
58
|
+
prevSessionRef.current = activeSessionId;
|
|
59
|
+
}, [activeSessionId]);
|
|
60
|
+
|
|
41
61
|
// Use external image state if provided (panel-level drag-drop), otherwise internal
|
|
42
62
|
const attachedImages = externalImages ?? internalImages;
|
|
43
63
|
const setAttachedImages = onImagesChange ?? setInternalImages;
|
|
44
64
|
|
|
65
|
+
// Auto-focus textarea on mount and when active session changes.
|
|
66
|
+
// The mount case handles when ClaudePanelInput replaces ReviewFooter after rejection.
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (textareaRef.current) {
|
|
69
|
+
textareaRef.current.focus();
|
|
70
|
+
}
|
|
71
|
+
}, [activeSessionId]);
|
|
72
|
+
|
|
45
73
|
const handleSend = useCallback(() => {
|
|
46
74
|
const trimmed = message.trim();
|
|
47
75
|
const hasContent = trimmed || attachedImages.length > 0;
|
|
@@ -93,20 +121,20 @@ export function ClaudePanelInput({
|
|
|
93
121
|
|
|
94
122
|
return (
|
|
95
123
|
<div
|
|
96
|
-
className="border-t border-zinc-200 bg-zinc-50 p-
|
|
124
|
+
className="border-t border-zinc-200 bg-zinc-50 p-4"
|
|
97
125
|
data-testid="claude-panel-input"
|
|
98
126
|
>
|
|
99
127
|
<div
|
|
100
128
|
className={`
|
|
101
|
-
relative rounded-lg border transition-
|
|
102
|
-
${isFocused ? 'border-
|
|
129
|
+
relative rounded-lg border-2 transition-[border-color] duration-200 ease-out
|
|
130
|
+
${isFocused ? 'border-[#819D9F] bg-white' : 'border-zinc-300 bg-white'}
|
|
103
131
|
${disabled ? 'opacity-50 cursor-not-allowed' : ''}
|
|
104
132
|
`}
|
|
105
133
|
>
|
|
106
134
|
|
|
107
135
|
{/* Thumbnail preview section */}
|
|
108
136
|
{attachedImages.length > 0 && (
|
|
109
|
-
<div className="flex flex-wrap gap-
|
|
137
|
+
<div className="flex flex-wrap gap-3 px-4 pt-3" data-testid="image-preview-section">
|
|
110
138
|
{attachedImages.map(image => (
|
|
111
139
|
<div
|
|
112
140
|
key={image.id}
|
|
@@ -116,12 +144,12 @@ export function ClaudePanelInput({
|
|
|
116
144
|
<img
|
|
117
145
|
src={image.dataUrl}
|
|
118
146
|
alt={image.name}
|
|
119
|
-
className="w-16 h-16 object-cover rounded
|
|
147
|
+
className="w-16 h-16 object-cover rounded"
|
|
120
148
|
/>
|
|
121
149
|
<button
|
|
122
150
|
type="button"
|
|
123
151
|
onClick={() => removeImage(image.id)}
|
|
124
|
-
className="absolute -top-1.5 -right-1.5 w-5 h-5 bg-zinc-700 hover:bg-zinc-900 text-white rounded-full flex items-center justify-center text-xs opacity-0 group-hover:opacity-100 transition-opacity"
|
|
152
|
+
className="absolute -top-1.5 -right-1.5 w-5 h-5 bg-zinc-700 hover:bg-zinc-900 text-white rounded-full flex items-center justify-center text-xs opacity-0 group-hover:opacity-100 transition-opacity duration-200 ease-out"
|
|
125
153
|
data-testid="remove-image-button"
|
|
126
154
|
aria-label={`Remove ${image.name}`}
|
|
127
155
|
>
|
|
@@ -143,10 +171,10 @@ export function ClaudePanelInput({
|
|
|
143
171
|
disabled={disabled}
|
|
144
172
|
rows={1}
|
|
145
173
|
className={`
|
|
146
|
-
w-full resize-none bg-transparent px-
|
|
174
|
+
w-full resize-none bg-transparent px-4 py-3 text-base text-zinc-900
|
|
147
175
|
placeholder:text-zinc-400 focus:outline-none
|
|
148
176
|
${isExpanded ? 'min-h-[80px]' : 'min-h-[40px]'}
|
|
149
|
-
transition-
|
|
177
|
+
transition-[min-height] duration-200 ease-out
|
|
150
178
|
`}
|
|
151
179
|
style={{ maxHeight: '200px' }}
|
|
152
180
|
data-testid="claude-input-textarea"
|
|
@@ -155,12 +183,12 @@ export function ClaudePanelInput({
|
|
|
155
183
|
{/* Footer with character count and hints - only show when expanded */}
|
|
156
184
|
<AnimatePresence>
|
|
157
185
|
{isExpanded && (
|
|
158
|
-
<
|
|
186
|
+
<m.div
|
|
159
187
|
initial={{ opacity: 0, height: 0 }}
|
|
160
188
|
animate={{ opacity: 1, height: 'auto' }}
|
|
161
189
|
exit={{ opacity: 0, height: 0 }}
|
|
162
190
|
transition={{ duration: 0.15 }}
|
|
163
|
-
className="flex items-center justify-between px-
|
|
191
|
+
className="flex items-center justify-between px-4 pb-3 text-xs text-zinc-500"
|
|
164
192
|
>
|
|
165
193
|
<div className="flex items-center gap-3" data-testid="keyboard-hints">
|
|
166
194
|
<span>
|
|
@@ -177,14 +205,14 @@ export function ClaudePanelInput({
|
|
|
177
205
|
<span>
|
|
178
206
|
<kbd className="px-1.5 py-0.5 rounded bg-zinc-200 text-zinc-600 font-mono text-[10px]">Esc</kbd>
|
|
179
207
|
<kbd className="px-1.5 py-0.5 rounded bg-zinc-200 text-zinc-600 font-mono text-[10px] ml-0.5">Esc</kbd>
|
|
180
|
-
{' '}to
|
|
208
|
+
{' '}to interrupt
|
|
181
209
|
</span>
|
|
182
210
|
)}
|
|
183
211
|
</div>
|
|
184
212
|
<span data-testid="character-count">
|
|
185
213
|
{characterCount > 0 ? `${characterCount} chars` : ''}
|
|
186
214
|
</span>
|
|
187
|
-
</
|
|
215
|
+
</m.div>
|
|
188
216
|
)}
|
|
189
217
|
</AnimatePresence>
|
|
190
218
|
</div>
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
1
|
import { useState, useEffect, useRef } from 'react';
|
|
4
|
-
import
|
|
2
|
+
import { Button } from '@/components/ui/Button';
|
|
5
3
|
|
|
6
4
|
type ConnectState = 'idle' | 'waiting' | 'success' | 'error';
|
|
7
5
|
|
|
@@ -89,15 +87,14 @@ export function ConnectClaudeScreen({ onConnect, onCheckAuth }: ConnectClaudeScr
|
|
|
89
87
|
|
|
90
88
|
return (
|
|
91
89
|
<div className="flex flex-col items-center justify-center min-h-screen bg-white dark:bg-zinc-900 p-8">
|
|
92
|
-
<div className="max-w-md w-full space-y-
|
|
90
|
+
<div className="max-w-md w-full space-y-10">
|
|
93
91
|
{/* Logo */}
|
|
94
|
-
<div className="flex flex-col items-center space-y-
|
|
95
|
-
<
|
|
92
|
+
<div className="flex flex-col items-center space-y-6">
|
|
93
|
+
<img
|
|
96
94
|
src="/jettypod_wordmark.png"
|
|
97
95
|
alt="JettyPod"
|
|
98
96
|
width={160}
|
|
99
97
|
height={40}
|
|
100
|
-
priority
|
|
101
98
|
/>
|
|
102
99
|
<h1 className="text-2xl font-semibold text-zinc-900 dark:text-zinc-100 text-center">
|
|
103
100
|
Connect Claude Code
|
|
@@ -111,7 +108,7 @@ export function ConnectClaudeScreen({ onConnect, onCheckAuth }: ConnectClaudeScr
|
|
|
111
108
|
{/* Error Banner */}
|
|
112
109
|
{errorMessage && (
|
|
113
110
|
<div
|
|
114
|
-
className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-700 dark:text-red-400 px-
|
|
111
|
+
className="bg-red-50 dark:bg-red-900/20 border-2 border-red-200 dark:border-red-800 text-red-700 dark:text-red-400 px-5 py-4 rounded-xl text-base"
|
|
115
112
|
data-testid="connect-error"
|
|
116
113
|
>
|
|
117
114
|
{errorMessage}
|
|
@@ -119,7 +116,7 @@ export function ConnectClaudeScreen({ onConnect, onCheckAuth }: ConnectClaudeScr
|
|
|
119
116
|
)}
|
|
120
117
|
|
|
121
118
|
{/* Progress Stepper */}
|
|
122
|
-
<div className="flex flex-col gap-
|
|
119
|
+
<div className="flex flex-col gap-5">
|
|
123
120
|
{steps.map((label, i) => {
|
|
124
121
|
const stepNum = i + 1;
|
|
125
122
|
const isDone = completedSteps.includes(stepNum);
|
|
@@ -131,14 +128,14 @@ export function ConnectClaudeScreen({ onConnect, onCheckAuth }: ConnectClaudeScr
|
|
|
131
128
|
return (
|
|
132
129
|
<div
|
|
133
130
|
key={stepNum}
|
|
134
|
-
className={`flex items-center gap-
|
|
131
|
+
className={`flex items-center gap-4 text-base ${
|
|
135
132
|
isDone ? 'text-zinc-900 dark:text-zinc-100' :
|
|
136
133
|
isActive ? 'text-zinc-900 dark:text-zinc-100 font-medium' :
|
|
137
134
|
'text-zinc-400 dark:text-zinc-500'
|
|
138
135
|
}`}
|
|
139
136
|
>
|
|
140
137
|
<span
|
|
141
|
-
className={`w-7 h-7 rounded-full flex items-center justify-center text-
|
|
138
|
+
className={`w-7 h-7 rounded-full flex items-center justify-center text-base font-semibold flex-shrink-0 transition-colors duration-200 ease-out ${
|
|
142
139
|
isDone ? 'bg-green-400 text-white' :
|
|
143
140
|
isActive ? 'bg-[#c8d9da] text-[#3d4d4e]' :
|
|
144
141
|
'bg-zinc-200 dark:bg-zinc-700 text-zinc-400 dark:text-zinc-500'
|
|
@@ -153,7 +150,7 @@ export function ConnectClaudeScreen({ onConnect, onCheckAuth }: ConnectClaudeScr
|
|
|
153
150
|
</div>
|
|
154
151
|
|
|
155
152
|
{/* Connect Button */}
|
|
156
|
-
<div className="pt-
|
|
153
|
+
<div className="pt-6">
|
|
157
154
|
{state === 'success' ? (
|
|
158
155
|
<div
|
|
159
156
|
className="w-full py-3 px-6 rounded-xl font-medium text-center bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-400"
|
|
@@ -162,29 +159,15 @@ export function ConnectClaudeScreen({ onConnect, onCheckAuth }: ConnectClaudeScr
|
|
|
162
159
|
✓ Connected!
|
|
163
160
|
</div>
|
|
164
161
|
) : (
|
|
165
|
-
<
|
|
162
|
+
<Button
|
|
166
163
|
onClick={handleConnect}
|
|
167
164
|
disabled={state === 'waiting'}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
cursor: state === 'waiting' ? 'default' : 'pointer',
|
|
171
|
-
background: 'linear-gradient(145deg, #ffffff 0%, #faf9f7 10%, #f0f4f4 35%, #c8d9da 55%, #819D9F 90%)',
|
|
172
|
-
color: '#3d4d4e',
|
|
173
|
-
boxShadow: `
|
|
174
|
-
0 1px 1px rgba(0, 0, 0, 0.02),
|
|
175
|
-
0 2px 4px rgba(0, 0, 0, 0.03),
|
|
176
|
-
0 6px 12px rgba(0, 0, 0, 0.05),
|
|
177
|
-
0 12px 24px rgba(0, 0, 0, 0.06),
|
|
178
|
-
0 20px 40px rgba(129, 157, 159, 0.2),
|
|
179
|
-
0 32px 64px rgba(129, 157, 159, 0.18),
|
|
180
|
-
inset 0 2px 4px rgba(255, 255, 255, 1),
|
|
181
|
-
inset 0 -2px 4px rgba(129, 157, 159, 0.05)
|
|
182
|
-
`,
|
|
183
|
-
}}
|
|
165
|
+
size="lg"
|
|
166
|
+
fullWidth
|
|
184
167
|
data-testid="connect-claude-button"
|
|
185
168
|
>
|
|
186
169
|
{state === 'waiting' ? (
|
|
187
|
-
<span className="flex items-center justify-center gap-
|
|
170
|
+
<span className="flex items-center justify-center gap-3">
|
|
188
171
|
<span className="inline-block w-4 h-4 border-2 border-[#c8d9da] border-t-[#3d4d4e] rounded-full animate-spin" />
|
|
189
172
|
Waiting for login...
|
|
190
173
|
</span>
|
|
@@ -193,20 +176,20 @@ export function ConnectClaudeScreen({ onConnect, onCheckAuth }: ConnectClaudeScr
|
|
|
193
176
|
) : (
|
|
194
177
|
'Connect Claude Code'
|
|
195
178
|
)}
|
|
196
|
-
</
|
|
179
|
+
</Button>
|
|
197
180
|
)}
|
|
198
181
|
</div>
|
|
199
182
|
|
|
200
183
|
{/* Status text */}
|
|
201
184
|
{state === 'waiting' && (
|
|
202
|
-
<p className="text-
|
|
185
|
+
<p className="text-base text-zinc-400 dark:text-zinc-500 text-center">
|
|
203
186
|
A browser window should have opened. Complete the sign-in there.
|
|
204
187
|
</p>
|
|
205
188
|
)}
|
|
206
189
|
|
|
207
190
|
{/* Info Section */}
|
|
208
|
-
<div className="pt-
|
|
209
|
-
<div className="border border-zinc-200 dark:border-zinc-700 rounded-
|
|
191
|
+
<div className="pt-10 space-y-4">
|
|
192
|
+
<div className="border-2 border-zinc-200 dark:border-zinc-700 rounded-xl p-6 text-zinc-500 dark:text-zinc-400 text-base">
|
|
210
193
|
<p>
|
|
211
194
|
<strong className="text-zinc-700 dark:text-zinc-300">Why do I need this?</strong>
|
|
212
195
|
</p>
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
'use client';
|
|
2
1
|
|
|
3
2
|
import { useState } from 'react';
|
|
4
3
|
|
|
@@ -51,15 +50,15 @@ export function CopyableId({ id, title, type, size = 'sm' }: CopyableIdProps) {
|
|
|
51
50
|
};
|
|
52
51
|
|
|
53
52
|
const sizeClasses = size === 'md'
|
|
54
|
-
? 'text-
|
|
55
|
-
: 'text-
|
|
53
|
+
? 'text-base px-1.5 py-1'
|
|
54
|
+
: 'text-sm px-1 py-0.5';
|
|
56
55
|
|
|
57
56
|
const iconClasses = size === 'md' ? 'w-4 h-4' : 'w-3 h-3';
|
|
58
57
|
|
|
59
58
|
return (
|
|
60
59
|
<button
|
|
61
60
|
onClick={handleCopy}
|
|
62
|
-
className={`flex items-center gap-1 text-zinc-400 font-mono -mx-1 rounded cursor-pointer hover:bg-zinc-100 dark:hover:bg-zinc-700 active:scale-95 transition-
|
|
61
|
+
className={`flex items-center gap-1 text-zinc-400 font-mono -mx-1 rounded cursor-pointer hover:bg-zinc-100 dark:hover:bg-zinc-700 active:scale-95 transition-[color,background-color] duration-200 ease-out ${sizeClasses}`}
|
|
63
62
|
title={`Copy: #${id} ${title} (${type})`}
|
|
64
63
|
>
|
|
65
64
|
<span>#{id}</span>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useNavigate } from 'react-router-dom';
|
|
3
|
+
import { Button } from '@/components/ui/Button';
|
|
4
|
+
import { Input } from '@/components/ui/Input';
|
|
5
|
+
import { dataBridge } from '@/lib/data-bridge';
|
|
6
|
+
|
|
7
|
+
interface DetailReviewActionsProps {
|
|
8
|
+
workItemId: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function DetailReviewActions({ workItemId }: DetailReviewActionsProps) {
|
|
12
|
+
const navigate = useNavigate();
|
|
13
|
+
const [showRejectInput, setShowRejectInput] = useState(false);
|
|
14
|
+
const [rejectReason, setRejectReason] = useState('');
|
|
15
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
16
|
+
|
|
17
|
+
const handleAccept = async () => {
|
|
18
|
+
setIsSubmitting(true);
|
|
19
|
+
try {
|
|
20
|
+
await dataBridge.updateStatus(workItemId, 'done');
|
|
21
|
+
navigate('/');
|
|
22
|
+
} catch {
|
|
23
|
+
setIsSubmitting(false);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const handleRejectConfirm = async () => {
|
|
28
|
+
if (!rejectReason.trim()) return;
|
|
29
|
+
setIsSubmitting(true);
|
|
30
|
+
try {
|
|
31
|
+
await dataBridge.updateStatus(workItemId, 'in_progress', rejectReason.trim());
|
|
32
|
+
navigate(`/?rejected=${workItemId}&reason=${encodeURIComponent(rejectReason.trim())}`);
|
|
33
|
+
} catch {
|
|
34
|
+
setIsSubmitting(false);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
if (showRejectInput) {
|
|
39
|
+
return (
|
|
40
|
+
<div className="flex items-center gap-2" data-testid="detail-review-reject-area">
|
|
41
|
+
<Input
|
|
42
|
+
type="text"
|
|
43
|
+
value={rejectReason}
|
|
44
|
+
onChange={(e) => setRejectReason(e.target.value)}
|
|
45
|
+
onKeyDown={(e) => {
|
|
46
|
+
if (e.key === 'Enter' && rejectReason.trim()) handleRejectConfirm();
|
|
47
|
+
if (e.key === 'Escape') {
|
|
48
|
+
setShowRejectInput(false);
|
|
49
|
+
setRejectReason('');
|
|
50
|
+
}
|
|
51
|
+
}}
|
|
52
|
+
placeholder="Rejection reason..."
|
|
53
|
+
size="sm"
|
|
54
|
+
error
|
|
55
|
+
autoFocus
|
|
56
|
+
data-testid="detail-review-reject-input"
|
|
57
|
+
/>
|
|
58
|
+
<Button
|
|
59
|
+
onClick={handleRejectConfirm}
|
|
60
|
+
disabled={!rejectReason.trim()}
|
|
61
|
+
loading={isSubmitting}
|
|
62
|
+
variant="destructive"
|
|
63
|
+
size="sm"
|
|
64
|
+
data-testid="detail-review-reject-confirm"
|
|
65
|
+
>
|
|
66
|
+
Reject
|
|
67
|
+
</Button>
|
|
68
|
+
<Button
|
|
69
|
+
onClick={() => { setShowRejectInput(false); setRejectReason(''); }}
|
|
70
|
+
variant="ghost"
|
|
71
|
+
size="sm"
|
|
72
|
+
data-testid="detail-review-reject-cancel"
|
|
73
|
+
>
|
|
74
|
+
Cancel
|
|
75
|
+
</Button>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div className="flex items-center gap-2" data-testid="detail-review-actions">
|
|
82
|
+
<Button
|
|
83
|
+
onClick={handleAccept}
|
|
84
|
+
loading={isSubmitting}
|
|
85
|
+
size="sm"
|
|
86
|
+
data-testid="detail-review-accept"
|
|
87
|
+
>
|
|
88
|
+
Accept
|
|
89
|
+
</Button>
|
|
90
|
+
<Button
|
|
91
|
+
onClick={() => setShowRejectInput(true)}
|
|
92
|
+
variant="secondary"
|
|
93
|
+
size="sm"
|
|
94
|
+
data-testid="detail-review-reject"
|
|
95
|
+
>
|
|
96
|
+
Reject
|
|
97
|
+
</Button>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}
|