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
@@ -0,0 +1,214 @@
1
+ 'use client';
2
+
3
+ import { useState, useCallback } from 'react';
4
+ import { m, AnimatePresence, useReducedMotion } from 'framer-motion';
5
+ import type { WorkItem } from '@/lib/db';
6
+ import { shadow } from '@/lib/shadows';
7
+ import { Button } from '@/components/ui/Button';
8
+ import { TypeIcon } from '@/components/TypeIcon';
9
+
10
+ interface OnboardingWelcomeProps {
11
+ onboardingItems: WorkItem[];
12
+ onStartChore: (id: number, title: string) => void;
13
+ }
14
+
15
+ const REVEAL_EASE = [0.22, 1, 0.36, 1] as const;
16
+
17
+ export function OnboardingWelcome({ onboardingItems, onStartChore }: OnboardingWelcomeProps) {
18
+ const prefersReducedMotion = useReducedMotion();
19
+ const [phase, setPhase] = useState<'welcome' | 'transitioning' | 'revealed'>('welcome');
20
+
21
+ const handleLetsGo = useCallback(() => {
22
+ setPhase('transitioning');
23
+ }, []);
24
+
25
+ const handleExitComplete = useCallback(() => {
26
+ setPhase('revealed');
27
+ }, []);
28
+
29
+ const handleStartChore = useCallback((item: WorkItem) => {
30
+ onStartChore(item.id, item.title);
31
+ }, [onStartChore]);
32
+
33
+ const hasItems = onboardingItems.length > 0;
34
+ const isRevealed = phase === 'revealed';
35
+ const dur = prefersReducedMotion ? 0.05 : undefined;
36
+
37
+ return (
38
+ <div
39
+ className={`h-full flex justify-center overflow-hidden relative ${isRevealed ? 'items-start pt-16' : 'items-center pt-0'}`}
40
+ style={{ transition: prefersReducedMotion ? 'none' : 'padding-top 0.8s cubic-bezier(0.22, 1, 0.36, 1)' }}
41
+ >
42
+ <div className="flex items-start gap-12">
43
+ {/* Primary column — starts as single centered column, narrows to left column on reveal */}
44
+ <m.div
45
+ className="shrink-0 flex flex-col"
46
+ animate={{ width: isRevealed ? 340 : 480 }}
47
+ initial={false}
48
+ transition={{ duration: dur ?? 0.8, ease: REVEAL_EASE }}
49
+ >
50
+ {/* Greeting + intro — fade out on "Let's go" */}
51
+ <AnimatePresence onExitComplete={handleExitComplete}>
52
+ {phase === 'welcome' && (
53
+ <>
54
+ <m.div
55
+ key="greeting"
56
+ className="text-base leading-relaxed mb-4"
57
+ initial={{ opacity: 0, y: 10 }}
58
+ animate={{ opacity: 1, y: 0 }}
59
+ exit={{ opacity: 0, y: -5, transition: { duration: dur ?? 0.25 } }}
60
+ transition={{ duration: dur ?? 0.4, delay: prefersReducedMotion ? 0 : 0.2 }}
61
+ >
62
+ Ahoy. Welcome to your new project.
63
+ </m.div>
64
+
65
+ <m.div
66
+ key="intro"
67
+ className="text-base leading-relaxed mb-4"
68
+ initial={{ opacity: 0, y: 10 }}
69
+ animate={{ opacity: 1, y: 0 }}
70
+ exit={{ opacity: 0, y: -5, transition: { duration: dur ?? 0.25 } }}
71
+ transition={{ duration: dur ?? 0.4, delay: prefersReducedMotion ? 0 : 0.6 }}
72
+ >
73
+ Here&rsquo;s what we&rsquo;ll work through:
74
+ </m.div>
75
+ </>
76
+ )}
77
+ </AnimatePresence>
78
+
79
+ {/* Epic label — appears above cards on reveal */}
80
+ <AnimatePresence>
81
+ {isRevealed && (
82
+ <m.div
83
+ key="epic-label"
84
+ className="flex items-center gap-1.5 text-xs font-semibold text-muted-foreground mb-2.5 uppercase tracking-wide"
85
+ initial={{ opacity: 0 }}
86
+ animate={{ opacity: 1 }}
87
+ transition={{ duration: dur ?? 0.3, delay: prefersReducedMotion ? 0 : 0.3 }}
88
+ >
89
+ <TypeIcon type="epic" className="w-4 h-4 inline" />
90
+ <span>Project Planning</span>
91
+ </m.div>
92
+ )}
93
+ </AnimatePresence>
94
+
95
+ {/* Cards — SAME DOM elements throughout, never unmount */}
96
+ {onboardingItems.map((item, i) => {
97
+ const isFirst = i === 0;
98
+ return (
99
+ <m.div
100
+ key={item.id}
101
+ className="bg-white dark:bg-zinc-900 border-2 border-zinc-200 dark:border-zinc-700 rounded-xl px-3.5 py-3 mb-2 flex items-center justify-between"
102
+ style={{
103
+ boxShadow: isRevealed && isFirst
104
+ ? `${shadow.sm}, 0 0 0 3px rgba(129,157,159,0.35)`
105
+ : shadow.sm,
106
+ animation: isRevealed && isFirst ? 'onboarding-pulse-glow 2s ease-in-out infinite' : undefined,
107
+ }}
108
+ initial={{ opacity: 0, y: 10 }}
109
+ animate={{ opacity: 1, y: 0 }}
110
+ transition={{
111
+ duration: dur ?? 0.4,
112
+ delay: prefersReducedMotion ? 0 : 0.8 + i * 0.1,
113
+ }}
114
+ >
115
+ <div className="flex items-center gap-2.5">
116
+ <TypeIcon type="chore" className="w-5 h-5 inline" />
117
+ <span className="text-sm font-medium leading-snug">{item.title}</span>
118
+ </div>
119
+
120
+ {/* Start button — grows in after cards settle */}
121
+ {isRevealed && (
122
+ <m.div
123
+ initial={{ opacity: 0, scale: 0.9 }}
124
+ animate={{ opacity: 1, scale: 1 }}
125
+ transition={{ duration: dur ?? 0.3, delay: prefersReducedMotion ? 0 : 0.5 }}
126
+ >
127
+ {isFirst ? (
128
+ <button
129
+ onClick={() => handleStartChore(item)}
130
+ className="px-3 py-1 text-[11px] font-semibold rounded-sm border-2 border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-muted-foreground cursor-pointer whitespace-nowrap"
131
+ style={{ animation: 'onboarding-pulse-bg 2s ease-in-out infinite' }}
132
+ >
133
+ start
134
+ </button>
135
+ ) : (
136
+ <button
137
+ disabled
138
+ className="px-3 py-1 text-[11px] font-semibold rounded-sm border-2 border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-muted-foreground opacity-40 cursor-default whitespace-nowrap"
139
+ >
140
+ start
141
+ </button>
142
+ )}
143
+ </m.div>
144
+ )}
145
+ </m.div>
146
+ );
147
+ })}
148
+
149
+ {/* Let's go button — fades out with greeting */}
150
+ <AnimatePresence>
151
+ {phase === 'welcome' && (
152
+ <m.div
153
+ key="button"
154
+ className="flex gap-2 mt-2"
155
+ initial={{ opacity: 0 }}
156
+ animate={{ opacity: 1 }}
157
+ exit={{ opacity: 0, transition: { duration: dur ?? 0.15 } }}
158
+ transition={{ duration: dur ?? 0.4, delay: prefersReducedMotion ? 0 : 1.0 + onboardingItems.length * 0.1 }}
159
+ >
160
+ <Button onClick={handleLetsGo} size="sm">
161
+ Let&rsquo;s go
162
+ </Button>
163
+ </m.div>
164
+ )}
165
+ </AnimatePresence>
166
+ </m.div>
167
+
168
+ {/* Conversation column — expands in from right on reveal */}
169
+ {isRevealed && (
170
+ <m.div
171
+ className="shrink-0 overflow-hidden"
172
+ initial={{ width: 0 }}
173
+ animate={{ width: 480 }}
174
+ transition={{ duration: dur ?? 0.8, ease: REVEAL_EASE }}
175
+ >
176
+ <div className="w-[480px] flex flex-col gap-4">
177
+ <m.div
178
+ className="text-base leading-relaxed"
179
+ initial={{ opacity: 0, x: 20 }}
180
+ animate={{ opacity: 1, x: 0 }}
181
+ transition={{ duration: dur ?? 0.5, delay: prefersReducedMotion ? 0 : 0.3 }}
182
+ >
183
+ Each card is a short conversation &mdash; we&rsquo;ll knock them out one at a time.
184
+ </m.div>
185
+
186
+ {hasItems && (
187
+ <m.div
188
+ className="text-sm leading-normal px-3.5 py-2.5 bg-yellow-50 border-2 border-yellow-200 rounded-lg"
189
+ initial={{ opacity: 0, y: 8 }}
190
+ animate={{ opacity: 1, y: 0 }}
191
+ transition={{ duration: dur ?? 0.4, delay: prefersReducedMotion ? 0 : 0.6 }}
192
+ >
193
+ 👈 Click <strong>start</strong> on the first card to begin.
194
+ </m.div>
195
+ )}
196
+ </div>
197
+ </m.div>
198
+ )}
199
+ </div>
200
+
201
+ {/* Keyframe animations for the highlighted card */}
202
+ <style>{`
203
+ @keyframes onboarding-pulse-glow {
204
+ 0%, 100% { box-shadow: ${shadow.sm}, 0 0 0 0px rgba(129,157,159,0); }
205
+ 50% { box-shadow: ${shadow.sm}, 0 0 0 3px rgba(129,157,159,0.35); }
206
+ }
207
+ @keyframes onboarding-pulse-bg {
208
+ 0%, 100% { background: #ffffff; }
209
+ 50% { background: rgba(129,157,159,0.12); border-color: rgba(129,157,159,0.4); color: #3d4d4e; }
210
+ }
211
+ `}</style>
212
+ </div>
213
+ );
214
+ }
@@ -1,30 +1,20 @@
1
1
  'use client';
