vibepulse 0.2.2 → 0.3.0-beta.0
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/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +1 -0
- package/.next/build-manifest.json +2 -2
- package/.next/cache/.previewinfo +1 -1
- package/.next/cache/.rscinfo +1 -1
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/fallback-build-manifest.json +2 -2
- package/.next/prerender-manifest.json +3 -3
- package/.next/routes-manifest.json +8 -0
- package/.next/server/app/_global-error/page.js +1 -1
- package/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/server/app/_global-error.html +2 -2
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/node/sessions/[id]/open-editor/route.js.nft.json +1 -1
- package/.next/server/app/api/node/sessions/route.js +5 -3
- package/.next/server/app/api/node/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/opencode-config/route.js.nft.json +1 -1
- package/.next/server/app/api/opencode-config/status/route.js.nft.json +1 -1
- package/.next/server/app/api/profiles/[id]/apply/route.js.nft.json +1 -1
- package/.next/server/app/api/profiles/[id]/export/route.js.nft.json +1 -1
- package/.next/server/app/api/profiles/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/profiles/import/route.js.nft.json +1 -1
- package/.next/server/app/api/profiles/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/archive/route.js +3 -2
- package/.next/server/app/api/sessions/[id]/archive/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/delete/route.js +3 -2
- package/.next/server/app/api/sessions/[id]/delete/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/open-editor/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/open-editor/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/restore/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/sessions/[id]/restore/route/build-manifest.json +11 -0
- package/.next/server/app/api/sessions/[id]/restore/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/sessions/[id]/restore/route.js +8 -0
- package/.next/server/app/api/sessions/[id]/restore/route.js.map +5 -0
- package/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -0
- package/.next/server/app/api/sessions/[id]/restore/route_client-reference-manifest.js +2 -0
- package/.next/server/app/api/sessions/route.js +4 -2
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +1 -0
- package/.next/server/chunks/[root-of-the-server]__31d19c5c._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__31d19c5c._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__56f5f249._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__56f5f249._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__98073dd6._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__98073dd6._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__b7b717eb._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__b7b717eb._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__d8e61048._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__d8e61048._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__f441109e._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__f441109e._.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js.map +1 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js +3 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js.map +1 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js +3 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js.map +1 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js +1 -1
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js.map +1 -1
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js +1 -1
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js.map +1 -1
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js +3 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js.map +1 -0
- package/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js +3 -0
- package/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js.map +1 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js → [root-of-the-server]__c91a8380._.js} +2 -2
- package/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js.map → [root-of-the-server]__c91a8380._.js.map} +1 -1
- package/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js +3 -3
- package/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js.map +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +2 -2
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/routes-manifest.json +8 -0
- package/.next/standalone/.next/server/app/_global-error/page.js +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/node/sessions/[id]/open-editor/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/node/sessions/route.js +5 -3
- package/.next/standalone/.next/server/app/api/node/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/opencode-config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/opencode-config/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/profiles/[id]/apply/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/profiles/[id]/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/profiles/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/profiles/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/profiles/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/archive/route.js +3 -2
- package/.next/standalone/.next/server/app/api/sessions/[id]/archive/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/delete/route.js +3 -2
- package/.next/standalone/.next/server/app/api/sessions/[id]/delete/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/open-editor/route.js +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/open-editor/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js +8 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/sessions/route.js +4 -2
- package/.next/standalone/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__31d19c5c._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__56f5f249._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__98073dd6._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__b7b717eb._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d8e61048._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f441109e._.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js +3 -0
- package/.next/standalone/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js → [root-of-the-server]__c91a8380._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +2 -2
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/b3bc362202331708.css +3 -0
- package/.next/standalone/.next/static/chunks/{65d5354ba0add961.js → c1294e057d8d4681.js} +3 -3
- package/.next/standalone/README.md +29 -5
- package/.next/standalone/docs/session-status-detection.md +36 -0
- package/.next/standalone/docs/superpowers/specs/2026-04-09-claude-capability-alignment-design.md +39 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/src/app/api/node/sessions/[id]/archive/route.test.ts +60 -1
- package/.next/standalone/src/app/api/node/sessions/[id]/archive/route.ts +77 -22
- package/.next/standalone/src/app/api/node/sessions/route.test.ts +282 -0
- package/.next/standalone/src/app/api/node/sessions/route.ts +141 -17
- package/.next/standalone/src/app/api/opencode-events/route.test.ts +3 -1
- package/.next/standalone/src/app/api/sessions/[id]/archive/route.test.ts +101 -0
- package/.next/standalone/src/app/api/sessions/[id]/archive/route.ts +47 -12
- package/.next/standalone/src/app/api/sessions/[id]/delete/route.test.ts +92 -0
- package/.next/standalone/src/app/api/sessions/[id]/delete/route.ts +45 -10
- package/.next/standalone/src/app/api/sessions/[id]/open-editor/route.test.ts +74 -0
- package/.next/standalone/src/app/api/sessions/[id]/open-editor/route.ts +22 -2
- package/.next/standalone/src/app/api/sessions/[id]/restore/route.test.ts +186 -0
- package/.next/standalone/src/app/api/sessions/[id]/restore/route.ts +184 -0
- package/.next/standalone/src/app/api/sessions/route.test.ts +1889 -107
- package/.next/standalone/src/app/api/sessions/route.ts +365 -981
- package/.next/standalone/src/components/KanbanBoard.test.tsx +307 -1
- package/.next/standalone/src/components/KanbanBoard.tsx +105 -18
- package/.next/standalone/src/components/ProjectCard.test.tsx +416 -2
- package/.next/standalone/src/components/ProjectCard.tsx +238 -86
- package/.next/standalone/src/components/SessionCard.test.tsx +253 -2
- package/.next/standalone/src/components/SessionCard.tsx +182 -76
- package/.next/standalone/src/hooks/useOpencodeSync.test.ts +321 -1
- package/.next/standalone/src/hooks/useOpencodeSync.ts +16 -12
- package/.next/standalone/src/lib/claudeSessionOverrides.test.ts +75 -0
- package/.next/standalone/src/lib/claudeSessionOverrides.ts +169 -0
- package/.next/standalone/src/lib/session-providers/claudeCode.test.ts +2288 -0
- package/.next/standalone/src/lib/session-providers/claudeCode.ts +1083 -0
- package/.next/standalone/src/lib/session-providers/localAggregator.test.ts +322 -0
- package/.next/standalone/src/lib/session-providers/localAggregator.ts +302 -0
- package/.next/standalone/src/lib/session-providers/opencodeProvider.ts +723 -0
- package/.next/standalone/src/lib/session-providers/providerIds.test.ts +337 -0
- package/.next/standalone/src/lib/session-providers/providerIds.ts +176 -0
- package/.next/standalone/src/lib/session-providers/types.ts +131 -0
- package/.next/standalone/src/lib/transform.test.ts +253 -0
- package/.next/standalone/src/lib/transform.ts +96 -37
- package/.next/standalone/src/types/index.ts +23 -17
- package/.next/static/chunks/b3bc362202331708.css +3 -0
- package/.next/static/chunks/{65d5354ba0add961.js → c1294e057d8d4681.js} +3 -3
- package/.next/trace +1 -1
- package/.next/trace-build +1 -1
- package/.next/types/routes.d.ts +2 -1
- package/.next/types/validator.ts +9 -0
- package/README.md +29 -5
- package/package.json +1 -1
- package/.next/server/chunks/[root-of-the-server]__2f981540._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__2f981540._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__3745b314._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__3745b314._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__6c428a24._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__6c428a24._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__73a00b88._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__73a00b88._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__db285678._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__db285678._.js.map +0 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__2f981540._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__3745b314._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__6c428a24._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__73a00b88._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__db285678._.js +0 -3
- package/.next/standalone/.next/static/chunks/f42202943f6742e5.css +0 -3
- package/.next/static/chunks/f42202943f6742e5.css +0 -3
- /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_clientMiddlewareManifest.json +0 -0
- /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_ssgManifest.js +0 -0
- /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_buildManifest.js +0 -0
- /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_clientMiddlewareManifest.json +0 -0
- /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_ssgManifest.js +0 -0
|
@@ -22,6 +22,16 @@ interface ProjectCardProps {
|
|
|
22
22
|
multipleHostsEnabled?: boolean;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
const RECENT_IDLE_CHILD_VISIBILITY_WINDOW_MS = 60_000;
|
|
26
|
+
|
|
27
|
+
function shouldShowChildSession(child: { realTimeStatus: string; waitingForUser: boolean; updatedAt: number }): boolean {
|
|
28
|
+
if (child.realTimeStatus !== 'idle' || child.waitingForUser) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return Date.now() - child.updatedAt <= RECENT_IDLE_CHILD_VISIBILITY_WINDOW_MS;
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
function formatRelativeTime(timestamp: number): string {
|
|
26
36
|
const diffMs = Date.now() - timestamp;
|
|
27
37
|
const diffMins = Math.floor(diffMs / (1000 * 60));
|
|
@@ -38,36 +48,39 @@ function buildTooltipTitle(lines: string[], debugReason?: string): string {
|
|
|
38
48
|
return debugReason ? [...lines, `Reason: ${debugReason}`].join('\n') : lines.join('\n');
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
function StatusDot({ status, waitingForUser }: { status: string; waitingForUser: boolean }) {
|
|
51
|
+
function StatusDot({ status, waitingForUser, provider }: { status: string; waitingForUser: boolean; provider?: string }) {
|
|
52
|
+
const isClaudeProvider = provider === 'claude-code';
|
|
53
|
+
const shapeClass = isClaudeProvider ? 'rotate-45 rounded-[1px]' : 'rounded-full';
|
|
54
|
+
const dotSizeClass = isClaudeProvider ? 'h-[7px] w-[7px]' : 'h-2 w-2';
|
|
42
55
|
if (waitingForUser) {
|
|
43
56
|
return (
|
|
44
|
-
<span className=
|
|
45
|
-
<span className=
|
|
46
|
-
<span className=
|
|
57
|
+
<span className={`relative flex ${dotSizeClass} flex-shrink-0`} title="Waiting">
|
|
58
|
+
<span className={`animate-ping absolute inline-flex h-full w-full ${shapeClass} bg-amber-400 opacity-75`}></span>
|
|
59
|
+
<span className={`relative inline-flex ${dotSizeClass} ${shapeClass} bg-amber-500`}></span>
|
|
47
60
|
</span>
|
|
48
61
|
);
|
|
49
62
|
}
|
|
50
63
|
switch (status) {
|
|
51
64
|
case 'busy':
|
|
52
65
|
return (
|
|
53
|
-
<span className=
|
|
54
|
-
<span className=
|
|
55
|
-
<span className=
|
|
66
|
+
<span className={`relative flex ${dotSizeClass} flex-shrink-0`} title="Running">
|
|
67
|
+
<span className={`animate-pulse absolute inline-flex h-full w-full ${shapeClass} bg-emerald-400 opacity-75`}></span>
|
|
68
|
+
<span className={`relative inline-flex ${dotSizeClass} ${shapeClass} bg-emerald-500`}></span>
|
|
56
69
|
</span>
|
|
57
70
|
);
|
|
58
71
|
case 'retry':
|
|
59
72
|
return (
|
|
60
|
-
<span className=
|
|
61
|
-
<span className=
|
|
62
|
-
<span className=
|
|
73
|
+
<span className={`relative flex ${dotSizeClass} flex-shrink-0`} title="Retrying">
|
|
74
|
+
<span className={`animate-ping absolute inline-flex h-full w-full ${shapeClass} bg-red-400 opacity-75`}></span>
|
|
75
|
+
<span className={`relative inline-flex ${dotSizeClass} ${shapeClass} bg-red-500`}></span>
|
|
63
76
|
</span>
|
|
64
77
|
);
|
|
65
78
|
default:
|
|
66
|
-
return <span className=
|
|
79
|
+
return <span className={`inline-flex ${dotSizeClass} bg-gray-400 flex-shrink-0 ${shapeClass}`} title="Idle"></span>;
|
|
67
80
|
}
|
|
68
81
|
}
|
|
69
82
|
|
|
70
|
-
function HeaderActionMenu({ cards,
|
|
83
|
+
function HeaderActionMenu({ cards, isActionPending, onActionError, onPendingActionChange, readOnlyMode }: { cards: KanbanCard[]; isActionPending: boolean; onActionError: (message: string | null) => void; onPendingActionChange: (value: 'open' | 'archive' | 'restore' | 'delete' | null) => void; readOnlyMode: boolean }) {
|
|
71
84
|
const queryClient = useQueryClient();
|
|
72
85
|
const [open, setOpen] = useState(false);
|
|
73
86
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
@@ -83,7 +96,11 @@ function HeaderActionMenu({ cards, readOnly = false, isActionPending, onActionEr
|
|
|
83
96
|
return () => document.removeEventListener('mousedown', handler);
|
|
84
97
|
}, [open]);
|
|
85
98
|
|
|
86
|
-
const
|
|
99
|
+
const canArchiveAny = !readOnlyMode && cards.some(c => c.capabilities ? c.capabilities.archive : !c.readOnly);
|
|
100
|
+
const canDeleteAny = !readOnlyMode && cards.some(c => c.capabilities ? c.capabilities.delete : !c.readOnly);
|
|
101
|
+
|
|
102
|
+
if (!canArchiveAny && !canDeleteAny) return null;
|
|
103
|
+
|
|
87
104
|
const hasMixedHosts = new Set(cards.map((card) => card.hostId ?? 'local')).size > 1;
|
|
88
105
|
|
|
89
106
|
const handleArchiveAll = async (e: React.MouseEvent) => {
|
|
@@ -96,7 +113,15 @@ function HeaderActionMenu({ cards, readOnly = false, isActionPending, onActionEr
|
|
|
96
113
|
return;
|
|
97
114
|
}
|
|
98
115
|
|
|
99
|
-
const unarchivedCards = cards.filter(c =>
|
|
116
|
+
const unarchivedCards = cards.filter(c => {
|
|
117
|
+
if (c.status === 'done') return false;
|
|
118
|
+
return c.capabilities ? c.capabilities.archive : !c.readOnly;
|
|
119
|
+
});
|
|
120
|
+
if (unarchivedCards.length === 0) {
|
|
121
|
+
setOpen(false);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
100
125
|
onPendingActionChange('archive');
|
|
101
126
|
try {
|
|
102
127
|
const responses = await Promise.all(unarchivedCards.map(card =>
|
|
@@ -116,6 +141,41 @@ function HeaderActionMenu({ cards, readOnly = false, isActionPending, onActionEr
|
|
|
116
141
|
await queryClient.invalidateQueries({ queryKey: ['sessions'] });
|
|
117
142
|
};
|
|
118
143
|
|
|
144
|
+
const handleRestoreAll = async (e: React.MouseEvent) => {
|
|
145
|
+
e.stopPropagation();
|
|
146
|
+
if (isActionPending) return;
|
|
147
|
+
onActionError(null);
|
|
148
|
+
if (hasMixedHosts) {
|
|
149
|
+
onActionError('Mixed-host restore is not supported');
|
|
150
|
+
setOpen(false);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const archivedCards = cards.filter(c => c.status === 'done' && (c.capabilities ? c.capabilities.archive : !c.readOnly));
|
|
155
|
+
if (archivedCards.length === 0) {
|
|
156
|
+
setOpen(false);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
onPendingActionChange('restore');
|
|
161
|
+
try {
|
|
162
|
+
const responses = await Promise.all(archivedCards.map(card =>
|
|
163
|
+
fetch(`/api/sessions/${card.id}/restore`, { method: 'POST' })
|
|
164
|
+
));
|
|
165
|
+
const failedResponse = responses.find((response) => !response.ok);
|
|
166
|
+
if (failedResponse) {
|
|
167
|
+
const errorBody = await failedResponse.json().catch(() => null);
|
|
168
|
+
onActionError(mapSessionActionError(errorBody, 'Failed to restore sessions'));
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
onActionError('Remote node is offline or unreachable.');
|
|
172
|
+
} finally {
|
|
173
|
+
onPendingActionChange(null);
|
|
174
|
+
}
|
|
175
|
+
setOpen(false);
|
|
176
|
+
await queryClient.invalidateQueries({ queryKey: ['sessions'] });
|
|
177
|
+
};
|
|
178
|
+
|
|
119
179
|
const handleDeleteAll = async (e: React.MouseEvent) => {
|
|
120
180
|
e.stopPropagation();
|
|
121
181
|
if (isActionPending) return;
|
|
@@ -126,10 +186,16 @@ function HeaderActionMenu({ cards, readOnly = false, isActionPending, onActionEr
|
|
|
126
186
|
return;
|
|
127
187
|
}
|
|
128
188
|
|
|
129
|
-
|
|
189
|
+
const deletableCards = cards.filter(c => c.capabilities ? c.capabilities.delete : !c.readOnly);
|
|
190
|
+
if (deletableCards.length === 0) {
|
|
191
|
+
setOpen(false);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!confirm(`Delete ${deletableCards.length} session(s)? This cannot be undone.`)) return;
|
|
130
196
|
onPendingActionChange('delete');
|
|
131
197
|
try {
|
|
132
|
-
const responses = await Promise.all(
|
|
198
|
+
const responses = await Promise.all(deletableCards.map(card =>
|
|
133
199
|
fetch(`/api/sessions/${card.id}/delete`, { method: 'POST' })
|
|
134
200
|
));
|
|
135
201
|
const failedResponse = responses.find((response) => !response.ok);
|
|
@@ -146,7 +212,8 @@ function HeaderActionMenu({ cards, readOnly = false, isActionPending, onActionEr
|
|
|
146
212
|
await queryClient.invalidateQueries({ queryKey: ['sessions'] });
|
|
147
213
|
};
|
|
148
214
|
|
|
149
|
-
|
|
215
|
+
const hasArchivableInBatch = !readOnlyMode && cards.some(c => (c.status !== 'done') && (c.capabilities ? c.capabilities.archive : !c.readOnly));
|
|
216
|
+
const hasRestorableInBatch = !readOnlyMode && cards.some(c => c.status === 'done' && (c.capabilities ? c.capabilities.archive : !c.readOnly));
|
|
150
217
|
|
|
151
218
|
return (
|
|
152
219
|
<div className="relative" ref={menuRef}>
|
|
@@ -163,7 +230,7 @@ function HeaderActionMenu({ cards, readOnly = false, isActionPending, onActionEr
|
|
|
163
230
|
</button>
|
|
164
231
|
{open && (
|
|
165
232
|
<div className="absolute right-0 top-6 w-32 rounded-md border border-gray-200 bg-white shadow-lg dark:border-zinc-700 dark:bg-zinc-900 z-20">
|
|
166
|
-
{
|
|
233
|
+
{hasArchivableInBatch && (
|
|
167
234
|
<button
|
|
168
235
|
type="button"
|
|
169
236
|
className="w-full text-left px-3 py-1.5 text-xs text-gray-700 hover:bg-gray-50 dark:text-gray-200 dark:hover:bg-zinc-800"
|
|
@@ -173,14 +240,26 @@ function HeaderActionMenu({ cards, readOnly = false, isActionPending, onActionEr
|
|
|
173
240
|
Archive all
|
|
174
241
|
</button>
|
|
175
242
|
)}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
243
|
+
{hasRestorableInBatch && (
|
|
244
|
+
<button
|
|
245
|
+
type="button"
|
|
246
|
+
className="w-full text-left px-3 py-1.5 text-xs text-gray-700 hover:bg-gray-50 dark:text-gray-200 dark:hover:bg-zinc-800"
|
|
247
|
+
onClick={handleRestoreAll}
|
|
248
|
+
disabled={isActionPending}
|
|
249
|
+
>
|
|
250
|
+
Restore all
|
|
251
|
+
</button>
|
|
252
|
+
)}
|
|
253
|
+
{canDeleteAny && (
|
|
254
|
+
<button
|
|
255
|
+
type="button"
|
|
256
|
+
className="w-full text-left px-3 py-1.5 text-xs text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-900/20"
|
|
257
|
+
onClick={handleDeleteAll}
|
|
258
|
+
disabled={isActionPending}
|
|
259
|
+
>
|
|
260
|
+
Delete all
|
|
261
|
+
</button>
|
|
262
|
+
)}
|
|
184
263
|
</div>
|
|
185
264
|
)}
|
|
186
265
|
</div>
|
|
@@ -188,7 +267,7 @@ function HeaderActionMenu({ cards, readOnly = false, isActionPending, onActionEr
|
|
|
188
267
|
}
|
|
189
268
|
|
|
190
269
|
// Hover-reveal action menu for each session row
|
|
191
|
-
function RowActionMenu({
|
|
270
|
+
function RowActionMenu({ card, isActionPending, onActionError, onPendingActionChange, readOnlyMode }: { card: KanbanCard; isActionPending: boolean; onActionError: (message: string | null) => void; onPendingActionChange: (value: 'open' | 'archive' | 'restore' | 'delete' | null) => void; readOnlyMode: boolean }) {
|
|
192
271
|
const queryClient = useQueryClient();
|
|
193
272
|
const [open, setOpen] = useState(false);
|
|
194
273
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
@@ -204,13 +283,18 @@ function RowActionMenu({ cardId, archived, isActionPending, onActionError, onPen
|
|
|
204
283
|
return () => document.removeEventListener('mousedown', handler);
|
|
205
284
|
}, [open]);
|
|
206
285
|
|
|
286
|
+
const canArchive = !readOnlyMode && (card.capabilities ? card.capabilities.archive : !card.readOnly);
|
|
287
|
+
const canDelete = !readOnlyMode && (card.capabilities ? card.capabilities.delete : !card.readOnly);
|
|
288
|
+
|
|
289
|
+
if (!canArchive && !canDelete) return null;
|
|
290
|
+
|
|
207
291
|
const handleArchive = async (e: React.MouseEvent) => {
|
|
208
292
|
e.stopPropagation();
|
|
209
293
|
if (isActionPending) return;
|
|
210
294
|
onActionError(null);
|
|
211
295
|
onPendingActionChange('archive');
|
|
212
296
|
try {
|
|
213
|
-
const response = await fetch(`/api/sessions/${
|
|
297
|
+
const response = await fetch(`/api/sessions/${card.id}/archive`, { method: 'POST' });
|
|
214
298
|
if (!response.ok) {
|
|
215
299
|
const errorBody = await response.json().catch(() => null);
|
|
216
300
|
onActionError(mapSessionActionError(errorBody, 'Failed to archive session'));
|
|
@@ -224,13 +308,33 @@ function RowActionMenu({ cardId, archived, isActionPending, onActionError, onPen
|
|
|
224
308
|
}
|
|
225
309
|
};
|
|
226
310
|
|
|
311
|
+
const handleRestore = async (e: React.MouseEvent) => {
|
|
312
|
+
e.stopPropagation();
|
|
313
|
+
if (isActionPending) return;
|
|
314
|
+
onActionError(null);
|
|
315
|
+
onPendingActionChange('restore');
|
|
316
|
+
try {
|
|
317
|
+
const response = await fetch(`/api/sessions/${card.id}/restore`, { method: 'POST' });
|
|
318
|
+
if (!response.ok) {
|
|
319
|
+
const errorBody = await response.json().catch(() => null);
|
|
320
|
+
onActionError(mapSessionActionError(errorBody, 'Failed to restore session'));
|
|
321
|
+
}
|
|
322
|
+
} catch {
|
|
323
|
+
onActionError('Remote node is offline or unreachable.');
|
|
324
|
+
} finally {
|
|
325
|
+
onPendingActionChange(null);
|
|
326
|
+
setOpen(false);
|
|
327
|
+
await queryClient.invalidateQueries({ queryKey: ['sessions'] });
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
227
331
|
const handleDelete = async (e: React.MouseEvent) => {
|
|
228
332
|
e.stopPropagation();
|
|
229
333
|
if (isActionPending) return;
|
|
230
334
|
onActionError(null);
|
|
231
335
|
onPendingActionChange('delete');
|
|
232
336
|
try {
|
|
233
|
-
const response = await fetch(`/api/sessions/${
|
|
337
|
+
const response = await fetch(`/api/sessions/${card.id}/delete`, { method: 'POST' });
|
|
234
338
|
if (!response.ok) {
|
|
235
339
|
const errorBody = await response.json().catch(() => null);
|
|
236
340
|
onActionError(mapSessionActionError(errorBody, 'Failed to delete session'));
|
|
@@ -259,7 +363,7 @@ function RowActionMenu({ cardId, archived, isActionPending, onActionError, onPen
|
|
|
259
363
|
</button>
|
|
260
364
|
{open && (
|
|
261
365
|
<div className="absolute right-0 top-6 w-28 rounded-md border border-gray-200 bg-white shadow-lg dark:border-zinc-700 dark:bg-zinc-900 z-20">
|
|
262
|
-
{
|
|
366
|
+
{(card.status !== 'done' && canArchive) && (
|
|
263
367
|
<button
|
|
264
368
|
type="button"
|
|
265
369
|
className="w-full text-left px-3 py-1.5 text-xs text-gray-700 hover:bg-gray-50 dark:text-gray-200 dark:hover:bg-zinc-800"
|
|
@@ -268,15 +372,27 @@ function RowActionMenu({ cardId, archived, isActionPending, onActionError, onPen
|
|
|
268
372
|
>
|
|
269
373
|
Archive
|
|
270
374
|
</button>
|
|
271
|
-
)
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
375
|
+
)}
|
|
376
|
+
{(card.status === 'done' && canArchive) && (
|
|
377
|
+
<button
|
|
378
|
+
type="button"
|
|
379
|
+
className="w-full text-left px-3 py-1.5 text-xs text-gray-700 hover:bg-gray-50 dark:text-gray-200 dark:hover:bg-zinc-800"
|
|
380
|
+
onClick={handleRestore}
|
|
381
|
+
disabled={isActionPending}
|
|
382
|
+
>
|
|
383
|
+
Restore
|
|
384
|
+
</button>
|
|
385
|
+
)}
|
|
386
|
+
{canDelete && (
|
|
387
|
+
<button
|
|
388
|
+
type="button"
|
|
389
|
+
className="w-full text-left px-3 py-1.5 text-xs text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-900/20"
|
|
390
|
+
onClick={handleDelete}
|
|
391
|
+
disabled={isActionPending}
|
|
392
|
+
>
|
|
393
|
+
Delete
|
|
394
|
+
</button>
|
|
395
|
+
)}
|
|
280
396
|
</div>
|
|
281
397
|
)}
|
|
282
398
|
</div>
|
|
@@ -284,10 +400,10 @@ function RowActionMenu({ cardId, archived, isActionPending, onActionError, onPen
|
|
|
284
400
|
}
|
|
285
401
|
|
|
286
402
|
// Session row with expandable subagent children
|
|
287
|
-
function SessionRow({ card, isLast,
|
|
403
|
+
function SessionRow({ card, isLast, isActionPending, onActionError, onPendingActionChange, readOnlyMode }: { card: KanbanCard; isLast: boolean; isActionPending: boolean; onActionError: (message: string | null) => void; onPendingActionChange: (value: 'open' | 'archive' | 'restore' | 'delete' | null) => void; readOnlyMode: boolean }) {
|
|
288
404
|
const [expanded, setExpanded] = useState(true);
|
|
289
405
|
const visibleChildren = (card.children || []).filter(
|
|
290
|
-
|
|
406
|
+
shouldShowChildSession
|
|
291
407
|
);
|
|
292
408
|
const hasChildren = visibleChildren.length > 0;
|
|
293
409
|
const rowTitle = buildTooltipTitle([
|
|
@@ -319,7 +435,7 @@ function SessionRow({ card, isLast, readOnly = false, isActionPending, onActionE
|
|
|
319
435
|
</svg>
|
|
320
436
|
</button>
|
|
321
437
|
)}
|
|
322
|
-
<StatusDot status={card.opencodeStatus} waitingForUser={card.waitingForUser} />
|
|
438
|
+
<StatusDot status={card.opencodeStatus} waitingForUser={card.waitingForUser} provider={card.provider} />
|
|
323
439
|
<span className="text-sm text-gray-700 dark:text-gray-300 truncate flex-1 min-w-0">
|
|
324
440
|
{card.title || 'Untitled Session'}
|
|
325
441
|
</span>
|
|
@@ -334,11 +450,9 @@ function SessionRow({ card, isLast, readOnly = false, isActionPending, onActionE
|
|
|
334
450
|
{formatRelativeTime(card.updatedAt)}
|
|
335
451
|
</span>
|
|
336
452
|
{/* Action menu: hidden by default, visible on hover */}
|
|
337
|
-
|
|
338
|
-
<
|
|
339
|
-
|
|
340
|
-
</div>
|
|
341
|
-
) : null}
|
|
453
|
+
<div className="hidden group-hover/row:flex flex-shrink-0">
|
|
454
|
+
<RowActionMenu card={card} isActionPending={isActionPending} onActionError={onActionError} onPendingActionChange={onPendingActionChange} readOnlyMode={readOnlyMode} />
|
|
455
|
+
</div>
|
|
342
456
|
</div>
|
|
343
457
|
{/* Subagent children */}
|
|
344
458
|
{hasChildren && expanded && (
|
|
@@ -353,7 +467,7 @@ function SessionRow({ card, isLast, readOnly = false, isActionPending, onActionE
|
|
|
353
467
|
<span className="text-gray-300 dark:text-zinc-600 text-xs flex-shrink-0 font-mono leading-none">
|
|
354
468
|
{i === visibleChildren.length - 1 ? '└' : '├'}
|
|
355
469
|
</span>
|
|
356
|
-
<StatusDot status={child.realTimeStatus} waitingForUser={child.waitingForUser} />
|
|
470
|
+
<StatusDot status={child.realTimeStatus} waitingForUser={child.waitingForUser} provider={card.provider} />
|
|
357
471
|
<span className="text-xs text-gray-500 dark:text-gray-400 truncate flex-1 min-w-0">
|
|
358
472
|
{child.title || 'Subagent'}
|
|
359
473
|
</span>
|
|
@@ -365,15 +479,36 @@ function SessionRow({ card, isLast, readOnly = false, isActionPending, onActionE
|
|
|
365
479
|
);
|
|
366
480
|
}
|
|
367
481
|
|
|
368
|
-
export function ProjectCard({ projectName, branch, cards, readOnly
|
|
369
|
-
const
|
|
370
|
-
|
|
482
|
+
export function ProjectCard({ projectName, branch, cards, readOnly = false, hostLabel: _hostLabel, multipleHostsEnabled }: ProjectCardProps) {
|
|
483
|
+
const childSessionIds = useMemo(() => {
|
|
484
|
+
const ids = new Set<string>();
|
|
485
|
+
for (const card of cards) {
|
|
486
|
+
for (const child of card.children || []) {
|
|
487
|
+
ids.add(child.id);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return ids;
|
|
491
|
+
}, [cards]);
|
|
492
|
+
|
|
493
|
+
const deduplicatedCards = useMemo(() => {
|
|
494
|
+
return cards.filter((card) => {
|
|
495
|
+
if (!childSessionIds.has(card.id)) {
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return (card.children?.length ?? 0) > 0;
|
|
500
|
+
});
|
|
501
|
+
}, [cards, childSessionIds]);
|
|
502
|
+
|
|
503
|
+
const firstCard = deduplicatedCards[0] ?? cards[0];
|
|
504
|
+
const actionableCards = deduplicatedCards;
|
|
505
|
+
const primaryCard = actionableCards[0] ?? firstCard;
|
|
371
506
|
const hostLabel = _hostLabel ?? firstCard?.hostLabel;
|
|
372
|
-
const hostId = firstCard?.hostId;
|
|
507
|
+
const hostId = primaryCard?.hostId ?? firstCard?.hostId;
|
|
373
508
|
const showHostBadge = hostLabel && (multipleHostsEnabled || hostLabel !== 'Local');
|
|
374
509
|
const hostAccentClass = getHostAccentTextClass(hostId, hostLabel);
|
|
375
510
|
const [actionError, setActionError] = useState<string | null>(null);
|
|
376
|
-
const [pendingAction, setPendingAction] = useState<'open' | 'archive' | 'delete' | null>(null);
|
|
511
|
+
const [pendingAction, setPendingAction] = useState<'open' | 'archive' | 'restore' | 'delete' | null>(null);
|
|
377
512
|
const [openTool, setOpenTool] = useState(() => {
|
|
378
513
|
if (typeof window === 'undefined') return 'vscode';
|
|
379
514
|
return window.localStorage.getItem('vibepulse:open-tool') || 'vscode';
|
|
@@ -400,8 +535,8 @@ export function ProjectCard({ projectName, branch, cards, readOnly: _readOnly, h
|
|
|
400
535
|
return storedHost;
|
|
401
536
|
}
|
|
402
537
|
|
|
403
|
-
const firstRemoteBaseUrl =
|
|
404
|
-
if (firstRemoteBaseUrl &&
|
|
538
|
+
const firstRemoteBaseUrl = primaryCard?.hostBaseUrl;
|
|
539
|
+
if (firstRemoteBaseUrl && primaryCard?.hostId !== 'local') {
|
|
405
540
|
try {
|
|
406
541
|
return new URL(firstRemoteBaseUrl).hostname;
|
|
407
542
|
} catch {
|
|
@@ -414,20 +549,27 @@ export function ProjectCard({ projectName, branch, cards, readOnly: _readOnly, h
|
|
|
414
549
|
}
|
|
415
550
|
|
|
416
551
|
return '';
|
|
417
|
-
}, [
|
|
552
|
+
}, [primaryCard?.hostBaseUrl, primaryCard?.hostId]);
|
|
418
553
|
const isActionPending = pendingAction !== null;
|
|
554
|
+
const isReadOnly = !!readOnly;
|
|
419
555
|
const pendingActionLabel = pendingAction === 'open'
|
|
420
556
|
? 'Opening…'
|
|
421
557
|
: pendingAction === 'archive'
|
|
422
558
|
? 'Archiving…'
|
|
423
|
-
: pendingAction === '
|
|
424
|
-
? '
|
|
425
|
-
:
|
|
426
|
-
|
|
559
|
+
: pendingAction === 'restore'
|
|
560
|
+
? 'Restoring…'
|
|
561
|
+
: pendingAction === 'delete'
|
|
562
|
+
? 'Deleting…'
|
|
563
|
+
: null;
|
|
564
|
+
const isRemoteProjectCard = !!primaryCard && primaryCard.hostId !== undefined && primaryCard.hostId !== 'local';
|
|
427
565
|
const isConfigPendingForRemoteOpen = isRemoteProjectCard && isConfigLoading && !config;
|
|
428
566
|
const isConfigUnavailableForRemoteOpen = isRemoteProjectCard && !config && !isConfigLoading;
|
|
429
567
|
|
|
430
568
|
const handleOpenProject = () => {
|
|
569
|
+
if (isReadOnly) {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
|
|
431
573
|
if (isActionPending) {
|
|
432
574
|
return;
|
|
433
575
|
}
|
|
@@ -441,24 +583,36 @@ export function ProjectCard({ projectName, branch, cards, readOnly: _readOnly, h
|
|
|
441
583
|
return;
|
|
442
584
|
}
|
|
443
585
|
|
|
444
|
-
const directory =
|
|
586
|
+
const directory = primaryCard?.directory;
|
|
445
587
|
if (!directory) return;
|
|
446
588
|
setActionError(null);
|
|
447
|
-
const firstProjectCard =
|
|
589
|
+
const firstProjectCard = primaryCard;
|
|
448
590
|
const openEditorTargetMode = config?.vibepulse?.openEditorTargetMode === 'hub' ? 'hub' : 'remote';
|
|
449
591
|
const isRemoteProject = firstProjectCard?.hostId !== undefined && firstProjectCard.hostId !== 'local';
|
|
592
|
+
const canOpenEditor = firstProjectCard?.capabilities ? firstProjectCard.capabilities.openEditor : true;
|
|
450
593
|
|
|
451
594
|
if (isRemoteProject && openEditorTargetMode === 'hub' && openTool === 'antigravity') {
|
|
452
595
|
setActionError('Antigravity does not support hub-mode remote opens. Use VS Code or switch target mode to Remote node.');
|
|
453
596
|
return;
|
|
454
597
|
}
|
|
455
598
|
|
|
456
|
-
|
|
599
|
+
if (isRemoteProject && openEditorTargetMode === 'remote' && !canOpenEditor && openTool === 'antigravity') {
|
|
600
|
+
setActionError('Antigravity cannot open remote sessions without remote editor support. Use VS Code.');
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const useRemoteSshTarget =
|
|
605
|
+
isRemoteProject
|
|
606
|
+
&& openTool === 'vscode'
|
|
607
|
+
&& (
|
|
608
|
+
openEditorTargetMode === 'hub'
|
|
609
|
+
|| (openEditorTargetMode === 'remote' && !canOpenEditor)
|
|
610
|
+
);
|
|
457
611
|
const target = buildEditorUri(openTool === 'antigravity' ? 'antigravity' : 'vscode', directory, {
|
|
458
612
|
remoteSshHost: useRemoteSshTarget ? remoteSshHost : null,
|
|
459
613
|
});
|
|
460
614
|
|
|
461
|
-
if (isRemoteProject && openEditorTargetMode === 'remote') {
|
|
615
|
+
if (isRemoteProject && openEditorTargetMode === 'remote' && canOpenEditor) {
|
|
462
616
|
void (async () => {
|
|
463
617
|
setPendingAction('open');
|
|
464
618
|
try {
|
|
@@ -498,42 +652,40 @@ export function ProjectCard({ projectName, branch, cards, readOnly: _readOnly, h
|
|
|
498
652
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
|
|
499
653
|
</svg>
|
|
500
654
|
</div>
|
|
501
|
-
<
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
</div>
|
|
515
|
-
)}
|
|
655
|
+
<div className="flex-1 min-w-0 flex items-center gap-2">
|
|
656
|
+
<span className="text-sm font-semibold text-gray-800 dark:text-gray-200 truncate">
|
|
657
|
+
{projectName}
|
|
658
|
+
</span>
|
|
659
|
+
</div>
|
|
660
|
+
<div className="flex items-center flex-shrink-0 bg-gray-100 dark:bg-zinc-700/50 rounded-full h-6 border border-gray-200/50 dark:border-zinc-700">
|
|
661
|
+
{deduplicatedCards.length > 1 && (
|
|
662
|
+
<span className="text-[11px] font-medium text-gray-500 dark:text-gray-400 pl-2.5 pr-1">
|
|
663
|
+
{deduplicatedCards.length}
|
|
664
|
+
</span>
|
|
665
|
+
)}
|
|
666
|
+
<div className={`${deduplicatedCards.length > 1 ? 'pr-0.5' : 'px-0.5'}`}>
|
|
667
|
+
<HeaderActionMenu cards={actionableCards} isActionPending={isActionPending} onActionError={setActionError} onPendingActionChange={setPendingAction} readOnlyMode={isReadOnly} />
|
|
516
668
|
</div>
|
|
517
|
-
|
|
669
|
+
</div>
|
|
518
670
|
</div>
|
|
519
671
|
|
|
520
672
|
{/* Session rows */}
|
|
521
673
|
<div className="border-t border-gray-100 dark:border-zinc-700/50">
|
|
522
|
-
{
|
|
674
|
+
{deduplicatedCards.map((card, index) => (
|
|
523
675
|
<SessionRow
|
|
524
676
|
key={card.id}
|
|
525
677
|
card={card}
|
|
526
|
-
isLast={index ===
|
|
527
|
-
readOnly={readOnly}
|
|
678
|
+
isLast={index === deduplicatedCards.length - 1}
|
|
528
679
|
isActionPending={isActionPending}
|
|
529
680
|
onActionError={setActionError}
|
|
530
681
|
onPendingActionChange={setPendingAction}
|
|
682
|
+
readOnlyMode={isReadOnly}
|
|
531
683
|
/>
|
|
532
684
|
))}
|
|
533
685
|
</div>
|
|
534
686
|
|
|
535
687
|
{/* Footer */}
|
|
536
|
-
{
|
|
688
|
+
{(branch || primaryCard?.directory) && (
|
|
537
689
|
<div className="flex items-center justify-between gap-2 px-3 py-1.5 border-t border-gray-100 dark:border-zinc-700/50 bg-gray-50/50 dark:bg-zinc-800/50">
|
|
538
690
|
<div className="min-w-0 flex-1 text-[10px] text-gray-400 dark:text-gray-500 truncate">
|
|
539
691
|
{branch ? (
|
|
@@ -550,7 +702,7 @@ export function ProjectCard({ projectName, branch, cards, readOnly: _readOnly, h
|
|
|
550
702
|
window.localStorage.setItem('vibepulse:open-tool', e.target.value);
|
|
551
703
|
}}
|
|
552
704
|
title="Select open tool"
|
|
553
|
-
disabled={isActionPending || isConfigPendingForRemoteOpen || isConfigUnavailableForRemoteOpen}
|
|
705
|
+
disabled={isReadOnly || isActionPending || isConfigPendingForRemoteOpen || isConfigUnavailableForRemoteOpen}
|
|
554
706
|
>
|
|
555
707
|
<option value="vscode">VSCode</option>
|
|
556
708
|
<option value="antigravity">Antigravity</option>
|
|
@@ -560,7 +712,7 @@ export function ProjectCard({ projectName, branch, cards, readOnly: _readOnly, h
|
|
|
560
712
|
onClick={handleOpenProject}
|
|
561
713
|
className="flex items-center gap-1 px-2 py-0.5 rounded-md text-[10px] font-medium text-gray-500 hover:text-blue-600 hover:bg-blue-50 disabled:cursor-not-allowed disabled:opacity-50 dark:text-gray-400 dark:hover:text-blue-400 dark:hover:bg-blue-900/20 transition-colors"
|
|
562
714
|
title="Open project"
|
|
563
|
-
disabled={isActionPending || isConfigPendingForRemoteOpen || isConfigUnavailableForRemoteOpen}
|
|
715
|
+
disabled={isReadOnly || isActionPending || isConfigPendingForRemoteOpen || isConfigUnavailableForRemoteOpen}
|
|
564
716
|
>
|
|
565
717
|
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
566
718
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|