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.
Files changed (240) hide show
  1. package/.env +4 -3
  2. package/Cargo.lock +6450 -0
  3. package/Cargo.toml +35 -0
  4. package/README.md +5 -1
  5. package/TAURI-MIGRATION-PLAN.md +840 -0
  6. package/apps/dashboard/app/connect-claude/page.tsx +5 -6
  7. package/apps/dashboard/app/decision/[id]/page.tsx +63 -58
  8. package/apps/dashboard/app/demo/gates/page.tsx +43 -45
  9. package/apps/dashboard/app/design-system/page.tsx +868 -0
  10. package/apps/dashboard/app/globals.css +80 -4
  11. package/apps/dashboard/app/install-claude/page.tsx +4 -6
  12. package/apps/dashboard/app/login/page.tsx +72 -54
  13. package/apps/dashboard/app/page.tsx +101 -48
  14. package/apps/dashboard/app/settings/page.tsx +61 -13
  15. package/apps/dashboard/app/signup/page.tsx +242 -0
  16. package/apps/dashboard/app/subscribe/page.tsx +0 -2
  17. package/apps/dashboard/app/tests/page.tsx +37 -4
  18. package/apps/dashboard/app/welcome/page.tsx +13 -16
  19. package/apps/dashboard/app/work/[id]/page.tsx +117 -118
  20. package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
  21. package/apps/dashboard/components/AppShell.tsx +92 -85
  22. package/apps/dashboard/components/CardMenu.tsx +45 -12
  23. package/apps/dashboard/components/ClaudePanel.tsx +771 -850
  24. package/apps/dashboard/components/ClaudePanelInput.tsx +43 -15
  25. package/apps/dashboard/components/ConnectClaudeScreen.tsx +17 -34
  26. package/apps/dashboard/components/CopyableId.tsx +3 -4
  27. package/apps/dashboard/components/DetailReviewActions.tsx +100 -0
  28. package/apps/dashboard/components/DragContext.tsx +134 -63
  29. package/apps/dashboard/components/DraggableCard.tsx +3 -5
  30. package/apps/dashboard/components/DropZone.tsx +6 -7
  31. package/apps/dashboard/components/EditableDetailDescription.tsx +7 -13
  32. package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
  33. package/apps/dashboard/components/EditableTitle.tsx +26 -7
  34. package/apps/dashboard/components/ElapsedTimer.tsx +66 -0
  35. package/apps/dashboard/components/EpicGroup.tsx +359 -0
  36. package/apps/dashboard/components/GateCard.tsx +79 -17
  37. package/apps/dashboard/components/GateChoiceCard.tsx +15 -18
  38. package/apps/dashboard/components/InstallClaudeScreen.tsx +15 -32
  39. package/apps/dashboard/components/JettyLoader.tsx +37 -0
  40. package/apps/dashboard/components/KanbanBoard.tsx +368 -958
  41. package/apps/dashboard/components/KanbanCard.tsx +740 -0
  42. package/apps/dashboard/components/LazyCard.tsx +62 -0
  43. package/apps/dashboard/components/LazyMarkdown.tsx +11 -0
  44. package/apps/dashboard/components/MainNav.tsx +38 -73
  45. package/apps/dashboard/components/MessageBlock.tsx +468 -0
  46. package/apps/dashboard/components/ModeStartCard.tsx +15 -16
  47. package/apps/dashboard/components/OnboardingWelcome.tsx +213 -0
  48. package/apps/dashboard/components/PlaceholderCard.tsx +3 -4
  49. package/apps/dashboard/components/ProjectSwitcher.tsx +30 -30
  50. package/apps/dashboard/components/PrototypeTimeline.tsx +72 -51
  51. package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +406 -388
  52. package/apps/dashboard/components/RealTimeTestsWrapper.tsx +373 -235
  53. package/apps/dashboard/components/ReviewFooter.tsx +139 -0
  54. package/apps/dashboard/components/SessionList.tsx +19 -19
  55. package/apps/dashboard/components/SubscribeContent.tsx +91 -47
  56. package/apps/dashboard/components/TestTree.tsx +16 -16
  57. package/apps/dashboard/components/TipCard.tsx +16 -17
  58. package/apps/dashboard/components/Toast.tsx +5 -6
  59. package/apps/dashboard/components/TypeIcon.tsx +55 -0
  60. package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
  61. package/apps/dashboard/components/WaveCompletionAnimation.tsx +52 -65
  62. package/apps/dashboard/components/WelcomeScreen.tsx +19 -35
  63. package/apps/dashboard/components/WorkItemHeader.tsx +4 -5
  64. package/apps/dashboard/components/WorkItemTree.tsx +11 -32
  65. package/apps/dashboard/components/settings/AccountSection.tsx +55 -35
  66. package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
  67. package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
  68. package/apps/dashboard/components/settings/EnvVarsSection.tsx +74 -152
  69. package/apps/dashboard/components/settings/GeneralSection.tsx +162 -56
  70. package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
  71. package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -5
  72. package/apps/dashboard/components/ui/Button.tsx +104 -0
  73. package/apps/dashboard/components/ui/Input.tsx +78 -0
  74. package/apps/dashboard/components.json +1 -1
  75. package/apps/dashboard/contexts/ClaudeSessionContext.tsx +711 -418
  76. package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -5
  77. package/apps/dashboard/contexts/UsageContext.tsx +87 -32
  78. package/apps/dashboard/dev.sh +35 -0
  79. package/apps/dashboard/eslint.config.mjs +9 -9
  80. package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
  81. package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
  82. package/apps/dashboard/hooks/useWebSocket.ts +138 -83
  83. package/apps/dashboard/index.html +73 -0
  84. package/apps/dashboard/lib/constants.ts +43 -0
  85. package/apps/dashboard/lib/data-bridge.ts +722 -0
  86. package/apps/dashboard/lib/db.ts +69 -1265
  87. package/apps/dashboard/lib/environment-config.ts +173 -0
  88. package/apps/dashboard/lib/environment-verification.ts +119 -0
  89. package/apps/dashboard/lib/kanban-utils.ts +270 -0
  90. package/apps/dashboard/lib/proof-run.ts +495 -0
  91. package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
  92. package/apps/dashboard/lib/run-migrations.js +27 -2
  93. package/apps/dashboard/lib/service-recovery.ts +326 -0
  94. package/apps/dashboard/lib/session-state-machine.ts +1 -0
  95. package/apps/dashboard/lib/session-state-utils.ts +0 -164
  96. package/apps/dashboard/lib/session-stream-manager.ts +308 -134
  97. package/apps/dashboard/lib/shadows.ts +7 -0
  98. package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
  99. package/apps/dashboard/lib/tauri-bridge.ts +102 -0
  100. package/apps/dashboard/lib/tauri.ts +106 -0
  101. package/apps/dashboard/lib/utils.ts +6 -0
  102. package/apps/dashboard/next-env.d.ts +1 -1
  103. package/apps/dashboard/package.json +21 -32
  104. package/apps/dashboard/public/bug-icon.png +0 -0
  105. package/apps/dashboard/public/buoy-icon.png +0 -0
  106. package/apps/dashboard/public/fonts/Satoshi-Variable.woff2 +0 -0
  107. package/apps/dashboard/public/fonts/Satoshi-VariableItalic.woff2 +0 -0
  108. package/apps/dashboard/public/in-flight-seagull.png +0 -0
  109. package/apps/dashboard/public/jetty-icon-loading-alt.svg +11 -0
  110. package/apps/dashboard/public/jetty-icon-loading.svg +11 -0
  111. package/apps/dashboard/public/jettypod_logo.png +0 -0
  112. package/apps/dashboard/public/pier-icon.png +0 -0
  113. package/apps/dashboard/public/star-icon.png +0 -0
  114. package/apps/dashboard/public/wrench-icon.png +0 -0
  115. package/apps/dashboard/scripts/tauri-build.js +228 -0
  116. package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
  117. package/apps/dashboard/scripts/ws-server.js +191 -0
  118. package/apps/dashboard/src/main.tsx +12 -0
  119. package/apps/dashboard/src/router.tsx +107 -0
  120. package/apps/dashboard/src/vite-env.d.ts +1 -0
  121. package/apps/dashboard/tsconfig.json +7 -12
  122. package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
  123. package/apps/dashboard/vite.config.ts +33 -0
  124. package/apps/update-server/src/index.ts +228 -80
  125. package/claude-hooks/global-guardrails.js +14 -13
  126. package/crates/jettypod-cli/Cargo.toml +19 -0
  127. package/crates/jettypod-cli/src/commands.rs +1249 -0
  128. package/crates/jettypod-cli/src/main.rs +595 -0
  129. package/crates/jettypod-core/Cargo.toml +26 -0
  130. package/crates/jettypod-core/build.rs +98 -0
  131. package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
  132. package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
  133. package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
  134. package/crates/jettypod-core/src/auth.rs +294 -0
  135. package/crates/jettypod-core/src/config.rs +397 -0
  136. package/crates/jettypod-core/src/db/mod.rs +507 -0
  137. package/crates/jettypod-core/src/db/recovery.rs +114 -0
  138. package/crates/jettypod-core/src/db/startup.rs +101 -0
  139. package/crates/jettypod-core/src/db/validate.rs +149 -0
  140. package/crates/jettypod-core/src/error.rs +76 -0
  141. package/crates/jettypod-core/src/git.rs +458 -0
  142. package/crates/jettypod-core/src/lib.rs +20 -0
  143. package/crates/jettypod-core/src/sessions.rs +625 -0
  144. package/crates/jettypod-core/src/skills.rs +556 -0
  145. package/crates/jettypod-core/src/work.rs +1086 -0
  146. package/crates/jettypod-core/src/worktree.rs +628 -0
  147. package/crates/jettypod-core/src/ws.rs +767 -0
  148. package/cucumber-test.cjs +6 -0
  149. package/cucumber.js +9 -3
  150. package/docs/COMMAND_REFERENCE.md +34 -0
  151. package/hooks/post-checkout +32 -75
  152. package/hooks/post-merge +111 -10
  153. package/jest.setup.js +1 -0
  154. package/jettypod.js +145 -116
  155. package/lib/bdd-preflight.js +96 -0
  156. package/lib/chore-taxonomy.js +33 -10
  157. package/lib/database.js +36 -16
  158. package/lib/db-watcher.js +1 -1
  159. package/lib/git-hooks/pre-commit +1 -1
  160. package/lib/jettypod-backup.js +27 -4
  161. package/lib/merge-lock.js +111 -253
  162. package/lib/migrations/027-plan-at-creation-column.js +3 -1
  163. package/lib/migrations/029-remove-autoincrement.js +307 -0
  164. package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
  165. package/lib/migrations/030-rejection-round-columns.js +54 -0
  166. package/lib/migrations/031-session-isolation-index.js +17 -0
  167. package/lib/migrations/index.js +47 -4
  168. package/lib/schema.js +10 -5
  169. package/lib/seed-onboarding.js +1 -1
  170. package/lib/update-command/index.js +9 -175
  171. package/lib/work-commands/index.js +144 -19
  172. package/lib/work-tracking/index.js +148 -27
  173. package/lib/worktree-diagnostics.js +16 -16
  174. package/lib/worktree-facade.js +1 -1
  175. package/lib/worktree-manager.js +8 -8
  176. package/lib/worktree-reconciler.js +5 -5
  177. package/package.json +9 -2
  178. package/scripts/ndjson-to-cucumber-json.js +152 -0
  179. package/scripts/postinstall.js +25 -0
  180. package/skills-templates/bug-mode/SKILL.md +79 -20
  181. package/skills-templates/bug-planning/SKILL.md +25 -29
  182. package/skills-templates/chore-mode/SKILL.md +171 -69
  183. package/skills-templates/chore-mode/verification.js +51 -10
  184. package/skills-templates/chore-planning/SKILL.md +47 -18
  185. package/skills-templates/design-system-selection/SKILL.md +273 -0
  186. package/skills-templates/epic-planning/SKILL.md +82 -48
  187. package/skills-templates/external-transition/SKILL.md +47 -47
  188. package/skills-templates/feature-planning/SKILL.md +173 -74
  189. package/skills-templates/production-mode/SKILL.md +69 -49
  190. package/skills-templates/request-routing/SKILL.md +4 -4
  191. package/skills-templates/simple-improvement/SKILL.md +74 -29
  192. package/skills-templates/speed-mode/SKILL.md +217 -141
  193. package/skills-templates/stable-mode/SKILL.md +148 -89
  194. package/apps/dashboard/README.md +0 -36
  195. package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -386
  196. package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
  197. package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -167
  198. package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
  199. package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -378
  200. package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
  201. package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
  202. package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
  203. package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
  204. package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
  205. package/apps/dashboard/app/api/kanban/route.ts +0 -15
  206. package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
  207. package/apps/dashboard/app/api/settings/general/route.ts +0 -21
  208. package/apps/dashboard/app/api/tests/route.ts +0 -9
  209. package/apps/dashboard/app/api/tests/run/route.ts +0 -82
  210. package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
  211. package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
  212. package/apps/dashboard/app/api/usage/route.ts +0 -17
  213. package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
  214. package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
  215. package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
  216. package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -21
  217. package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
  218. package/apps/dashboard/app/layout.tsx +0 -43
  219. package/apps/dashboard/components/UpgradeBanner.tsx +0 -29
  220. package/apps/dashboard/electron/ipc-handlers.js +0 -1028
  221. package/apps/dashboard/electron/main.js +0 -2124
  222. package/apps/dashboard/electron/preload.js +0 -123
  223. package/apps/dashboard/electron/session-manager.js +0 -141
  224. package/apps/dashboard/electron-builder.config.js +0 -357
  225. package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
  226. package/apps/dashboard/lib/claude-process-manager.ts +0 -492
  227. package/apps/dashboard/lib/db-bridge.ts +0 -282
  228. package/apps/dashboard/lib/prototypes.ts +0 -202
  229. package/apps/dashboard/lib/test-results-db.ts +0 -307
  230. package/apps/dashboard/lib/tests.ts +0 -282
  231. package/apps/dashboard/next.config.js +0 -50
  232. package/apps/dashboard/postcss.config.mjs +0 -7
  233. package/apps/dashboard/public/file.svg +0 -1
  234. package/apps/dashboard/public/globe.svg +0 -1
  235. package/apps/dashboard/public/next.svg +0 -1
  236. package/apps/dashboard/public/vercel.svg +0 -1
  237. package/apps/dashboard/public/window.svg +0 -1
  238. package/apps/dashboard/scripts/download-node.js +0 -104
  239. package/apps/dashboard/scripts/upload-to-r2.js +0 -89
  240. package/docs/bdd-guidance.md +0 -390