2
2
 
3
- import { motion } from 'framer-motion';
4
-
5
- interface PlaceholderCardProps {
6
- height: number;
7
- minHeight?: number;
8
- }
9
-
10
- const DEFAULT_MIN_HEIGHT = 40;
11
-
12
- export function PlaceholderCard({ height, minHeight = DEFAULT_MIN_HEIGHT }: PlaceholderCardProps) {
13
- const effectiveHeight = Math.max(height, minHeight);
3
+ import { m } from 'framer-motion';
14
4
 
5
+ export function PlaceholderCard() {
15
6
  return (
16
- <motion.div
7
+ <m.div
17
8
  data-testid="drag-placeholder"
18
- initial={{ opacity: 0, height: 0 }}
19
- animate={{ opacity: 0.6, height: effectiveHeight }}
20
- exit={{ opacity: 0, height: 0 }}
21
- transition={{ duration: 0.2, ease: 'easeOut' }}
9
+ initial={{ opacity: 0, scaleX: 0.3 }}
10
+ animate={{ opacity: 1, scaleX: 1 }}
11
+ exit={{ opacity: 0, scaleX: 0.3 }}
12
+ transition={{ duration: 0.15, ease: 'easeOut' }}
22
13
  style={{
23
- borderRadius: 8,
24
- border: '2px dashed rgba(100, 116, 139, 0.4)',
25
- backgroundColor: 'rgba(100, 116, 139, 0.1)',
26
- marginTop: 8,
27
- marginBottom: 8,
14
+ height: 3,
15
+ borderRadius: 2,
16
+ background: 'linear-gradient(90deg, transparent, #819D9F, transparent)',
17
+ margin: '2px 8px',
28
18
  }}
29
19
  />
30
20
  );
@@ -72,6 +72,19 @@ export function ProjectSwitcher({ projectName }: ProjectSwitcherProps) {
72
72
  }
73
73
  };
74
74
 
75
+ const handleNewProject = async () => {
76
+ setError(null);
77
+ setIsOpen(false);
78
+ try {
79
+ const result = await window.electronAPI!.project.newProject();
80
+ if (result.success) {
81
+ window.location.reload();
82
+ }
83
+ } catch {
84
+ setError('Failed to create new project.');
85
+ }
86
+ };
87
+
75
88
  const handleOpenProject = async () => {
76
89
  setError(null);
77
90
  setIsOpen(false);
@@ -89,7 +102,7 @@ export function ProjectSwitcher({ projectName }: ProjectSwitcherProps) {
89
102
  // Non-Electron: render static pill (no switching possible)
90
103
  if (!isElectron) {
91
104
  return (
92
- <span className="px-2.5 py-1 text-sm bg-zinc-100 text-zinc-600 rounded-full border border-zinc-200 dark:bg-zinc-800 dark:text-zinc-400 dark:border-zinc-700">
105
+ <span className="px-5 py-1.5 text-base bg-zinc-100 text-zinc-600 rounded-full border-2 border-zinc-200 dark:bg-zinc-800 dark:text-zinc-400 dark:border-zinc-700">
93
106
  {projectName}
94
107
  </span>
95
108
  );
@@ -98,26 +111,30 @@ export function ProjectSwitcher({ projectName }: ProjectSwitcherProps) {
98
111
  const dropdownContent = (
99
112
  <div
100
113
  ref={dropdownRef}
101
- className="fixed z-50 bg-white dark:bg-zinc-800 rounded-lg shadow-lg border border-zinc-200 dark:border-zinc-700 py-1 min-w-[200px] max-w-[320px]"
114
+ className="fixed z-50 bg-white dark:bg-zinc-800 rounded-lg shadow-lg py-1.5 min-w-[200px] max-w-[320px]"
102
115
  style={{
103
116
  top: dropdownPosition?.top ?? 0,
104
117
  left: dropdownPosition?.left ?? 0,
105
118
  }}
106
119
  >
107
120
  {/* Current project */}
108
- <div className="px-3 py-2 text-sm text-zinc-400 dark:text-zinc-500 flex items-center gap-2">
121
+ <div className="px-4 py-3 text-base text-zinc-400 dark:text-zinc-500 flex items-center gap-3">
109
122
  <span className="w-1.5 h-1.5 rounded-full bg-green-500 flex-shrink-0" />
110
123
  <span className="truncate">{projectName}</span>
111
124
  </div>
112
125
 
113
126
  {/* Other recent projects */}
127
+ {recentProjects.filter(p => p.name !== projectName).length > 0 && (
128
+ <div className="px-4 pt-3 pb-1.5 text-base font-medium text-zinc-400 dark:text-zinc-500 uppercase tracking-wider">Recent</div>
129
+ )}
114
130
  {recentProjects
115
131
  .filter(p => p.name !== projectName)
132
+ .slice(0, 4)
116
133
  .map((project) => (
117
134
  <button
118
135
  key={project.path}
119
136
  onClick={() => handleProjectClick(project)}
120
- className="w-full px-3 py-2 text-left text-sm text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-700 flex items-center gap-2"
137
+ className="w-full px-4 py-3 text-left text-base text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-700 flex items-center gap-3"
121
138
  >
122
139
  <span className="w-1.5 h-1.5 flex-shrink-0" />
123
140
  <span className="truncate">{project.name}</span>
@@ -125,12 +142,23 @@ export function ProjectSwitcher({ projectName }: ProjectSwitcherProps) {
125
142
  ))}
126
143
 
127
144
  {/* Divider */}
128
- <div className="border-t border-zinc-200 dark:border-zinc-700 my-1" />
145
+ <div className="border-t border-zinc-200 dark:border-zinc-700 my-1.5" />
146
+
147
+ {/* New Project action */}
148
+ <button
149
+ onClick={handleNewProject}
150
+ className="w-full px-4 py-3 text-left text-base text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-700 flex items-center gap-3"
151
+ >
152
+ <svg className="w-3.5 h-3.5 text-zinc-400 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
153
+ <path strokeLinecap="round" strokeLinejoin="round" d="M12 4v16m8-8H4" />
154
+ </svg>
155
+ <span>New Project...</span>
156
+ </button>
129
157
 
130
158
  {/* Open Project action */}
131
159
  <button
132
160
  onClick={handleOpenProject}
133
- className="w-full px-3 py-2 text-left text-sm text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-700 flex items-center gap-2"
161
+ className="w-full px-4 py-3 text-left text-base text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-700 flex items-center gap-3"
134
162
  >
135
163
  <svg className="w-3.5 h-3.5 text-zinc-400 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
136
164
  <path strokeLinecap="round" strokeLinejoin="round" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
@@ -145,7 +173,7 @@ export function ProjectSwitcher({ projectName }: ProjectSwitcherProps) {
145
173
  <button
146
174
  ref={buttonRef}
147
175
  onClick={() => { setError(null); setIsOpen(!isOpen); }}
148
- className="px-2.5 py-1 text-sm bg-zinc-100 text-zinc-600 rounded-full border border-zinc-200 hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-400 dark:border-zinc-700 dark:hover:bg-zinc-700 transition-colors cursor-pointer flex items-center gap-1"
176
+ className="px-5 py-1.5 text-base bg-zinc-100 text-zinc-600 rounded-full border-2 border-zinc-200 hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-400 dark:border-zinc-700 dark:hover:bg-zinc-700 transition-colors duration-200 ease-out cursor-pointer flex items-center gap-1.5"
149
177
  >
150
178
  {projectName}
151
179
  <svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
@@ -154,7 +182,7 @@ export function ProjectSwitcher({ projectName }: ProjectSwitcherProps) {
154
182
  </button>
155
183
 
156
184
  {error && (
157
- <div className="absolute top-full left-0 mt-1 px-2.5 py-1.5 text-xs text-red-700 bg-red-50 border border-red-200 rounded-lg dark:text-red-400 dark:bg-red-900/20 dark:border-red-800 whitespace-nowrap">
185
+ <div className="absolute top-full left-0 mt-1 px-3 py-2 text-xs text-red-700 bg-red-50 border-2 border-red-200 rounded-lg dark:text-red-400 dark:bg-red-900/20 dark:border-red-800 whitespace-nowrap">
158
186
  {error}
159
187
  </div>
160
188
  )}
@@ -19,30 +19,30 @@ function PrototypeCard({ prototype, isSelected, onSelect, compact }: PrototypeCa
19
19
  return (
20
20
  <div
21
21
  onClick={onSelect}
22
- className={`flex bg-white border rounded-lg overflow-hidden cursor-pointer transition-colors ${
23
- isSelected ? 'border-blue-500 bg-blue-50' : 'border-zinc-200 hover:border-zinc-400'
22
+ className={`flex bg-white rounded-lg overflow-hidden cursor-pointer transition-colors duration-200 ease-out ${
23
+ isSelected ? 'border-2 border-[#819D9F] bg-[#e8f0f0]' : ''
24
24
  }`}
25
25
  >
26
26
  {/* Date column */}
27
- <div className="w-12 shrink-0 bg-zinc-100 flex flex-col items-center justify-center py-2 px-1 border-r border-zinc-200">
27
+ <div className="w-12 shrink-0 bg-zinc-100 flex flex-col items-center justify-center py-3 px-1.5 border-r border-zinc-200">
28
28
  <span className="text-base font-semibold text-zinc-900 leading-none">{day}</span>
29
29
  <span className="text-[10px] text-zinc-500 uppercase leading-none mt-0.5">{weekday}</span>
30
30
  </div>
31
31
 
32
32
  {/* Content */}
33
- <div className="flex-1 py-2 px-3">
34
- <div className="flex items-center gap-2">
35
- <span className="font-medium text-sm text-zinc-900 truncate">{prototype.title}</span>
33
+ <div className="flex-1 py-3 px-4">
34
+ <div className="flex items-center gap-3">
35
+ <span className="font-medium text-base text-zinc-900 truncate">{prototype.title}</span>
36
36
  {prototype.files.length > 1 && (
37
- <span className="text-[10px] px-1.5 py-0.5 bg-green-100 text-green-700 rounded-full whitespace-nowrap">
37
+ <span className="text-[10px] px-2 py-1 bg-green-100 text-green-700 rounded-full whitespace-nowrap">
38
38
  {prototype.files.length} opts
39
39
  </span>
40
40
  )}
41
41
  </div>
42
42
  {!compact && (
43
- <div className="flex items-center gap-2 text-xs">
43
+ <div className="flex items-center gap-3 text-base">
44
44
  {prototype.feature ? (
45
- <span className="text-blue-600">#{prototype.feature.id}</span>
45
+ <span className="text-[#5a7d7f]">#{prototype.feature.id}</span>
46
46
  ) : (
47
47
  <span className="text-zinc-500">Research</span>
48
48
  )}
@@ -66,7 +66,7 @@ function MonthGroup({ month, prototypes, selectedId, onSelect, compact }: MonthG
66
66
  return (
67
67
  <div className="mb-4">
68
68
  {/* Month header */}
69
- <div className="flex items-center gap-2 mb-2">
69
+ <div className="flex items-center gap-3 mb-3">
70
70
  <span className="text-[10px] font-semibold text-zinc-500 uppercase tracking-wider">
71
71
  {month}
72
72
  </span>
@@ -74,7 +74,7 @@ function MonthGroup({ month, prototypes, selectedId, onSelect, compact }: MonthG
74
74
  </div>
75
75
 
76
76
  {/* Prototype cards */}
77
- <div className="space-y-1.5">
77
+ <div className="space-y-2">
78
78
  {prototypes.map(prototype => (
79
79
  <PrototypeCard
80
80
  key={prototype.id}
@@ -125,23 +125,23 @@ function PreviewPanel({ prototype, onClose }: PreviewPanelProps) {
125
125
  };
126
126
 
127
127
  return (
128
- <div className="flex flex-col h-full bg-white border border-zinc-200 rounded-lg overflow-hidden">
128
+ <div className="flex flex-col h-full bg-white rounded-lg overflow-hidden">
129
129
  {/* Header */}
130
- <div className="flex items-center justify-between p-4 border-b border-zinc-200">
130
+ <div className="flex items-center justify-between p-6 border-b border-zinc-200">
131
131
  <div>
132
132
  <h3 className="font-semibold text-zinc-900">{prototype.title}</h3>
133
- <p className="text-sm text-zinc-500">{selectedFile?.name || prototype.description}</p>
133
+ <p className="text-base text-zinc-500">{selectedFile?.name || prototype.description}</p>
134
134
  </div>
135
135
  <div className="flex items-center gap-2">
136
136
  <button
137
137
  onClick={handleOpen}
138
- className="px-3 py-1.5 text-xs bg-zinc-100 border border-zinc-300 rounded text-zinc-700 hover:border-blue-500 hover:text-blue-600 transition-colors"
138
+ className="px-4 py-2 text-base bg-zinc-100 border border-zinc-300 rounded text-zinc-700 hover:border-[#819D9F] hover:text-[#5a7d7f] transition-colors duration-200 ease-out"
139
139
  >
140
140
  Open
141
141
  </button>
142
142
  <button
143
143
  onClick={onClose}
144
- className="p-2 text-zinc-400 hover:text-zinc-700 transition-colors"
144
+ className="p-2 text-zinc-400 hover:text-zinc-700 transition-colors duration-200 ease-out"
145
145
  >
146
146
  <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
147
147
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
@@ -152,16 +152,16 @@ function PreviewPanel({ prototype, onClose }: PreviewPanelProps) {
152
152
 
153
153
  {/* File selector */}
154
154
  {prototype.files.length > 1 && (
155
- <div className="px-4 py-2 border-b border-zinc-200">
156
- <div className="flex flex-wrap gap-2">
155
+ <div className="px-5 py-3 border-b border-zinc-200">
156
+ <div className="flex flex-wrap gap-3">
157
157
  {prototype.files.map(file => (
158
158
  <button
159
159
  key={file.path}
160
160
  onClick={() => setSelectedFile(file)}
161
- className={`px-2 py-1 text-xs rounded transition-colors ${
161
+ className={`px-3 py-1.5 text-base rounded transition-colors duration-200 ease-out ${
162
162
  selectedFile?.path === file.path
163
- ? 'bg-blue-600 text-white'
164
- : 'bg-zinc-100 border border-zinc-300 text-zinc-700 hover:border-blue-500'
163
+ ? 'bg-[#819D9F] text-white'
164
+ : 'bg-zinc-100 border border-zinc-300 text-zinc-700 hover:border-[#819D9F]'
165
165
  }`}
166
166
  >
167
167
  {file.name}
@@ -190,8 +190,8 @@ function PreviewPanel({ prototype, onClose }: PreviewPanelProps) {
190
190
  ) : (
191
191
  <div className="flex items-center justify-center h-full text-zinc-500">
192
192
  <div className="text-center">
193
- <p className="mb-2">Cannot preview this file type</p>
194
- <p className="text-sm">{selectedFile?.name}</p>
193
+ <p className="mb-3">Cannot preview this file type</p>
194
+ <p className="text-base">{selectedFile?.name}</p>
195
195
  </div>
196
196
  </div>
197
197
  )}
@@ -233,9 +233,9 @@ export function PrototypeTimeline({ data }: PrototypeTimelineProps) {
233
233
  : null;
234
234
 
235
235
  return (
236
- <div className="flex gap-4">
236
+ <div className="flex gap-6">
237
237
  {/* Timeline list */}
238
- <div className={`${selectedPrototype ? 'w-[320px] shrink-0' : 'w-full'} transition-all`}>
238
+ <div className={`${selectedPrototype ? 'w-[320px] shrink-0' : 'w-full'} transition-[width] duration-200 ease-out`}>
239
239
  {months.map(month => (
240
240
  <MonthGroup
241
241
  key={month}