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
|
@@ -196,7 +196,7 @@ describe('SessionCard', () => {
|
|
|
196
196
|
expect((fetchMock.mock.calls as unknown as Array<[RequestInfo | URL, RequestInit | undefined]>).filter(([, init]) => init?.method === 'POST')).toHaveLength(0);
|
|
197
197
|
});
|
|
198
198
|
|
|
199
|
-
it('calls the hub open-editor route for remote mode
|
|
199
|
+
it('calls the hub open-editor route for remote mode sessions that support openEditor', async () => {
|
|
200
200
|
const queryClient = new QueryClient({
|
|
201
201
|
defaultOptions: {
|
|
202
202
|
queries: { retry: false },
|
|
@@ -213,7 +213,14 @@ describe('SessionCard', () => {
|
|
|
213
213
|
|
|
214
214
|
render(
|
|
215
215
|
<QueryClientProvider client={queryClient}>
|
|
216
|
-
<SessionCard card={createCard(
|
|
216
|
+
<SessionCard card={createCard({
|
|
217
|
+
capabilities: {
|
|
218
|
+
openProject: true,
|
|
219
|
+
openEditor: true,
|
|
220
|
+
archive: true,
|
|
221
|
+
delete: true,
|
|
222
|
+
},
|
|
223
|
+
})} />
|
|
217
224
|
</QueryClientProvider>
|
|
218
225
|
);
|
|
219
226
|
|
|
@@ -234,6 +241,84 @@ describe('SessionCard', () => {
|
|
|
234
241
|
expect(window.location.assign).not.toHaveBeenCalled();
|
|
235
242
|
});
|
|
236
243
|
|
|
244
|
+
it('falls back to file open when remote mode is selected but openEditor capability is unsupported', async () => {
|
|
245
|
+
const queryClient = new QueryClient({
|
|
246
|
+
defaultOptions: {
|
|
247
|
+
queries: { retry: false },
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
queryClient.setQueryData(['opencode-config'], { vibepulse: { openEditorTargetMode: 'remote' } });
|
|
251
|
+
const fetchMock = vi.fn(async () => new Response(JSON.stringify({ success: true }), {
|
|
252
|
+
status: 200,
|
|
253
|
+
headers: { 'Content-Type': 'application/json' },
|
|
254
|
+
}));
|
|
255
|
+
Object.defineProperty(globalThis, 'fetch', { value: fetchMock, configurable: true });
|
|
256
|
+
|
|
257
|
+
render(
|
|
258
|
+
<QueryClientProvider client={queryClient}>
|
|
259
|
+
<SessionCard card={createCard({
|
|
260
|
+
provider: 'claude-code',
|
|
261
|
+
readOnly: true,
|
|
262
|
+
capabilities: {
|
|
263
|
+
openProject: true,
|
|
264
|
+
openEditor: false,
|
|
265
|
+
archive: false,
|
|
266
|
+
delete: false,
|
|
267
|
+
},
|
|
268
|
+
})} />
|
|
269
|
+
</QueryClientProvider>
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
await screen.findByText('Remote Session');
|
|
273
|
+
await waitFor(() => {
|
|
274
|
+
expect(screen.getByRole('button', { name: /remote session/i })).not.toBeDisabled();
|
|
275
|
+
});
|
|
276
|
+
fireEvent.doubleClick(screen.getByRole('button', { name: /remote session/i }));
|
|
277
|
+
|
|
278
|
+
expect(window.location.assign).toHaveBeenCalledWith('vscode://vscode-remote/ssh-remote+node-1.test/tmp/demo');
|
|
279
|
+
expect((fetchMock.mock.calls as unknown as Array<[RequestInfo | URL, RequestInit | undefined]>).filter(([, init]) => init?.method === 'POST')).toHaveLength(0);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('shows actionable error when Antigravity is selected for remote fallback without openEditor support', async () => {
|
|
283
|
+
window.localStorage.setItem('vibepulse:open-tool', 'antigravity');
|
|
284
|
+
const queryClient = new QueryClient({
|
|
285
|
+
defaultOptions: {
|
|
286
|
+
queries: { retry: false },
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
queryClient.setQueryData(['opencode-config'], { vibepulse: { openEditorTargetMode: 'remote' } });
|
|
290
|
+
const fetchMock = vi.fn(async () => new Response(JSON.stringify({ success: true }), {
|
|
291
|
+
status: 200,
|
|
292
|
+
headers: { 'Content-Type': 'application/json' },
|
|
293
|
+
}));
|
|
294
|
+
Object.defineProperty(globalThis, 'fetch', { value: fetchMock, configurable: true });
|
|
295
|
+
|
|
296
|
+
render(
|
|
297
|
+
<QueryClientProvider client={queryClient}>
|
|
298
|
+
<SessionCard card={createCard({
|
|
299
|
+
provider: 'claude-code',
|
|
300
|
+
readOnly: true,
|
|
301
|
+
capabilities: {
|
|
302
|
+
openProject: true,
|
|
303
|
+
openEditor: false,
|
|
304
|
+
archive: false,
|
|
305
|
+
delete: false,
|
|
306
|
+
},
|
|
307
|
+
})} />
|
|
308
|
+
</QueryClientProvider>
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
await screen.findByText('Remote Session');
|
|
312
|
+
await waitFor(() => {
|
|
313
|
+
expect(screen.getByRole('button', { name: /remote session/i })).not.toBeDisabled();
|
|
314
|
+
});
|
|
315
|
+
fireEvent.doubleClick(screen.getByRole('button', { name: /remote session/i }));
|
|
316
|
+
|
|
317
|
+
expect(await screen.findByText('Antigravity cannot open remote sessions without remote editor support. Use VS Code.')).toBeTruthy();
|
|
318
|
+
expect(window.location.assign).not.toHaveBeenCalled();
|
|
319
|
+
expect((fetchMock.mock.calls as unknown as Array<[RequestInfo | URL, RequestInit | undefined]>).filter(([, init]) => init?.method === 'POST')).toHaveLength(0);
|
|
320
|
+
});
|
|
321
|
+
|
|
237
322
|
it('shows an explicit loading state while a remote open request is in flight', async () => {
|
|
238
323
|
const queryClient = new QueryClient({
|
|
239
324
|
defaultOptions: {
|
|
@@ -515,6 +600,91 @@ describe('SessionCard', () => {
|
|
|
515
600
|
expect(await screen.findByText('Session was not found.')).toBeTruthy();
|
|
516
601
|
});
|
|
517
602
|
|
|
603
|
+
it('respects capabilities for action visibility over readOnly alone', async () => {
|
|
604
|
+
const capabilityCard = createCard({
|
|
605
|
+
readOnly: true,
|
|
606
|
+
capabilities: {
|
|
607
|
+
openProject: true,
|
|
608
|
+
openEditor: true,
|
|
609
|
+
archive: true,
|
|
610
|
+
delete: false,
|
|
611
|
+
},
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
const user = userEvent.setup();
|
|
615
|
+
renderWithProviders(<SessionCard card={capabilityCard} />);
|
|
616
|
+
|
|
617
|
+
await screen.findByText('Remote Session');
|
|
618
|
+
|
|
619
|
+
await user.click(screen.getByTitle('Actions'));
|
|
620
|
+
|
|
621
|
+
expect(screen.getByRole('button', { name: 'Archive' })).toBeTruthy();
|
|
622
|
+
expect(screen.queryByRole('button', { name: 'Delete' })).toBeNull();
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it('shows no actions menu if both archive and delete capabilities are false', async () => {
|
|
626
|
+
const capabilityCard = createCard({
|
|
627
|
+
capabilities: {
|
|
628
|
+
openProject: true,
|
|
629
|
+
openEditor: true,
|
|
630
|
+
archive: false,
|
|
631
|
+
delete: false,
|
|
632
|
+
},
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
renderWithProviders(<SessionCard card={capabilityCard} />);
|
|
636
|
+
|
|
637
|
+
await screen.findByText('Remote Session');
|
|
638
|
+
|
|
639
|
+
expect(screen.queryByTitle('Actions')).toBeNull();
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
it('shows archive and delete actions for Claude sessions when capabilities allow them', async () => {
|
|
643
|
+
renderWithProviders(<SessionCard card={createCard({
|
|
644
|
+
provider: 'claude-code',
|
|
645
|
+
readOnly: true,
|
|
646
|
+
id: 'local:claude~550e8400-e29b-41d4-a716-446655440000',
|
|
647
|
+
rawSessionId: '550e8400-e29b-41d4-a716-446655440000',
|
|
648
|
+
capabilities: {
|
|
649
|
+
openProject: true,
|
|
650
|
+
openEditor: false,
|
|
651
|
+
archive: true,
|
|
652
|
+
delete: true,
|
|
653
|
+
},
|
|
654
|
+
})} />);
|
|
655
|
+
|
|
656
|
+
expect(screen.getByTitle('Actions')).toBeTruthy();
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
it('shows restore for archived Claude sessions', async () => {
|
|
660
|
+
const fetchMock = vi.fn(async () => new Response(JSON.stringify({ success: true }), {
|
|
661
|
+
status: 200,
|
|
662
|
+
headers: { 'Content-Type': 'application/json' },
|
|
663
|
+
}));
|
|
664
|
+
Object.defineProperty(globalThis, 'fetch', { value: fetchMock, configurable: true });
|
|
665
|
+
|
|
666
|
+
renderWithProviders(<SessionCard card={createCard({
|
|
667
|
+
provider: 'claude-code',
|
|
668
|
+
readOnly: true,
|
|
669
|
+
status: 'done',
|
|
670
|
+
id: 'local:claude~550e8400-e29b-41d4-a716-446655440000',
|
|
671
|
+
rawSessionId: '550e8400-e29b-41d4-a716-446655440000',
|
|
672
|
+
capabilities: {
|
|
673
|
+
openProject: true,
|
|
674
|
+
openEditor: false,
|
|
675
|
+
archive: true,
|
|
676
|
+
delete: true,
|
|
677
|
+
},
|
|
678
|
+
})} />);
|
|
679
|
+
|
|
680
|
+
fireEvent.click(screen.getByTitle('Actions'));
|
|
681
|
+
fireEvent.click(screen.getByText('Restore'));
|
|
682
|
+
|
|
683
|
+
await waitFor(() => {
|
|
684
|
+
expect(fetchMock).toHaveBeenCalledWith('/api/sessions/local:claude~550e8400-e29b-41d4-a716-446655440000/restore', expect.objectContaining({ method: 'POST' }));
|
|
685
|
+
});
|
|
686
|
+
});
|
|
687
|
+
|
|
518
688
|
it('shows a loading-settings state before remote open mode is hydrated', async () => {
|
|
519
689
|
const fetchMock = vi.fn(async () => new Promise<Response>(() => {}));
|
|
520
690
|
Object.defineProperty(globalThis, 'fetch', { value: fetchMock, configurable: true });
|
|
@@ -563,4 +733,85 @@ describe('SessionCard', () => {
|
|
|
563
733
|
|
|
564
734
|
expect(await screen.findByText('Remote node is offline or unreachable.')).toBeTruthy();
|
|
565
735
|
});
|
|
736
|
+
|
|
737
|
+
it('shows recently-updated idle children so delegated subagents remain visible', () => {
|
|
738
|
+
const now = Date.now();
|
|
739
|
+
renderWithProviders(
|
|
740
|
+
<SessionCard
|
|
741
|
+
card={createCard({
|
|
742
|
+
hostId: 'local',
|
|
743
|
+
hostLabel: 'Local',
|
|
744
|
+
hostKind: 'local',
|
|
745
|
+
hostBaseUrl: undefined,
|
|
746
|
+
children: [
|
|
747
|
+
{
|
|
748
|
+
id: 'child-recent-idle',
|
|
749
|
+
title: 'Recent Idle Child',
|
|
750
|
+
realTimeStatus: 'idle',
|
|
751
|
+
waitingForUser: false,
|
|
752
|
+
createdAt: now - 120_000,
|
|
753
|
+
updatedAt: now - 20_000,
|
|
754
|
+
},
|
|
755
|
+
],
|
|
756
|
+
})}
|
|
757
|
+
/>
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
expect(screen.getByText('Recent Idle Child')).toBeTruthy();
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
it('hides stale idle children once they are older than the recent visibility window', () => {
|
|
764
|
+
const now = Date.now();
|
|
765
|
+
renderWithProviders(
|
|
766
|
+
<SessionCard
|
|
767
|
+
card={createCard({
|
|
768
|
+
hostId: 'local',
|
|
769
|
+
hostLabel: 'Local',
|
|
770
|
+
hostKind: 'local',
|
|
771
|
+
hostBaseUrl: undefined,
|
|
772
|
+
children: [
|
|
773
|
+
{
|
|
774
|
+
id: 'child-stale-idle',
|
|
775
|
+
title: 'Stale Idle Child',
|
|
776
|
+
realTimeStatus: 'idle',
|
|
777
|
+
waitingForUser: false,
|
|
778
|
+
createdAt: now - 180_000,
|
|
779
|
+
updatedAt: now - 120_000,
|
|
780
|
+
},
|
|
781
|
+
],
|
|
782
|
+
})}
|
|
783
|
+
/>
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
expect(screen.queryByText('Stale Idle Child')).toBeNull();
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
describe('SessionCard Provider Visuals', () => {
|
|
790
|
+
it('does not show a provider marker for OpenCode sessions', () => {
|
|
791
|
+
renderWithProviders(<SessionCard card={createCard({ provider: 'opencode' })} />);
|
|
792
|
+
expect(screen.queryByTitle('Provider: OpenCode')).toBeNull();
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it('renders Claude status as a diamond instead of showing a separate provider marker', () => {
|
|
796
|
+
renderWithProviders(<SessionCard card={createCard({ provider: 'claude-code' })} />);
|
|
797
|
+
expect(screen.getByTitle('Idle').className).toContain('rotate-45');
|
|
798
|
+
expect(screen.getByTitle('Idle').className).toContain('h-[9px]');
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
it('does not render transcript affordances for Claude cards even when transcript metadata exists', () => {
|
|
802
|
+
const claudeCardWithTranscript: KanbanCard & { hasTranscript: boolean } = {
|
|
803
|
+
...createCard({
|
|
804
|
+
provider: 'claude-code',
|
|
805
|
+
readOnly: true,
|
|
806
|
+
rawSessionId: '550e8400-e29b-41d4-a716-446655440000',
|
|
807
|
+
id: 'local:claude~550e8400-e29b-41d4-a716-446655440000',
|
|
808
|
+
}),
|
|
809
|
+
hasTranscript: true,
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
renderWithProviders(<SessionCard card={claudeCardWithTranscript} />);
|
|
813
|
+
|
|
814
|
+
expect(screen.queryByRole('button', { name: /transcript/i })).toBeNull();
|
|
815
|
+
expect(screen.queryByText(/transcript/i)).toBeNull();
|
|
816
|
+
});
|
|
566
817
|
});
|
|
@@ -17,6 +17,16 @@ interface SessionCardProps {
|
|
|
17
17
|
card: KanbanCard;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
const RECENT_IDLE_CHILD_VISIBILITY_WINDOW_MS = 60_000;
|
|
21
|
+
|
|
22
|
+
function shouldShowChildSession(child: { realTimeStatus: string; waitingForUser: boolean; updatedAt: number }): boolean {
|
|
23
|
+
if (child.realTimeStatus !== 'idle' || child.waitingForUser) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return Date.now() - child.updatedAt <= RECENT_IDLE_CHILD_VISIBILITY_WINDOW_MS;
|
|
28
|
+
}
|
|
29
|
+
|
|
20
30
|
function formatRelativeTime(timestamp: number): string {
|
|
21
31
|
const diffMs = Date.now() - timestamp;
|
|
22
32
|
const diffMins = Math.floor(diffMs / (1000 * 60));
|
|
@@ -30,13 +40,16 @@ function formatRelativeTime(timestamp: number): string {
|
|
|
30
40
|
}
|
|
31
41
|
|
|
32
42
|
// Status indicator component
|
|
33
|
-
function StatusIndicator({ status, waitingForUser }: { status: string; waitingForUser: boolean }) {
|
|
43
|
+
function StatusIndicator({ status, waitingForUser, provider }: { status: string; waitingForUser: boolean; provider?: string }) {
|
|
44
|
+
const isClaudeProvider = provider === 'claude-code';
|
|
45
|
+
const shapeClass = isClaudeProvider ? 'rotate-45 rounded-[1px]' : 'rounded-full';
|
|
46
|
+
const dotSizeClass = isClaudeProvider ? 'h-[9px] w-[9px]' : 'h-2.5 w-2.5';
|
|
34
47
|
if (waitingForUser) {
|
|
35
48
|
return (
|
|
36
49
|
<div className="flex items-center gap-1.5 text-amber-600 dark:text-amber-400">
|
|
37
|
-
<span className=
|
|
38
|
-
<span className=
|
|
39
|
-
<span className=
|
|
50
|
+
<span className={`relative flex ${dotSizeClass}`}>
|
|
51
|
+
<span className={`animate-ping absolute inline-flex h-full w-full ${shapeClass} bg-amber-400 opacity-75`} title="Waiting"></span>
|
|
52
|
+
<span className={`relative inline-flex ${dotSizeClass} ${shapeClass} bg-amber-500`}></span>
|
|
40
53
|
</span>
|
|
41
54
|
<span className="text-xs font-medium">Waiting</span>
|
|
42
55
|
</div>
|
|
@@ -46,9 +59,9 @@ function StatusIndicator({ status, waitingForUser }: { status: string; waitingFo
|
|
|
46
59
|
case 'busy':
|
|
47
60
|
return (
|
|
48
61
|
<div className="flex items-center gap-1.5 text-emerald-600 dark:text-emerald-400">
|
|
49
|
-
<span className=
|
|
50
|
-
<span className=
|
|
51
|
-
<span className=
|
|
62
|
+
<span className={`relative flex ${dotSizeClass}`}>
|
|
63
|
+
<span className={`animate-pulse absolute inline-flex h-full w-full ${shapeClass} bg-emerald-400 opacity-75`} title="Running"></span>
|
|
64
|
+
<span className={`relative inline-flex ${dotSizeClass} ${shapeClass} bg-emerald-500`}></span>
|
|
52
65
|
</span>
|
|
53
66
|
<span className="text-xs font-medium">Running</span>
|
|
54
67
|
</div>
|
|
@@ -56,9 +69,9 @@ function StatusIndicator({ status, waitingForUser }: { status: string; waitingFo
|
|
|
56
69
|
case 'retry':
|
|
57
70
|
return (
|
|
58
71
|
<div className="flex items-center gap-1.5 text-red-600 dark:text-red-400">
|
|
59
|
-
<span className=
|
|
60
|
-
<span className=
|
|
61
|
-
<span className=
|
|
72
|
+
<span className={`relative flex ${dotSizeClass}`}>
|
|
73
|
+
<span className={`animate-ping absolute inline-flex h-full w-full ${shapeClass} bg-red-400 opacity-75`} title="Retrying"></span>
|
|
74
|
+
<span className={`relative inline-flex ${dotSizeClass} ${shapeClass} bg-red-500`}></span>
|
|
62
75
|
</span>
|
|
63
76
|
<span className="text-xs font-medium">Retrying</span>
|
|
64
77
|
</div>
|
|
@@ -67,7 +80,7 @@ function StatusIndicator({ status, waitingForUser }: { status: string; waitingFo
|
|
|
67
80
|
default:
|
|
68
81
|
return (
|
|
69
82
|
<div className="flex items-center gap-1.5 text-gray-500 dark:text-gray-400">
|
|
70
|
-
<span className=
|
|
83
|
+
<span className={`inline-flex ${dotSizeClass} bg-gray-400 ${shapeClass}`} title="Idle"></span>
|
|
71
84
|
<span className="text-xs font-medium">Idle</span>
|
|
72
85
|
</div>
|
|
73
86
|
);
|
|
@@ -80,7 +93,7 @@ export function SessionCard({ card }: SessionCardProps) {
|
|
|
80
93
|
const [remoteSshHost, setRemoteSshHost] = useState('');
|
|
81
94
|
const [actionOpen, setActionOpen] = useState(false);
|
|
82
95
|
const [actionError, setActionError] = useState<string | null>(null);
|
|
83
|
-
const [pendingAction, setPendingAction] = useState<'open' | 'archive' | 'delete' | null>(null);
|
|
96
|
+
const [pendingAction, setPendingAction] = useState<'open' | 'archive' | 'restore' | 'delete' | null>(null);
|
|
84
97
|
const actionMenuRef = useRef<HTMLDivElement>(null);
|
|
85
98
|
const { data: config, isLoading: isConfigLoading, isError: isConfigError } = useQuery<ConfigResponse>({
|
|
86
99
|
queryKey: ['opencode-config'],
|
|
@@ -108,9 +121,11 @@ export function SessionCard({ card }: SessionCardProps) {
|
|
|
108
121
|
? 'Opening…'
|
|
109
122
|
: pendingAction === 'archive'
|
|
110
123
|
? 'Archiving…'
|
|
111
|
-
: pendingAction === '
|
|
112
|
-
? '
|
|
113
|
-
:
|
|
124
|
+
: pendingAction === 'restore'
|
|
125
|
+
? 'Restoring…'
|
|
126
|
+
: pendingAction === 'delete'
|
|
127
|
+
? 'Deleting…'
|
|
128
|
+
: null;
|
|
114
129
|
|
|
115
130
|
useEffect(() => {
|
|
116
131
|
const storedTool = window.localStorage.getItem('vibepulse:open-tool');
|
|
@@ -181,12 +196,24 @@ export function SessionCard({ card }: SessionCardProps) {
|
|
|
181
196
|
return;
|
|
182
197
|
}
|
|
183
198
|
|
|
184
|
-
const
|
|
199
|
+
const canOpenEditor = card.capabilities ? card.capabilities.openEditor : true;
|
|
200
|
+
if (isRemoteCard && openEditorTargetMode === 'remote' && !canOpenEditor && openTool === 'antigravity') {
|
|
201
|
+
setActionError('Antigravity cannot open remote sessions without remote editor support. Use VS Code.');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const useRemoteSshTarget =
|
|
206
|
+
isRemoteCard
|
|
207
|
+
&& openTool === 'vscode'
|
|
208
|
+
&& (
|
|
209
|
+
openEditorTargetMode === 'hub'
|
|
210
|
+
|| (openEditorTargetMode === 'remote' && !canOpenEditor)
|
|
211
|
+
);
|
|
185
212
|
const target = buildEditorUri(openTool === 'antigravity' ? 'antigravity' : 'vscode', card.directory, {
|
|
186
213
|
remoteSshHost: useRemoteSshTarget ? remoteSshHost : null,
|
|
187
214
|
});
|
|
188
215
|
|
|
189
|
-
if (isRemoteCard && openEditorTargetMode === 'remote') {
|
|
216
|
+
if (isRemoteCard && openEditorTargetMode === 'remote' && canOpenEditor) {
|
|
190
217
|
setPendingAction('open');
|
|
191
218
|
try {
|
|
192
219
|
const response = await fetch(`/api/sessions/${card.id}/open-editor`, {
|
|
@@ -216,6 +243,9 @@ export function SessionCard({ card }: SessionCardProps) {
|
|
|
216
243
|
return;
|
|
217
244
|
}
|
|
218
245
|
|
|
246
|
+
const canArchive = card.capabilities ? card.capabilities.archive : !card.readOnly;
|
|
247
|
+
if (!canArchive) return;
|
|
248
|
+
|
|
219
249
|
setActionError(null);
|
|
220
250
|
setPendingAction('archive');
|
|
221
251
|
try {
|
|
@@ -233,11 +263,39 @@ export function SessionCard({ card }: SessionCardProps) {
|
|
|
233
263
|
}
|
|
234
264
|
};
|
|
235
265
|
|
|
266
|
+
const handleRestore = async () => {
|
|
267
|
+
if (isActionPending) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const canArchive = card.capabilities ? card.capabilities.archive : !card.readOnly;
|
|
272
|
+
if (!canArchive) return;
|
|
273
|
+
|
|
274
|
+
setActionError(null);
|
|
275
|
+
setPendingAction('restore');
|
|
276
|
+
try {
|
|
277
|
+
const response = await fetch(`/api/sessions/${card.id}/restore`, { method: 'POST' });
|
|
278
|
+
if (!response.ok) {
|
|
279
|
+
const errorBody = await response.json().catch(() => null);
|
|
280
|
+
setActionError(mapSessionActionError(errorBody, 'Failed to restore session'));
|
|
281
|
+
}
|
|
282
|
+
} catch {
|
|
283
|
+
setActionError('Remote node is offline or unreachable.');
|
|
284
|
+
} finally {
|
|
285
|
+
setPendingAction(null);
|
|
286
|
+
setActionOpen(false);
|
|
287
|
+
await queryClient.invalidateQueries({ queryKey: ['sessions'] });
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
236
291
|
const handleDelete = async () => {
|
|
237
292
|
if (isActionPending) {
|
|
238
293
|
return;
|
|
239
294
|
}
|
|
240
295
|
|
|
296
|
+
const canDelete = card.capabilities ? card.capabilities.delete : !card.readOnly;
|
|
297
|
+
if (!canDelete) return;
|
|
298
|
+
|
|
241
299
|
setActionError(null);
|
|
242
300
|
setPendingAction('delete');
|
|
243
301
|
try {
|
|
@@ -255,6 +313,10 @@ export function SessionCard({ card }: SessionCardProps) {
|
|
|
255
313
|
}
|
|
256
314
|
};
|
|
257
315
|
|
|
316
|
+
const canArchive = card.capabilities ? card.capabilities.archive : !card.readOnly;
|
|
317
|
+
const canDelete = card.capabilities ? card.capabilities.delete : !card.readOnly;
|
|
318
|
+
const showActionsMenu = canArchive || canDelete;
|
|
319
|
+
|
|
258
320
|
return (
|
|
259
321
|
<article
|
|
260
322
|
className="relative w-full text-left p-4 bg-white dark:bg-zinc-800 rounded-xl shadow-sm border border-gray-200 dark:border-zinc-700 hover:shadow-lg hover:border-gray-300 dark:hover:border-zinc-600 transition-all duration-200"
|
|
@@ -269,7 +331,7 @@ export function SessionCard({ card }: SessionCardProps) {
|
|
|
269
331
|
>
|
|
270
332
|
{/* Top: Status indicator */}
|
|
271
333
|
<div className="flex items-center justify-between mb-2">
|
|
272
|
-
<StatusIndicator status={card.opencodeStatus} waitingForUser={card.waitingForUser} />
|
|
334
|
+
<StatusIndicator status={card.opencodeStatus} waitingForUser={card.waitingForUser} provider={card.provider} />
|
|
273
335
|
</div>
|
|
274
336
|
<h3
|
|
275
337
|
className="font-semibold text-gray-900 dark:text-gray-100 text-base line-clamp-2"
|
|
@@ -278,18 +340,17 @@ export function SessionCard({ card }: SessionCardProps) {
|
|
|
278
340
|
{card.title || 'Untitled Session'}
|
|
279
341
|
</h3>
|
|
280
342
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
)}
|
|
343
|
+
<div className="flex flex-wrap gap-1 mt-2">
|
|
344
|
+
{card.agents.map((agent) => (
|
|
345
|
+
<span
|
|
346
|
+
key={agent}
|
|
347
|
+
className="px-2 py-0.5 bg-indigo-50 text-indigo-700 dark:bg-indigo-900/20 dark:text-indigo-400 text-xs rounded-full font-medium"
|
|
348
|
+
>
|
|
349
|
+
{agent}
|
|
350
|
+
</span>
|
|
351
|
+
))}
|
|
352
|
+
</div>
|
|
353
|
+
|
|
293
354
|
{card.projectName && (
|
|
294
355
|
<div className="mt-2 flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
|
|
295
356
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" role="img" aria-hidden="true">
|
|
@@ -338,7 +399,33 @@ export function SessionCard({ card }: SessionCardProps) {
|
|
|
338
399
|
</div>
|
|
339
400
|
</div>
|
|
340
401
|
)}
|
|
402
|
+
{(() => {
|
|
403
|
+
const visibleChildren = (card.children || []).filter(
|
|
404
|
+
shouldShowChildSession
|
|
405
|
+
);
|
|
406
|
+
if (visibleChildren.length === 0) return null;
|
|
407
|
+
return (
|
|
408
|
+
<div className="mt-3 bg-gray-50/50 dark:bg-zinc-900/30 -mx-4 px-4 py-2 border-y border-gray-100 dark:border-zinc-700/50">
|
|
409
|
+
{visibleChildren.map((child, i) => (
|
|
410
|
+
<div
|
|
411
|
+
key={child.id}
|
|
412
|
+
className="flex items-center gap-2 pl-2 pr-3 py-1.5"
|
|
413
|
+
title={child.debugReason ? `Reason: ${child.debugReason}` : 'Subagent'}
|
|
414
|
+
>
|
|
415
|
+
<span className="text-gray-300 dark:text-zinc-600 text-xs flex-shrink-0 font-mono leading-none">
|
|
416
|
+
{i === visibleChildren.length - 1 ? '└' : '├'}
|
|
417
|
+
</span>
|
|
418
|
+
<StatusIndicator status={child.realTimeStatus} waitingForUser={child.waitingForUser} provider={card.provider} />
|
|
419
|
+
<span className="text-xs text-gray-500 dark:text-gray-400 truncate flex-1 min-w-0">
|
|
420
|
+
{child.title || 'Subagent'}
|
|
421
|
+
</span>
|
|
422
|
+
</div>
|
|
423
|
+
))}
|
|
424
|
+
</div>
|
|
425
|
+
);
|
|
426
|
+
})()}
|
|
341
427
|
{isConfigPendingForRemoteOpen ? (
|
|
428
|
+
|
|
342
429
|
<div className="mt-3 rounded-md border border-blue-200 bg-blue-50 px-2 py-1 text-xs text-blue-700 dark:border-blue-900/40 dark:bg-blue-900/20 dark:text-blue-300">
|
|
343
430
|
Loading open settings…
|
|
344
431
|
</div>
|
|
@@ -382,52 +469,71 @@ export function SessionCard({ card }: SessionCardProps) {
|
|
|
382
469
|
SSH: {remoteSshHost}
|
|
383
470
|
</span>
|
|
384
471
|
)}
|
|
385
|
-
|
|
386
|
-
<
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
e
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
<
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
<
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
472
|
+
{showActionsMenu && (
|
|
473
|
+
<div className="relative" ref={actionMenuRef}>
|
|
474
|
+
<button
|
|
475
|
+
type="button"
|
|
476
|
+
className="inline-flex items-center justify-center w-6 h-6 rounded-md text-gray-400 hover:text-gray-700 hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-50 dark:text-gray-500 dark:hover:text-gray-200 dark:hover:bg-zinc-700"
|
|
477
|
+
onClick={(e) => {
|
|
478
|
+
e.stopPropagation();
|
|
479
|
+
if (!isActionPending) {
|
|
480
|
+
setActionOpen((prev) => !prev);
|
|
481
|
+
}
|
|
482
|
+
}}
|
|
483
|
+
onDoubleClick={(e) => e.stopPropagation()}
|
|
484
|
+
aria-label="Actions"
|
|
485
|
+
title="Actions"
|
|
486
|
+
disabled={isActionPending}
|
|
487
|
+
>
|
|
488
|
+
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor" role="img" aria-hidden="true">
|
|
489
|
+
<path d="M5 10a2 2 0 110 4 2 2 0 010-4zm7 0a2 2 0 110 4 2 2 0 010-4zm7 0a2 2 0 110 4 2 2 0 010-4z" />
|
|
490
|
+
</svg>
|
|
491
|
+
</button>
|
|
492
|
+
{actionOpen && (
|
|
493
|
+
<div className="absolute right-0 mt-1 w-36 rounded-md border border-gray-200 bg-white shadow-lg dark:border-zinc-700 dark:bg-zinc-900 z-10">
|
|
494
|
+
{(card.status !== 'done' && canArchive) && (
|
|
495
|
+
<button
|
|
496
|
+
type="button"
|
|
497
|
+
className="w-full text-left px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 dark:text-gray-200 dark:hover:bg-zinc-800"
|
|
498
|
+
onClick={(e) => {
|
|
499
|
+
e.stopPropagation();
|
|
500
|
+
handleArchive();
|
|
501
|
+
}}
|
|
502
|
+
disabled={isActionPending}
|
|
503
|
+
>
|
|
504
|
+
Archive
|
|
505
|
+
</button>
|
|
506
|
+
)}
|
|
507
|
+
{(card.status === 'done' && canArchive) && (
|
|
508
|
+
<button
|
|
509
|
+
type="button"
|
|
510
|
+
className="w-full text-left px-3 py-2 text-sm text-gray-700 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 dark:text-gray-200 dark:hover:bg-zinc-800"
|
|
511
|
+
onClick={(e) => {
|
|
512
|
+
e.stopPropagation();
|
|
513
|
+
handleRestore();
|
|
514
|
+
}}
|
|
515
|
+
disabled={isActionPending}
|
|
516
|
+
>
|
|
517
|
+
Restore
|
|
518
|
+
</button>
|
|
519
|
+
)}
|
|
520
|
+
{canDelete && (
|
|
521
|
+
<button
|
|
522
|
+
type="button"
|
|
523
|
+
className="w-full text-left px-3 py-2 text-sm text-red-600 hover:bg-red-50 disabled:cursor-not-allowed disabled:opacity-50 dark:text-red-400 dark:hover:bg-red-900/20"
|
|
524
|
+
onClick={(e) => {
|
|
525
|
+
e.stopPropagation();
|
|
526
|
+
handleDelete();
|
|
527
|
+
}}
|
|
528
|
+
disabled={isActionPending}
|
|
529
|
+
>
|
|
530
|
+
Delete
|
|
531
|
+
</button>
|
|
532
|
+
)}
|
|
533
|
+
</div>
|
|
534
|
+
)}
|
|
535
|
+
</div>
|
|
536
|
+
)}
|
|
431
537
|
</div>
|
|
432
538
|
</article>
|
|
433
539
|
);
|