@@ -1,133 +1,140 @@
1
- 'use client';
2
-
3
- import { useState, useEffect } from 'react';
4
- import { usePathname, useRouter } from 'next/navigation';
5
- import { ClaudeSessionProvider, useClaudeSession } from '../contexts/ClaudeSessionContext';
6
- import { ConnectionStatusProvider } from '../contexts/ConnectionStatusContext';
1
+ import { useEffect, useRef, useLayoutEffect } from 'react';
2
+ import { useLocation, useNavigate } from 'react-router-dom';
3
+ import { ClaudeSessionProvider, useSessionState, useSessionActions } from '../contexts/ClaudeSessionContext';
4
+ import { ConnectionStatusProvider, useConnectionStatus } from '../contexts/ConnectionStatusContext';
7
5
  import { UsageProvider } from '../contexts/UsageContext';
8
6
  import { ToastProvider } from './Toast';
9
7
  import { MainNav } from './MainNav';
10
8
  import { ClaudePanel } from './ClaudePanel';
9
+ import { LazyMotion, domAnimation, m } from 'framer-motion';
10
+ import { isTauri, auth } from '@/lib/tauri-bridge';
11
11
  import type { ReactNode } from 'react';
12
12
 
13
+ // Module-level auth cache — auth status doesn't change within a session.
14
+ // Prevents redundant IPC calls if AppShell ever remounts.
15
+ let cachedAuthOk: boolean | null = null;
16
+
13
17
  // Pages that should not show the nav header (pre-project screens)
