jettypod 4.4.120 → 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 +2 -1
- 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 +54 -49
- package/apps/dashboard/app/demo/gates/page.tsx +3 -5
- package/apps/dashboard/app/design-system/page.tsx +1 -1
- package/apps/dashboard/app/globals.css +74 -2
- package/apps/dashboard/app/install-claude/page.tsx +3 -5
- package/apps/dashboard/app/login/page.tsx +17 -20
- package/apps/dashboard/app/page.tsx +101 -48
- package/apps/dashboard/app/settings/page.tsx +60 -12
- package/apps/dashboard/app/signup/page.tsx +14 -17
- 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 +12 -15
- package/apps/dashboard/app/work/[id]/page.tsx +90 -75
- package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
- package/apps/dashboard/components/AppShell.tsx +70 -61
- package/apps/dashboard/components/CardMenu.tsx +0 -1
- package/apps/dashboard/components/ClaudePanel.tsx +541 -283
- package/apps/dashboard/components/ClaudePanelInput.tsx +23 -4
- package/apps/dashboard/components/ConnectClaudeScreen.tsx +1 -5
- package/apps/dashboard/components/CopyableId.tsx +1 -2
- package/apps/dashboard/components/DetailReviewActions.tsx +11 -20
- package/apps/dashboard/components/DragContext.tsx +132 -62
- package/apps/dashboard/components/DraggableCard.tsx +3 -5
- package/apps/dashboard/components/DropZone.tsx +5 -6
- package/apps/dashboard/components/EditableDetailDescription.tsx +6 -12
- package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
- package/apps/dashboard/components/EditableTitle.tsx +0 -1
- package/apps/dashboard/components/ElapsedTimer.tsx +15 -3
- package/apps/dashboard/components/EpicGroup.tsx +100 -70
- package/apps/dashboard/components/GateCard.tsx +0 -1
- package/apps/dashboard/components/GateChoiceCard.tsx +1 -2
- package/apps/dashboard/components/InstallClaudeScreen.tsx +1 -5
- package/apps/dashboard/components/JettyLoader.tsx +0 -1
- package/apps/dashboard/components/KanbanBoard.tsx +319 -173
- package/apps/dashboard/components/KanbanCard.tsx +341 -107
- package/apps/dashboard/components/LazyCard.tsx +62 -0
- package/apps/dashboard/components/LazyMarkdown.tsx +0 -1
- package/apps/dashboard/components/MainNav.tsx +24 -25
- package/apps/dashboard/components/MessageBlock.tsx +93 -16
- package/apps/dashboard/components/ModeStartCard.tsx +0 -1
- package/apps/dashboard/components/OnboardingWelcome.tsx +0 -1
- package/apps/dashboard/components/PlaceholderCard.tsx +0 -1
- package/apps/dashboard/components/ProjectSwitcher.tsx +20 -20
- package/apps/dashboard/components/PrototypeTimeline.tsx +47 -26
- package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +308 -223
- package/apps/dashboard/components/RealTimeTestsWrapper.tsx +303 -160
- package/apps/dashboard/components/ReviewFooter.tsx +12 -14
- package/apps/dashboard/components/SessionList.tsx +0 -1
- package/apps/dashboard/components/SubscribeContent.tsx +40 -11
- package/apps/dashboard/components/TestTree.tsx +1 -2
- package/apps/dashboard/components/TipCard.tsx +2 -4
- package/apps/dashboard/components/Toast.tsx +0 -1
- package/apps/dashboard/components/TypeIcon.tsx +7 -8
- package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
- package/apps/dashboard/components/WaveCompletionAnimation.tsx +5 -17
- package/apps/dashboard/components/WelcomeScreen.tsx +2 -6
- package/apps/dashboard/components/WorkItemHeader.tsx +0 -1
- package/apps/dashboard/components/WorkItemTree.tsx +2 -4
- package/apps/dashboard/components/settings/AccountSection.tsx +27 -13
- 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 +20 -73
- package/apps/dashboard/components/settings/GeneralSection.tsx +137 -26
- package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
- package/apps/dashboard/components/settings/SettingsLayout.tsx +0 -1
- package/apps/dashboard/components/ui/Button.tsx +1 -1
- package/apps/dashboard/components/ui/Input.tsx +1 -1
- package/apps/dashboard/components.json +1 -1
- package/apps/dashboard/contexts/ClaudeSessionContext.tsx +611 -358
- package/apps/dashboard/contexts/ConnectionStatusContext.tsx +0 -1
- package/apps/dashboard/contexts/UsageContext.tsx +62 -31
- package/apps/dashboard/dev.sh +35 -0
- package/apps/dashboard/eslint.config.mjs +9 -9
- package/apps/dashboard/hooks/useWebSocket.ts +138 -83
- package/apps/dashboard/index.html +73 -0
- package/apps/dashboard/lib/data-bridge.ts +722 -0
- package/apps/dashboard/lib/db.ts +69 -1302
- 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 +226 -26
- package/apps/dashboard/lib/proof-run.ts +495 -0
- package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
- 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 +253 -122
- 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 +3 -3
- package/apps/dashboard/next-env.d.ts +1 -1
- package/apps/dashboard/package.json +21 -33
- package/apps/dashboard/public/bug-icon.png +0 -0
- package/apps/dashboard/public/buoy-icon.png +0 -0
- package/apps/dashboard/public/in-flight-seagull.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/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 +167 -30
- 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/jettypod.js +96 -4
- package/lib/bdd-preflight.js +96 -0
- package/lib/merge-lock.js +111 -253
- package/lib/migrations/030-rejection-round-columns.js +54 -0
- package/lib/migrations/031-session-isolation-index.js +17 -0
- package/lib/work-commands/index.js +58 -16
- package/lib/work-tracking/index.js +108 -8
- package/package.json +1 -1
- package/skills-templates/bug-mode/SKILL.md +43 -1
- package/skills-templates/chore-mode/SKILL.md +40 -1
- package/skills-templates/design-system-selection/SKILL.md +273 -0
- package/skills-templates/epic-planning/SKILL.md +14 -0
- package/skills-templates/feature-planning/SKILL.md +90 -1
- package/skills-templates/production-mode/SKILL.md +20 -0
- package/skills-templates/simple-improvement/SKILL.md +39 -2
- package/skills-templates/speed-mode/SKILL.md +10 -15
- package/skills-templates/stable-mode/SKILL.md +47 -0
- package/apps/dashboard/README.md +0 -36
- package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -446
- package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -280
- 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 -525
- 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]/route.ts +0 -35
- package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -63
- package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
- package/apps/dashboard/app/layout.tsx +0 -55
- package/apps/dashboard/components/UpgradeBanner.tsx +0 -30
- package/apps/dashboard/electron/ipc-handlers.js +0 -1026
- package/apps/dashboard/electron/main.js +0 -2306
- package/apps/dashboard/electron/preload.js +0 -125
- package/apps/dashboard/electron/session-manager.js +0 -163
- package/apps/dashboard/electron-builder.config.js +0 -357
- package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
- package/apps/dashboard/lib/backlog-parser.ts +0 -50
- package/apps/dashboard/lib/claude-process-manager.ts +0 -529
- package/apps/dashboard/lib/db-bridge.ts +0 -283
- 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 -66
- package/apps/dashboard/postcss.config.mjs +0 -7
- package/apps/dashboard/public/bug-icon.svg +0 -9
- package/apps/dashboard/public/buoy-icon.svg +0 -9
- package/apps/dashboard/public/file.svg +0 -1
- package/apps/dashboard/public/globe.svg +0 -1
- package/apps/dashboard/public/in-flight-seagull.svg +0 -9
- package/apps/dashboard/public/next.svg +0 -1
- package/apps/dashboard/public/pier-icon.svg +0 -14
- package/apps/dashboard/public/star-icon.svg +0 -9
- package/apps/dashboard/public/vercel.svg +0 -1
- package/apps/dashboard/public/window.svg +0 -1
- package/apps/dashboard/public/wrench-icon.svg +0 -9
- package/apps/dashboard/scripts/download-node.js +0 -104
- package/apps/dashboard/scripts/upload-to-r2.js +0 -89
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
4
|
-
import Link from 'next/link';
|
|
5
|
-
import { AnimatePresence } from 'framer-motion';
|
|
1
|
+
import { useCallback, useRef, useEffect, useState, memo } from 'react';
|
|
2
|
+
import { Link } from 'react-router-dom';
|
|
6
3
|
import { useDroppable } from '@dnd-kit/core';
|
|
7
4
|
import type { WorkItem, InFlightItem } from '@/lib/db';
|
|
8
|
-
import type { Session } from '../contexts/ClaudeSessionContext';
|
|
9
5
|
import { KanbanCard } from './KanbanCard';
|
|
10
6
|
import { useDragContext } from './DragContext';
|
|
11
7
|
import { DraggableCard } from './DraggableCard';
|
|
12
|
-
import { PlaceholderCard } from './PlaceholderCard';
|
|
13
8
|
import { TypeIcon } from './TypeIcon';
|
|
14
9
|
|
|
15
10
|
// Safe bounds for display_order to prevent overflow
|
|
@@ -31,7 +26,7 @@ export interface EpicGroupProps {
|
|
|
31
26
|
onEpicAssign?: (id: number, epicId: number | null) => Promise<void>;
|
|
32
27
|
onOrderChange?: (id: number, newOrder: number) => Promise<void>;
|
|
33
28
|
onTriggerClaude?: (id: number, title: string, type: string, conversational?: boolean, description?: string | null) => void;
|
|
34
|
-
|
|
29
|
+
activeSessionIds?: Set<string>;
|
|
35
30
|
onOpenSession?: (id: string) => void;
|
|
36
31
|
onCloseSession?: (id: string) => void;
|
|
37
32
|
onError?: (message: string) => void;
|
|
@@ -41,19 +36,9 @@ export interface EpicGroupProps {
|
|
|
41
36
|
onAnimationComplete?: () => void;
|
|
42
37
|
}
|
|
43
38
|
|
|
44
|
-
export function EpicGroup({ epicId, epicTitle, items, isInFlight = false, inFlightItems, isDraggable = true, onTitleSave, onStatusChange, onReject, onRestart, onEpicAssign, onOrderChange, onTriggerClaude,
|
|
39
|
+
export const EpicGroup = memo(function EpicGroup({ epicId, epicTitle, items, isInFlight = false, inFlightItems, isDraggable = true, onTitleSave, onStatusChange, onReject, onRestart, onEpicAssign, onOrderChange, onTriggerClaude, activeSessionIds, onOpenSession, onCloseSession, onError, usageAllowed = true, animatingItemId, onAnimationComplete }: EpicGroupProps) {
|
|
45
40
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
46
|
-
const { isDragging, draggedItem, activeEpicZone, activeDropZone, registerEpicDropZone, unregisterEpicDropZone, getCardPositions } = useDragContext();
|
|
47
|
-
|
|
48
|
-
// Local pointer tracking - only this component needs pointer Y for insertion preview.
|
|
49
|
-
// Using local state avoids re-rendering every context consumer at 60fps.
|
|
50
|
-
const [pointerY, setPointerY] = useState(0);
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
if (!isDragging) return;
|
|
53
|
-
const onPointerMove = (e: PointerEvent) => { setPointerY(e.clientY); };
|
|
54
|
-
window.addEventListener('pointermove', onPointerMove);
|
|
55
|
-
return () => window.removeEventListener('pointermove', onPointerMove);
|
|
56
|
-
}, [isDragging]);
|
|
41
|
+
const { isDragging, draggedItem, activeEpicZone, activeDropZone, registerEpicDropZone, unregisterEpicDropZone, getCardPositions, pointerPositionRef } = useDragContext();
|
|
57
42
|
|
|
58
43
|
// Use @dnd-kit's useDroppable for epic zone collision detection
|
|
59
44
|
const zoneId = epicId !== null ? `epic-${epicId}` : undefined;
|
|
@@ -122,19 +107,20 @@ export function EpicGroup({ epicId, epicTitle, items, isInFlight = false, inFlig
|
|
|
122
107
|
const itemMap = new Map(currentItems.map(item => [item.id, item]));
|
|
123
108
|
const visualOrder = cardPositions.map(pos => itemMap.get(pos.id)!).filter(Boolean);
|
|
124
109
|
|
|
125
|
-
// Calculate proper midpoint display_order between surrounding items
|
|
110
|
+
// Calculate proper midpoint display_order between surrounding items.
|
|
111
|
+
// Fallback uses id * INCREMENT to match the sort comparator and give proper gaps.
|
|
126
112
|
let newOrder: number;
|
|
127
113
|
if (visualOrder.length === 0) {
|
|
128
114
|
newOrder = DISPLAY_ORDER_INCREMENT;
|
|
129
115
|
} else if (insertIndex === 0) {
|
|
130
|
-
const firstOrder = visualOrder[0].display_order ?? visualOrder[0].id;
|
|
116
|
+
const firstOrder = visualOrder[0].display_order ?? visualOrder[0].id * DISPLAY_ORDER_INCREMENT;
|
|
131
117
|
newOrder = firstOrder - DISPLAY_ORDER_INCREMENT;
|
|
132
118
|
} else if (insertIndex >= visualOrder.length) {
|
|
133
|
-
const lastOrder = visualOrder[visualOrder.length - 1].display_order ?? visualOrder[visualOrder.length - 1].id;
|
|
119
|
+
const lastOrder = visualOrder[visualOrder.length - 1].display_order ?? visualOrder[visualOrder.length - 1].id * DISPLAY_ORDER_INCREMENT;
|
|
134
120
|
newOrder = lastOrder + DISPLAY_ORDER_INCREMENT;
|
|
135
121
|
} else {
|
|
136
|
-
const before = visualOrder[insertIndex - 1].display_order ?? visualOrder[insertIndex - 1].id;
|
|
137
|
-
const after = visualOrder[insertIndex].display_order ?? visualOrder[insertIndex].id;
|
|
122
|
+
const before = visualOrder[insertIndex - 1].display_order ?? visualOrder[insertIndex - 1].id * DISPLAY_ORDER_INCREMENT;
|
|
123
|
+
const after = visualOrder[insertIndex].display_order ?? visualOrder[insertIndex].id * DISPLAY_ORDER_INCREMENT;
|
|
138
124
|
newOrder = Math.floor((before + after) / 2);
|
|
139
125
|
}
|
|
140
126
|
|
|
@@ -190,31 +176,62 @@ export function EpicGroup({ epicId, epicTitle, items, isInFlight = false, inFlig
|
|
|
190
176
|
// Show reorder for ungrouped section when dragging an ungrouped card and cursor is over it
|
|
191
177
|
const showUngroupedReorder = isOverUngroupedSection && isDragging && draggedItemEpicId === null;
|
|
192
178
|
|
|
193
|
-
//
|
|
179
|
+
// Insertion preview — tracks pointer position via rAF loop so the line
|
|
180
|
+
// moves continuously even when DragContext change guards suppress re-renders.
|
|
194
181
|
const showPreview = (showReorderHighlight || showRemoveFromEpicZone || showHighlight || showUngroupedReorder) && draggedItem;
|
|
195
|
-
|
|
182
|
+
const [insertAfterItemId, setInsertAfterItemId] = useState<number | null | undefined>(undefined);
|
|
196
183
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
.map(pos => ({
|
|
203
|
-
id: pos.id,
|
|
204
|
-
midY: (pos.rect.top + pos.rect.bottom) / 2,
|
|
205
|
-
}))
|
|
206
|
-
.sort((a, b) => a.midY - b.midY);
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
if (!showPreview || !draggedItem) {
|
|
186
|
+
setInsertAfterItemId(undefined);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
207
189
|
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
190
|
+
// Build item ID set once — items array is stable during a drag
|
|
191
|
+
const itemIds = new Set(items.map(item => item.id));
|
|
192
|
+
const dragId = draggedItem.id;
|
|
193
|
+
|
|
194
|
+
let rafId: number;
|
|
195
|
+
let lastTickTime = 0;
|
|
196
|
+
// Reusable array to avoid allocations per frame
|
|
197
|
+
const groupPositions: Array<{ id: number; midY: number }> = [];
|
|
198
|
+
|
|
199
|
+
const tick = () => {
|
|
200
|
+
const now = performance.now();
|
|
201
|
+
// Throttle to ~100ms — fast enough for smooth preview, avoids layout
|
|
202
|
+
// thrashing on slower hardware (Intel Macs).
|
|
203
|
+
if (now - lastTickTime < 100) {
|
|
204
|
+
rafId = requestAnimationFrame(tick);
|
|
205
|
+
return;
|
|
215
206
|
}
|
|
216
|
-
|
|
217
|
-
|
|
207
|
+
lastTickTime = now;
|
|
208
|
+
|
|
209
|
+
const allPositions = getCardPositions();
|
|
210
|
+
groupPositions.length = 0;
|
|
211
|
+
for (const pos of allPositions) {
|
|
212
|
+
if (itemIds.has(pos.id) && pos.id !== dragId) {
|
|
213
|
+
groupPositions.push({ id: pos.id, midY: (pos.rect.top + pos.rect.bottom) / 2 });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
groupPositions.sort((a, b) => a.midY - b.midY);
|
|
217
|
+
|
|
218
|
+
const currentPointerY = pointerPositionRef.current.y;
|
|
219
|
+
let newInsert: number | null = null;
|
|
220
|
+
for (const pos of groupPositions) {
|
|
221
|
+
if (currentPointerY > pos.midY) {
|
|
222
|
+
newInsert = pos.id;
|
|
223
|
+
} else {
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
setInsertAfterItemId(prev => prev === newInsert ? prev : newInsert);
|
|
229
|
+
rafId = requestAnimationFrame(tick);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
rafId = requestAnimationFrame(tick);
|
|
233
|
+
return () => cancelAnimationFrame(rafId);
|
|
234
|
+
}, [showPreview, draggedItem, items, getCardPositions, pointerPositionRef]);
|
|
218
235
|
|
|
219
236
|
if (items.length === 0 && !showHighlight && !showReorderHighlight && !shouldRenderUngroupedZone) return null;
|
|
220
237
|
|
|
@@ -224,13 +241,13 @@ export function EpicGroup({ epicId, epicTitle, items, isInFlight = false, inFlig
|
|
|
224
241
|
return (
|
|
225
242
|
<div
|
|
226
243
|
ref={setRefs}
|
|
227
|
-
className={`${isStandaloneItem ? 'mb-2' : 'mb-6 p-3 -mx-3'} rounded-lg transition-[color,background-color
|
|
244
|
+
className={`${isStandaloneItem ? 'mb-2' : 'mb-6 p-3 -mx-3'} rounded-lg transition-[color,background-color] duration-200 ease-out ${
|
|
228
245
|
showHighlight
|
|
229
|
-
? 'ring-2 ring-
|
|
246
|
+
? 'ring-2 ring-[#819D9F] bg-[#E8EEEF]/50 dark:bg-[#819D9F]/20'
|
|
230
247
|
: showReorderHighlight
|
|
231
|
-
? 'ring-2 ring-
|
|
248
|
+
? 'ring-2 ring-[#E3D985] bg-[#F9F7E8]/50 dark:bg-[#E3D985]/20'
|
|
232
249
|
: showRemoveFromEpicZone
|
|
233
|
-
? 'ring-2 ring-
|
|
250
|
+
? 'ring-2 ring-[#E57A44] bg-[#FCEEE6]/50 dark:bg-[#E57A44]/20'
|
|
234
251
|
: ''
|
|
235
252
|
}`}
|
|
236
253
|
data-epic-id={epicId}
|
|
@@ -238,7 +255,8 @@ export function EpicGroup({ epicId, epicTitle, items, isInFlight = false, inFlig
|
|
|
238
255
|
{epicTitle && (
|
|
239
256
|
<div className="flex items-center gap-3 mb-3">
|
|
240
257
|
<Link
|
|
241
|
-
|
|
258
|
+
to={`/work/${epicId}`}
|
|
259
|
+
viewTransition
|
|
242
260
|
className="group/epic flex items-center gap-1.5 text-base font-medium text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-300"
|
|
243
261
|
>
|
|
244
262
|
<TypeIcon type="epic" />
|
|
@@ -263,12 +281,12 @@ export function EpicGroup({ epicId, epicTitle, items, isInFlight = false, inFlig
|
|
|
263
281
|
</span>
|
|
264
282
|
)}
|
|
265
283
|
{showHighlight && (
|
|
266
|
-
<span className="text-xs px-2 py-1 rounded bg-
|
|
284
|
+
<span className="text-xs px-2 py-1 rounded bg-[#E8EEEF] text-[#4A6365] dark:bg-[#819D9F]/20 dark:text-[#819D9F]">
|
|
267
285
|
drop to assign
|
|
268
286
|
</span>
|
|
269
287
|
)}
|
|
270
288
|
{showReorderHighlight && (
|
|
271
|
-
<span className="text-xs px-2 py-1 rounded bg-
|
|
289
|
+
<span className="text-xs px-2 py-1 rounded bg-[#F9F7E8] text-[#8B7D2F] dark:bg-[#E3D985]/20 dark:text-[#E3D985]">
|
|
272
290
|
reorder
|
|
273
291
|
</span>
|
|
274
292
|
)}
|
|
@@ -277,27 +295,33 @@ export function EpicGroup({ epicId, epicTitle, items, isInFlight = false, inFlig
|
|
|
277
295
|
{/* Ungrouped section header - shown when dragging from epic */}
|
|
278
296
|
{isUngroupedSection && showRemoveFromEpicZone && items.length === 0 && (
|
|
279
297
|
<div className="flex items-center gap-3 py-4">
|
|
280
|
-
<span className="text-base font-medium text-
|
|
298
|
+
<span className="text-base font-medium text-[#E57A44] dark:text-[#E57A44]">
|
|
281
299
|
Drop here to remove from epic
|
|
282
300
|
</span>
|
|
283
301
|
</div>
|
|
284
302
|
)}
|
|
285
303
|
{isUngroupedSection && items.length > 0 && isDraggable && showRemoveFromEpicZone && (
|
|
286
304
|
<div className="flex items-center gap-3 mb-3">
|
|
287
|
-
<span className="text-xs px-2 py-1 rounded bg-
|
|
305
|
+
<span className="text-xs px-2 py-1 rounded bg-[#FCEEE6] text-[#9E4A1E] dark:bg-[#E57A44]/20 dark:text-[#E57A44]">
|
|
288
306
|
drop to remove from epic
|
|
289
307
|
</span>
|
|
290
308
|
</div>
|
|
291
309
|
)}
|
|
292
310
|
<div className="space-y-3">
|
|
293
|
-
{
|
|
294
|
-
<AnimatePresence>
|
|
295
|
-
{insertAfterItemId === null && (
|
|
296
|
-
<PlaceholderCard key="placeholder-start" />
|
|
297
|
-
)}
|
|
298
|
-
</AnimatePresence>
|
|
299
|
-
{items.map((item) => (
|
|
311
|
+
{items.map((item, index) => (
|
|
300
312
|
<div key={item.id}>
|
|
313
|
+
{/* CSS insertion indicator — replaces AnimatePresence+PlaceholderCard for less overhead */}
|
|
314
|
+
{index === 0 && insertAfterItemId === null && (
|
|
315
|
+
<div
|
|
316
|
+
data-testid="drag-placeholder"
|
|
317
|
+
style={{
|
|
318
|
+
height: 3,
|
|
319
|
+
borderRadius: 2,
|
|
320
|
+
background: 'linear-gradient(90deg, transparent, #819D9F, transparent)',
|
|
321
|
+
margin: '2px 8px 8px',
|
|
322
|
+
}}
|
|
323
|
+
/>
|
|
324
|
+
)}
|
|
301
325
|
<DraggableCard item={item} disabled={!isDraggable}>
|
|
302
326
|
<KanbanCard
|
|
303
327
|
item={item}
|
|
@@ -306,7 +330,7 @@ export function EpicGroup({ epicId, epicTitle, items, isInFlight = false, inFlig
|
|
|
306
330
|
onReject={onReject}
|
|
307
331
|
onRestart={onRestart}
|
|
308
332
|
onTriggerClaude={onTriggerClaude}
|
|
309
|
-
hasActiveSession={
|
|
333
|
+
hasActiveSession={activeSessionIds?.has(String(item.id))}
|
|
310
334
|
onOpenSession={onOpenSession}
|
|
311
335
|
onCloseSession={onCloseSession}
|
|
312
336
|
usageAllowed={usageAllowed}
|
|
@@ -315,15 +339,21 @@ export function EpicGroup({ epicId, epicTitle, items, isInFlight = false, inFlig
|
|
|
315
339
|
isHighlighted={false}
|
|
316
340
|
/>
|
|
317
341
|
</DraggableCard>
|
|
318
|
-
{/*
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
342
|
+
{/* CSS insertion indicator after this card */}
|
|
343
|
+
{insertAfterItemId === item.id && (
|
|
344
|
+
<div
|
|
345
|
+
data-testid="drag-placeholder"
|
|
346
|
+
style={{
|
|
347
|
+
height: 3,
|
|
348
|
+
borderRadius: 2,
|
|
349
|
+
background: 'linear-gradient(90deg, transparent, #819D9F, transparent)',
|
|
350
|
+
margin: '8px 8px 2px',
|
|
351
|
+
}}
|
|
352
|
+
/>
|
|
353
|
+
)}
|
|
324
354
|
</div>
|
|
325
355
|
))}
|
|
326
356
|
</div>
|
|
327
357
|
</div>
|
|
328
358
|
);
|
|
329
|
-
}
|
|
359
|
+
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
'use client';
|
|
2
1
|
|
|
3
2
|
import { useState } from 'react';
|
|
4
3
|
|
|
@@ -58,7 +57,7 @@ export function GateChoiceCard({
|
|
|
58
57
|
onMouseLeave={() => setHoveredId(null)}
|
|
59
58
|
disabled={disabled && !isSelected}
|
|
60
59
|
className={`
|
|
61
|
-
w-full text-left rounded-xl border-2 p-4 transition-[color,background-color,border-color
|
|
60
|
+
w-full text-left rounded-xl border-2 p-4 transition-[color,background-color,border-color] duration-200 ease-out
|
|
62
61
|
${isSelected
|
|
63
62
|
? 'border-[#819D9F] dark:border-[#819D9F] bg-[#e8f0f0] dark:bg-[#819D9F]/20 ring-2 ring-[#819D9F]/30'
|
|
64
63
|
: 'border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 hover:border-zinc-300 dark:hover:border-zinc-600'
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
1
|
import { useState, useEffect } from 'react';
|
|
4
|
-
import Image from 'next/image';
|
|
5
2
|
import { Button } from '@/components/ui/Button';
|
|
6
3
|
|
|
7
4
|
const statusMessages = [
|
|
@@ -73,12 +70,11 @@ export function InstallClaudeScreen({
|
|
|
73
70
|
<div className="max-w-md w-full space-y-10">
|
|
74
71
|
{/* Logo */}
|
|
75
72
|
<div className="flex flex-col items-center space-y-6">
|
|
76
|
-
<
|
|
73
|
+
<img
|
|
77
74
|
src="/jettypod_wordmark.png"
|
|
78
75
|
alt="JettyPod"
|
|
79
76
|
width={160}
|
|
80
77
|
height={40}
|
|
81
|
-
priority
|
|
82
78
|
/>
|
|
83
79
|
{!isInstalling && !isSuccess && (
|
|
84
80
|
<>
|