14
- const NO_NAV_PATHS = ['/login', '/subscribe', '/install-claude', '/connect-claude', '/welcome'];
18
+ const NO_NAV_PATHS = ['/login', '/signup', '/subscribe', '/install-claude', '/connect-claude', '/welcome', '/prototypes/onboarding'];
15
19
 
16
20
  // Pages accessible without authentication
17
- const PUBLIC_PATHS = ['/login', '/subscribe', '/install-claude', '/connect-claude', '/welcome'];
21
+ const PUBLIC_PATHS = ['/login', '/signup', '/subscribe', '/install-claude', '/connect-claude', '/welcome', '/design-system', '/prototypes/onboarding'];
18
22
 
19
23
  interface AppShellProps {
20
- projectName: string;
24
+ projectName?: string;
21
25
  children: ReactNode;
22
26
  }
23
27
 
24
- function AppShellContent({ projectName, children }: AppShellProps) {
25
- const pathname = usePathname();
26
- const router = useRouter();
28
+ function AppShellContent({ projectName = 'JettyPod', children }: AppShellProps) {
29
+ const { pathname } = useLocation();
30
+ const navigate = useNavigate();
27
31
  const showNav = !NO_NAV_PATHS.includes(pathname);
28
- const [authChecked, setAuthChecked] = useState(false);
32
+ const isPublicPath = PUBLIC_PATHS.includes(pathname);
33
+ const { showDisconnected, status: connectionStatus } = useConnectionStatus();
29
34
 
30
- // Auth enforcement gate: redirect unauthenticated users to /login
35
+ // Optimistic auth: render content immediately, check auth in the background.
36
+ // Redirects to login only if the async check fails — authenticated users
37
+ // (the common case) never see a loader or blank screen.
31
38
  useEffect(() => {
32
- if (PUBLIC_PATHS.includes(pathname)) {
33
- setAuthChecked(true);
34
- return;
35
- }
39
+ if (isPublicPath || cachedAuthOk) return;
36
40
 
37
41
  async function checkAuth() {
38
- if (typeof window !== 'undefined' && window.electronAPI?.isElectron) {
42
+ if (isTauri()) {
39
43
  try {
40
- const status = await window.electronAPI.auth.getStatus();
44
+ const status = await auth.getStatus();
41
45
  if (!status.authenticated) {
42
- router.push('/login');
46
+ const hasLoggedIn = await auth.hasLoggedInBefore();
47
+ navigate(hasLoggedIn ? '/login' : '/signup', { replace: true });
43
48
  return;
44
49
  }
50
+ cachedAuthOk = true;
45
51
  } catch {
46
- // Corrupted auth file, IPC error, etc. — treat as unauthenticated
47
- router.push('/login');
48
- return;
52
+ navigate('/login', { replace: true });
49
53
  }
50
54
  }
51
- setAuthChecked(true);
52
55
  }
53
56
  checkAuth();
54
- }, [pathname, router]);
57
+ // eslint-disable-next-line react-hooks/exhaustive-deps
58
+ }, []);
55
59
 
56
- const {
57
- claudePanelOpen,
58
- setClaudePanelOpen,
59
- activeSessionId,
60
- activeSession,
61
- sessions,
62
- standaloneSessions,
63
- messages,
64
- status,
65
- error,
66
- exitCode,
67
- canRetry,
68
- queuedMessage,
69
- switchSession,
70
- closeSession,
71
- createNewSession,
72
- sendMessage,
73
- retry,
74
- stop,
75
- narratedMode,
76
- toggleNarratedMode,
77
- } = useClaudeSession();
60
+ const { claudePanelOpen } = useSessionState();
61
+ const { setClaudePanelOpen, openSessionPanel } = useSessionActions();
78
62
 
79
- // Don't render anything until auth check completes (prevents content flash)
80
- if (!authChecked) {
81
- return <div className="h-screen" />;
82
- }
63
+ // Single source of truth for available content height.
64
+ // Sets --main-h CSS custom property on <main> so any descendant can use it
65
+ // without nested ResizeObservers or fragile flex-1 chains (which break in WKWebView).
66
+ const mainRef = useRef<HTMLElement>(null);
67
+ useLayoutEffect(() => {
68
+ const el = mainRef.current;
69
+ if (!el) return;
70
+ const ro = new ResizeObserver(([entry]) => {
71
+ el.style.setProperty('--main-h', `${entry.contentRect.height}px`);
72
+ });
73
+ ro.observe(el);
74
+ return () => ro.disconnect();
75
+ }, []);
83
76
 
84
77
  return (
85
78
  <div className="h-screen flex flex-col overflow-hidden">
86
- {showNav && <MainNav projectName={projectName} />}
87
- <main className={`flex-1 flex flex-col min-h-0 overflow-y-auto transition-[margin] duration-300 ${showNav && claudePanelOpen ? 'mr-[480px]' : ''}`}>
79
+ {showNav && (
80
+ <div className="relative flex-shrink-0">
81
+ <MainNav projectName={projectName} />
82
+ <m.button
83
+ onClick={() => claudePanelOpen ? setClaudePanelOpen(false) : openSessionPanel()}
84
+ className="absolute px-3 py-1.5 text-sm font-medium text-white hover:brightness-105 active:scale-[0.98] cursor-pointer"
85
+ animate={{ right: claudePanelOpen ? 480 : 0 }}
86
+ transition={{ type: 'spring', damping: 25, stiffness: 200 }}
87
+ style={{
88
+ zIndex: 51,
89
+ top: '50%',
90
+ transform: 'translateY(-50%)',
91
+ backgroundColor: '#819D9F',
92
+ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.06), 0 4px 12px rgba(129, 157, 159, 0.2)',
93
+ borderRadius: '12px 0 0 12px',
94
+ textAlign: 'center' as const,
95
+ lineHeight: 1.3,
96
+ }}
97
+ aria-label={claudePanelOpen ? 'Hide Claudes panel' : 'Show Claudes panel'}
98
+ data-testid="claudes-toggle-tab"
99
+ >
100
+ {claudePanelOpen ? <><span>Hide</span><br /><span>Claudes</span></> : <><span>Show</span><br /><span>Claudes</span></>}
101
+ </m.button>
102
+ </div>
103
+ )}
104
+ {showNav && showDisconnected && (
105
+ <div className="bg-amber-50 dark:bg-amber-950 border-b border-amber-200 dark:border-amber-800 px-4 py-2 text-center text-base text-amber-800 dark:text-amber-200" data-testid="disconnect-banner">
106
+ {connectionStatus === 'reconnecting'
107
+ ? 'Reconnecting to live updates...'
108
+ : 'Live updates disconnected. Changes may not appear automatically.'}
109
+ </div>
110
+ )}
111
+ <main ref={mainRef} className={`flex-1 flex flex-col min-h-0 overflow-hidden transition-[margin] duration-200 ease-out ${showNav && claudePanelOpen ? 'mr-[480px]' : ''}`}>
88
112
  {children}
89
113
  </main>
90
114
  {showNav && (
91
115
  <ClaudePanel
92
116
  isOpen={claudePanelOpen}
93
- workItemId={activeSessionId || 'sessions'}
94
- workItemTitle={activeSession?.title || 'Claude Sessions'}
95
- messages={messages}
96
- status={status}
97
- error={error}
98
- exitCode={exitCode}
99
- canRetry={canRetry}
100
- queuedMessage={queuedMessage}
101
117
  onClose={() => setClaudePanelOpen(false)}
102
- onRetry={retry}
103
- onSendMessage={sendMessage}
104
- onStop={stop}
105
- sessions={sessions}
106
- activeSessionId={activeSessionId}
107
- onSwitchSession={switchSession}
108
- standaloneSessions={standaloneSessions}
109
- onNewSession={createNewSession}
110
- onCloseSession={closeSession}
111
- narratedMode={narratedMode}
112
- onToggleNarratedMode={toggleNarratedMode}
113
118
  />
114
119
  )}
115
120
  </div>
116
121
  );
117
122
  }
118
123
 
119
- export function AppShell({ projectName, children }: AppShellProps) {
124
+ export function AppShell({ projectName = 'JettyPod', children }: AppShellProps) {
120
125
  return (
121
- <ConnectionStatusProvider>
122
- <UsageProvider>
123
- <ToastProvider>
124
- <ClaudeSessionProvider>
125
- <AppShellContent projectName={projectName}>
126
- {children}
127
- </AppShellContent>
128
- </ClaudeSessionProvider>
129
- </ToastProvider>
130
- </UsageProvider>
131
- </ConnectionStatusProvider>
126
+ <LazyMotion features={domAnimation} strict>
127
+ <ConnectionStatusProvider>
128
+ <UsageProvider>
129
+ <ToastProvider>
130
+ <ClaudeSessionProvider>
131
+ <AppShellContent projectName={projectName}>
132
+ {children}
133
+ </AppShellContent>
134
+ </ClaudeSessionProvider>
135
+ </ToastProvider>
136
+ </UsageProvider>
137
+ </ConnectionStatusProvider>
138
+ </LazyMotion>
132
139
  );
133
140
  }
@@ -1,4 +1,3 @@
1
- 'use client';
2
1
 
3
2
  import { useState, useRef, useEffect } from 'react';
4
3
  import { createPortal } from 'react-dom';
@@ -15,9 +14,11 @@ interface CardMenuProps {
15
14
  hasActiveSession?: boolean;
16
15
  onOpenSession?: (id: string) => void;
17
16
  usageAllowed?: boolean;
17
+ onEditName?: () => void;
18
+ onUnaccept?: () => void;
18
19
  }
19
20
 
20
- export function CardMenu({ itemId, itemTitle = '', itemType = 'chore', itemDescription, conversational = false, currentStatus, onStatusChange, onTriggerClaude, hasActiveSession, onOpenSession, usageAllowed = true }: CardMenuProps) {
21
+ export function CardMenu({ itemId, itemTitle = '', itemType = 'chore', itemDescription, conversational = false, currentStatus, onStatusChange, onTriggerClaude, hasActiveSession, onOpenSession, usageAllowed = true, onEditName, onUnaccept }: CardMenuProps) {
21
22
  const [isOpen, setIsOpen] = useState(false);
22
23
  const [error, setError] = useState<string | null>(null);
23
24
  const [dropdownPosition, setDropdownPosition] = useState<{ top: number; left: number } | null>(null);
@@ -92,21 +93,43 @@ export function CardMenu({ itemId, itemTitle = '', itemType = 'chore', itemDescr
92
93
  }
93
94
  };
94
95
 
96
+ const handleEditName = (e: React.MouseEvent) => {
97
+ e.stopPropagation();
98
+ setIsOpen(false);
99
+ onEditName?.();
100
+ };
101
+
102
+ const handleUnaccept = (e: React.MouseEvent) => {
103
+ e.stopPropagation();
104
+ setIsOpen(false);
105
+ onUnaccept?.();
106
+ };
107
+
95
108
  const dropdownContent = (
96
109
  <div
97
110
  ref={dropdownRef}
98
- 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-[140px]"
111
+ className="fixed z-50 bg-white dark:bg-zinc-800 rounded-lg shadow-lg py-1.5 min-w-[140px]"
99
112
  style={{
100
113
  top: dropdownPosition?.top ?? 0,
101
114
  left: dropdownPosition?.left ?? 0,
102
115
  }}
103
116
  data-testid="status-dropdown"
104
117
  >
118
+ {onEditName && (
119
+ <button
120
+ onClick={handleEditName}
121
+ 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"
122
+ data-testid="edit-name-button"
123
+ >
124
+ <span className="text-zinc-500">✏️</span>
125
+ Edit name
126
+ </button>
127
+ )}
105
128
  {(currentStatus === 'backlog' || currentStatus === 'cancelled') && (
106
129
  <button
107
130
  onClick={handleStart}
108
131
  disabled={!usageAllowed}
109
- className={`w-full px-3 py-2 text-left text-sm flex items-center gap-2 ${
132
+ className={`w-full px-4 py-3 text-left text-base flex items-center gap-3 ${
110
133
  usageAllowed
111
134
  ? 'text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-700'
112
135
  : 'text-zinc-400 dark:text-zinc-600 cursor-not-allowed'
@@ -114,24 +137,24 @@ export function CardMenu({ itemId, itemTitle = '', itemType = 'chore', itemDescr
114
137
  data-testid="start-button"
115
138
  title={!usageAllowed ? 'Weekly usage limit reached' : undefined}
116
139
  >
117
- <span className={usageAllowed ? 'text-blue-500' : 'text-zinc-400 dark:text-zinc-600'}>▶</span>
140
+ <span className={usageAllowed ? 'text-[#819D9F]' : 'text-zinc-400 dark:text-zinc-600'}>▶</span>
118
141
  Start
119
142
  </button>
120
143
  )}
121
144
  {hasActiveSession && (
122
145
  <button
123
146
  onClick={handleOpenSession}
124
- 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"
147
+ 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"
125
148
  data-testid="open-session-button"
126
149
  >
127
- <span className="text-blue-500">💬</span>
150
+ <span className="text-[#819D9F]">💬</span>
128
151
  Open session
129
152
  </button>
130
153
  )}
131
154
  {currentStatus !== 'done' && (
132
155
  <button
133
156
  onClick={(e) => handleAction(e, 'done')}
134
- 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"
157
+ 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"
135
158
  data-testid="mark-done-button"
136
159
  >
137
160
  <span className="text-green-500">✓</span>
@@ -141,17 +164,27 @@ export function CardMenu({ itemId, itemTitle = '', itemType = 'chore', itemDescr
141
164
  {(currentStatus === 'in_progress' || currentStatus === 'done') && (
142
165
  <button
143
166
  onClick={(e) => handleAction(e, 'backlog')}
144
- 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"
167
+ 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"
145
168
  data-testid="unstart-button"
146
169
  >
147
170
  <span className="text-amber-500">↩</span>
148
171
  Unstart
149
172
  </button>
150
173
  )}
174
+ {currentStatus === 'done' && onUnaccept && (
175
+ <button
176
+ onClick={handleUnaccept}
177
+ 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"
178
+ data-testid="unaccept-button"
179
+ >
180
+ <span className="text-red-500">↩</span>
181
+ Unaccept
182
+ </button>
183
+ )}
151
184
  {currentStatus !== 'cancelled' && (
152
185
  <button
153
186
  onClick={(e) => handleAction(e, 'cancelled')}
154
- 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"
187
+ 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"
155
188
  data-testid="cancel-button"
156
189
  >
157
190
  <span className="text-red-500">✕</span>
@@ -166,7 +199,7 @@ export function CardMenu({ itemId, itemTitle = '', itemType = 'chore', itemDescr
166
199
  <button
167
200
  ref={buttonRef}
168
201
  onClick={handleMenuClick}
169
- className="p-1 rounded hover:bg-zinc-200 dark:hover:bg-zinc-600 transition-colors"
202
+ className="p-1 rounded hover:bg-zinc-200 dark:hover:bg-zinc-600 transition-colors duration-200 ease-out"
170
203
  aria-label="Card menu"
171
204
  data-testid="menu-button"
172
205
  >
@@ -181,7 +214,7 @@ export function CardMenu({ itemId, itemTitle = '', itemType = 'chore', itemDescr
181
214
 
182
215
  {error && (
183
216
  <div
184
- className="absolute right-0 top-full mt-1 z-10 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 text-xs px-2 py-1 rounded border border-red-200 dark:border-red-800"
217
+ className="absolute right-0 top-full mt-1 z-10 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 text-xs px-3 py-1.5 rounded border-2 border-red-200 dark:border-red-800"
185
218
  data-testid="error-message"
186
219
  >
187
220
  {error}