@jlongo78/agent-spaces 0.9.8 → 0.9.9
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/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +3 -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 +23 -0
- package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/admin/users/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/analytics/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/cortex/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/network/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/workspaces/page_client-reference-manifest.js +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_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/admin/analytics.html +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.rsc +3 -3
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin/analytics/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin/analytics.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/admin/analytics.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.html +1 -1
- package/.next/standalone/.next/server/app/admin/users.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin/users/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin/users.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.html +1 -1
- package/.next/standalone/.next/server/app/analytics.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap/analytics/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap/analytics.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/analytics.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/analytics.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/claude/usage/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/claude/usage/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/claude/usage/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/claude/usage/route.js +8 -0
- package/.next/standalone/.next/server/app/api/claude/usage/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/claude/usage/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/claude/usage/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/whisper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/[todoId]/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/[todoId]/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/[todoId]/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/[todoId]/route.js +8 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/[todoId]/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/[todoId]/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/[todoId]/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/route.js +8 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/cortex.html +1 -1
- package/.next/standalone/.next/server/app/cortex.rsc +2 -2
- package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap/cortex/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap/cortex.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/cortex.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/cortex.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/cortex.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/cortex.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/login.html +1 -1
- package/.next/standalone/.next/server/app/login.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/login.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/login/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/login.segments/login.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/projects.html +1 -1
- package/.next/standalone/.next/server/app/m/projects.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/projects.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/m/projects/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/projects.segments/m/projects.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/projects.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/sessions.html +1 -1
- package/.next/standalone/.next/server/app/m/sessions.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/m/sessions/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions.segments/m/sessions.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/settings.html +1 -1
- package/.next/standalone/.next/server/app/m/settings.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/m/settings/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings.segments/m/settings.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/terminal/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/terminal.html +1 -1
- package/.next/standalone/.next/server/app/m/terminal.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/terminal.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/m/terminal/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/terminal.segments/m/terminal.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/terminal.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m.html +1 -1
- package/.next/standalone/.next/server/app/m.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/m/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.html +1 -1
- package/.next/standalone/.next/server/app/network.rsc +2 -2
- package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap/network/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap/network.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/network.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/network.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.html +1 -1
- package/.next/standalone/.next/server/app/projects.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap/projects/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap/projects.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.html +1 -1
- package/.next/standalone/.next/server/app/sessions.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap/sessions/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap/sessions.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +3 -3
- package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap/settings/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap/settings.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.html +1 -1
- package/.next/standalone/.next/server/app/terminal.rsc +3 -3
- package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap/terminal/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap/terminal.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/terminal.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/terminal.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/terminal.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/vr/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/vr.html +1 -1
- package/.next/standalone/.next/server/app/vr.rsc +2 -2
- package/.next/standalone/.next/server/app/vr.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/vr.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/vr.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/vr.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/vr.segments/vr/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/vr.segments/vr.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.html +1 -1
- package/.next/standalone/.next/server/app/workspaces.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap/workspaces/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap/workspaces.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__00e90fc6._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01ab8675._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__03974f05._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__046c9b91._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__04ae6bf0._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__056fa416._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ac4ea3f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0b8e64cb._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0facd39e._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__10bc76a3._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__115f3934._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__11f155f1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__160e7c73._.js +52 -24
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17a3b966._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17d3a2b2._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1a86c055._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20b5e9c4._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__28d6fbd8._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__2a3f866b._.js +16 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__316617e7._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__32ad8f71._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__35457394._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__35de78e6._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__3685ffcb._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__38954988._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__3a32b624._.js +111 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__426ad936._.js +17 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__4985c034._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5c6ce9ed._.js +16 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5cebe58a._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5d5e4789._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__65676930._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__67cab326._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__698c6f01._.js +16 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__6c64af29._.js +22 -9
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__73aed9f5._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__79b6a9bb._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__7db704c6._.js +17 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__812ca02b._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__821f50fa._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__8716b86e._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__884ef754._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__88cdbd68._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__89d9aba9._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__8c2e1260._.js +111 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__8d536cb5._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__8df8c5d1._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__8f2ccc41._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__95c9d682._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__9e5d7774._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__9edcff87._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__a049dfc2._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__a5b4bb9a._.js +16 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__a83262a1._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__a9cd1240._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__a9d7f822._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ad08c221._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ad585f2f._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__afcb8f7d._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__bc250d43._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__bce2a6e7._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__c011bf91._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__c37d6380._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__cae392eb._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__cc2616bb._.js +16 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d501fa9b._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d59c6c15._.js +16 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d5c1db32._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__dba60c86._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__de14b9ae._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e10643d1._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e2a996e5._.js +23 -10
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e3477417._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e4db362e._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e4e70b86._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e54925af._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e8cbeaca._.js +111 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e8edc5b0._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__eab4d83b._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ead29015._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f056fd83._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f0e99572._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__fe1e16d0._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ff9cd277._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ffaea2ce._.js +15 -2
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_claude_usage_route_actions_fe002ec1.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_workspaces_[id]_todos_route_actions_0d4ffac5.js +3 -0
- package/.next/standalone/.next/server/chunks/ce889_server_app_api_workspaces_[id]_todos_[todoId]_route_actions_754fe6b9.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__66aca5d4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_17b946fd._.js → _20c2cf3a._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_5f55bf8f._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_app_(desktop)_settings_page_tsx_f74824b3._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_app_(desktop)_terminal_page_tsx_de5e8d85._.js +1 -1
- package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__90eeddae._.js +1 -1
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- 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/14216197f9dcbe5b.css +3 -0
- package/.next/standalone/.next/static/chunks/{0852575eb90c1e8d.js → 4c9fb0a38f041a3d.js} +2 -2
- package/.next/standalone/.next/static/chunks/8f6f93ab45a5ff5b.js +1 -0
- package/.next/standalone/.next/static/chunks/{e62bb488d02db247.js → d2566c2dcf53fef3.js} +1 -1
- package/.next/standalone/.next/static/chunks/{7f6a14f1849fa94d.js → ff7d85dade44d1f3.js} +1 -1
- package/.next/standalone/cortex-hook-debug.log +61 -0
- package/.next/standalone/docs/features/claude-usage.md +105 -0
- package/.next/standalone/docs/features/workspace-todos.md +127 -0
- package/.next/standalone/docs/superpowers/plans/2026-04-21-workspace-todos.md +1097 -0
- package/.next/standalone/docs/superpowers/plans/2026-04-22-claude-usage-display.md +749 -0
- package/.next/standalone/docs/superpowers/specs/2026-04-21-workspace-todos-design.md +180 -0
- package/.next/standalone/docs/superpowers/specs/2026-04-22-claude-usage-display-design.md +183 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/src/app/(desktop)/settings/page.tsx +40 -1
- package/.next/standalone/src/app/(desktop)/terminal/page.tsx +54 -2
- package/.next/standalone/src/app/api/claude/usage/route.ts +33 -0
- package/.next/standalone/src/app/api/config/route.ts +3 -0
- package/.next/standalone/src/app/api/workspaces/[id]/todos/[todoId]/route.ts +55 -0
- package/.next/standalone/src/app/api/workspaces/[id]/todos/route.ts +43 -0
- package/.next/standalone/src/components/claude/claude-usage-strip.tsx +175 -0
- package/.next/standalone/src/components/workspace/workspace-todos.tsx +265 -0
- package/.next/standalone/src/hooks/use-claude-usage.ts +66 -0
- package/.next/standalone/src/lib/claude/credentials.ts +36 -0
- package/.next/standalone/src/lib/claude/usage.ts +69 -0
- package/.next/standalone/src/lib/config.ts +3 -0
- package/.next/standalone/src/lib/db/queries.ts +103 -1
- package/.next/standalone/src/lib/db/schema.ts +18 -0
- package/.next/standalone/src/types/claude.ts +39 -0
- package/.next/standalone/tests/db/workspace-todos.test.ts +156 -0
- package/package.json +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_3ba93bdd._.js +0 -3
- package/.next/standalone/.next/static/chunks/074df89a63b6a854.js +0 -1
- package/.next/standalone/.next/static/chunks/2b769d1597e4fc1c.css +0 -3
- /package/.next/standalone/.next/static/{u1pHON3drz1mBi7owkbBP → bAQs2gDlFrq3e8w9ni9tr}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{u1pHON3drz1mBi7owkbBP → bAQs2gDlFrq3e8w9ni9tr}/_clientMiddlewareManifest.json +0 -0
- /package/.next/standalone/.next/static/{u1pHON3drz1mBi7owkbBP → bAQs2gDlFrq3e8w9ni9tr}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,1097 @@
|
|
|
1
|
+
# Workspace Todos Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Add a workspace-scoped todo list to Spaces, rendered inline above the pane grid on `/terminal`, with minimal CRUD (add/edit/toggle/delete) and a "show completed" toggle.
|
|
6
|
+
|
|
7
|
+
**Architecture:** New SQLite table `workspace_todos` (FK to `workspaces` with `ON DELETE CASCADE`), new query helpers in `src/lib/db/queries.ts`, new Next.js API routes under `src/app/api/workspaces/[id]/todos/`, and a new client component `src/components/workspace/workspace-todos.tsx` mounted in `src/app/(desktop)/terminal/page.tsx`. Mutations are optimistic with rollback on failure.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Next.js App Router, React 18 client components, better-sqlite3, TypeScript, Tailwind, lucide-react, vitest + better-sqlite3 for tests.
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-04-21-workspace-todos-design.md`
|
|
12
|
+
|
|
13
|
+
**Repo workflow note:** Per user preference, commit directly to `main`. No feature branch, no PR. Each task ends with a commit.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## File Structure
|
|
18
|
+
|
|
19
|
+
**Create:**
|
|
20
|
+
- `src/app/api/workspaces/[id]/todos/route.ts` — GET + POST handlers
|
|
21
|
+
- `src/app/api/workspaces/[id]/todos/[todoId]/route.ts` — PATCH + DELETE handlers
|
|
22
|
+
- `src/components/workspace/workspace-todos.tsx` — inline UI component
|
|
23
|
+
- `tests/db/workspace-todos.test.ts` — query-layer tests (in-process better-sqlite3 instance)
|
|
24
|
+
- `docs/features/workspace-todos.md` — feature doc (per `docs/FEATURE_DOC_GUIDE.md`)
|
|
25
|
+
|
|
26
|
+
**Modify:**
|
|
27
|
+
- `src/lib/db/schema.ts` — add `workspace_todos` table + index inside `initSchema()`
|
|
28
|
+
- `src/lib/db/queries.ts` — add `getWorkspaceTodos`, `addWorkspaceTodo`, `updateWorkspaceTodo`, `deleteWorkspaceTodo`
|
|
29
|
+
- `src/types/claude.ts` (or equivalent types file — confirm in Task 2) — add `WorkspaceTodo` type
|
|
30
|
+
- `src/app/(desktop)/terminal/page.tsx` — mount `<WorkspaceTodos>` above the pane grid
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Task 1: DB schema — add `workspace_todos` table
|
|
35
|
+
|
|
36
|
+
**Files:**
|
|
37
|
+
- Modify: `src/lib/db/schema.ts`
|
|
38
|
+
|
|
39
|
+
- [ ] **Step 1: Append CREATE TABLE + INDEX DDL inside `initSchema()`**
|
|
40
|
+
|
|
41
|
+
Open `src/lib/db/schema.ts`. Locate the multi-statement SQL block inside `initSchema()` that defines all existing tables (roughly lines 27-116, inside the template-literal string passed to better-sqlite3). At the **end** of that template-literal block (right before the closing `` ` ``), append the following SQL:
|
|
42
|
+
|
|
43
|
+
```sql
|
|
44
|
+
CREATE TABLE IF NOT EXISTS workspace_todos (
|
|
45
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
46
|
+
workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
47
|
+
text TEXT NOT NULL,
|
|
48
|
+
completed INTEGER NOT NULL DEFAULT 0,
|
|
49
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
50
|
+
created TEXT NOT NULL DEFAULT (datetime('now')),
|
|
51
|
+
completed_at TEXT
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_workspace_todos_workspace
|
|
55
|
+
ON workspace_todos(workspace_id);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
No `addCol` calls are needed — this is a brand new table, and `IF NOT EXISTS` makes the migration safe on existing DBs.
|
|
59
|
+
|
|
60
|
+
- [ ] **Step 2: Start the app, confirm the table was created**
|
|
61
|
+
|
|
62
|
+
Run:
|
|
63
|
+
```bash
|
|
64
|
+
npm run build && npm start
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
In a second terminal, open the user's SQLite DB (adjust path if needed):
|
|
68
|
+
```bash
|
|
69
|
+
sqlite3 ~/.spaces/*.db ".schema workspace_todos"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Expected output includes the `CREATE TABLE workspace_todos (...)` statement and the `CREATE INDEX` line.
|
|
73
|
+
|
|
74
|
+
Stop the dev server.
|
|
75
|
+
|
|
76
|
+
- [ ] **Step 3: Commit**
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
git add src/lib/db/schema.ts
|
|
80
|
+
git commit -m "feat(db): add workspace_todos table for workspace-scoped todos"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Task 2: TypeScript type + DB query helpers
|
|
86
|
+
|
|
87
|
+
**Files:**
|
|
88
|
+
- Modify: `src/types/claude.ts` (or whichever types file holds `Workspace`/`PaneData` — grep to confirm)
|
|
89
|
+
- Modify: `src/lib/db/queries.ts`
|
|
90
|
+
|
|
91
|
+
- [ ] **Step 1: Locate the types file**
|
|
92
|
+
|
|
93
|
+
Run:
|
|
94
|
+
```bash
|
|
95
|
+
grep -rn "export interface Workspace" src/types src/lib
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Expected: one or two hits showing where `Workspace` is declared (likely `src/types/claude.ts`). Use that same file for `WorkspaceTodo`.
|
|
99
|
+
|
|
100
|
+
- [ ] **Step 2: Add the `WorkspaceTodo` type**
|
|
101
|
+
|
|
102
|
+
At the bottom of the types file from Step 1, add:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
export interface WorkspaceTodo {
|
|
106
|
+
id: number;
|
|
107
|
+
workspaceId: number;
|
|
108
|
+
text: string;
|
|
109
|
+
completed: boolean;
|
|
110
|
+
sortOrder: number;
|
|
111
|
+
created: string;
|
|
112
|
+
completedAt: string | null;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- [ ] **Step 3: Add query helpers to `src/lib/db/queries.ts`**
|
|
117
|
+
|
|
118
|
+
At the **end** of `src/lib/db/queries.ts` (after the last existing helper), append:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// ─── Workspace Todos ───────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
import type { WorkspaceTodo } from '@/types/claude'; // Adjust path if Step 1 found a different file
|
|
124
|
+
|
|
125
|
+
interface TodoRow {
|
|
126
|
+
id: number;
|
|
127
|
+
workspace_id: number;
|
|
128
|
+
text: string;
|
|
129
|
+
completed: number;
|
|
130
|
+
sort_order: number;
|
|
131
|
+
created: string;
|
|
132
|
+
completed_at: string | null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function rowToTodo(r: TodoRow): WorkspaceTodo {
|
|
136
|
+
return {
|
|
137
|
+
id: r.id,
|
|
138
|
+
workspaceId: r.workspace_id,
|
|
139
|
+
text: r.text,
|
|
140
|
+
completed: !!r.completed,
|
|
141
|
+
sortOrder: r.sort_order,
|
|
142
|
+
created: r.created,
|
|
143
|
+
completedAt: r.completed_at,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function getWorkspaceTodos(workspaceId: number): WorkspaceTodo[] {
|
|
148
|
+
const db = getDb();
|
|
149
|
+
const rows = db.prepare(`
|
|
150
|
+
SELECT id, workspace_id, text, completed, sort_order, created, completed_at
|
|
151
|
+
FROM workspace_todos
|
|
152
|
+
WHERE workspace_id = ?
|
|
153
|
+
ORDER BY completed ASC,
|
|
154
|
+
CASE WHEN completed = 0 THEN sort_order END ASC,
|
|
155
|
+
completed_at DESC
|
|
156
|
+
`).all(workspaceId) as TodoRow[];
|
|
157
|
+
return rows.map(rowToTodo);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function addWorkspaceTodo(workspaceId: number, text: string): WorkspaceTodo {
|
|
161
|
+
const db = getDb();
|
|
162
|
+
const trimmed = text.trim();
|
|
163
|
+
if (!trimmed) throw new Error('todo text is required');
|
|
164
|
+
|
|
165
|
+
const maxRow = db.prepare(`
|
|
166
|
+
SELECT COALESCE(MAX(sort_order), -1) AS max_order
|
|
167
|
+
FROM workspace_todos
|
|
168
|
+
WHERE workspace_id = ? AND completed = 0
|
|
169
|
+
`).get(workspaceId) as { max_order: number };
|
|
170
|
+
const nextOrder = (maxRow?.max_order ?? -1) + 1;
|
|
171
|
+
|
|
172
|
+
const info = db.prepare(`
|
|
173
|
+
INSERT INTO workspace_todos (workspace_id, text, sort_order)
|
|
174
|
+
VALUES (?, ?, ?)
|
|
175
|
+
`).run(workspaceId, trimmed, nextOrder);
|
|
176
|
+
|
|
177
|
+
const row = db.prepare(`
|
|
178
|
+
SELECT id, workspace_id, text, completed, sort_order, created, completed_at
|
|
179
|
+
FROM workspace_todos WHERE id = ?
|
|
180
|
+
`).get(Number(info.lastInsertRowid)) as TodoRow;
|
|
181
|
+
return rowToTodo(row);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function updateWorkspaceTodo(
|
|
185
|
+
todoId: number,
|
|
186
|
+
data: { text?: string; completed?: boolean }
|
|
187
|
+
): WorkspaceTodo | null {
|
|
188
|
+
const db = getDb();
|
|
189
|
+
|
|
190
|
+
if (data.text !== undefined) {
|
|
191
|
+
const trimmed = data.text.trim();
|
|
192
|
+
if (!trimmed) throw new Error('todo text cannot be empty');
|
|
193
|
+
db.prepare('UPDATE workspace_todos SET text = ? WHERE id = ?').run(trimmed, todoId);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (data.completed !== undefined) {
|
|
197
|
+
if (data.completed) {
|
|
198
|
+
db.prepare(`
|
|
199
|
+
UPDATE workspace_todos
|
|
200
|
+
SET completed = 1, completed_at = datetime('now')
|
|
201
|
+
WHERE id = ?
|
|
202
|
+
`).run(todoId);
|
|
203
|
+
} else {
|
|
204
|
+
db.prepare(`
|
|
205
|
+
UPDATE workspace_todos
|
|
206
|
+
SET completed = 0, completed_at = NULL
|
|
207
|
+
WHERE id = ?
|
|
208
|
+
`).run(todoId);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const row = db.prepare(`
|
|
213
|
+
SELECT id, workspace_id, text, completed, sort_order, created, completed_at
|
|
214
|
+
FROM workspace_todos WHERE id = ?
|
|
215
|
+
`).get(todoId) as TodoRow | undefined;
|
|
216
|
+
return row ? rowToTodo(row) : null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function deleteWorkspaceTodo(todoId: number): void {
|
|
220
|
+
const db = getDb();
|
|
221
|
+
db.prepare('DELETE FROM workspace_todos WHERE id = ?').run(todoId);
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Note:** If the top of `queries.ts` already imports from `@/types/claude`, merge `WorkspaceTodo` into that existing import instead of duplicating.
|
|
226
|
+
|
|
227
|
+
- [ ] **Step 4: Type-check**
|
|
228
|
+
|
|
229
|
+
Run:
|
|
230
|
+
```bash
|
|
231
|
+
npx tsc --noEmit
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Expected: no new errors.
|
|
235
|
+
|
|
236
|
+
- [ ] **Step 5: Commit**
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
git add src/lib/db/queries.ts src/types/claude.ts
|
|
240
|
+
git commit -m "feat(db): add workspace-todo query helpers"
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Task 3: Tests for the DB query helpers
|
|
246
|
+
|
|
247
|
+
**Files:**
|
|
248
|
+
- Create: `tests/db/workspace-todos.test.ts`
|
|
249
|
+
- Modify: `src/lib/db/schema.ts` (export a test-only accessor to the internal connection cache)
|
|
250
|
+
|
|
251
|
+
- [ ] **Step 1: Add a test-only accessor in `schema.ts`**
|
|
252
|
+
|
|
253
|
+
Open `src/lib/db/schema.ts`. At the very bottom of the file (after the `closeDb` function), append:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
// Test-only export. Do not use outside of tests.
|
|
257
|
+
export const __testing = {
|
|
258
|
+
dbs: _dbs,
|
|
259
|
+
};
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
This exposes the private per-user connection cache so tests can inject a throwaway DB without touching the user's real `~/.spaces/<user>.db`.
|
|
263
|
+
|
|
264
|
+
- [ ] **Step 2: Write the test file**
|
|
265
|
+
|
|
266
|
+
Create `tests/db/workspace-todos.test.ts` with:
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
import fs from 'fs';
|
|
270
|
+
import os from 'os';
|
|
271
|
+
import path from 'path';
|
|
272
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
273
|
+
import Database from 'better-sqlite3';
|
|
274
|
+
|
|
275
|
+
import { __testing } from '@/lib/db/schema';
|
|
276
|
+
import {
|
|
277
|
+
getWorkspaceTodos,
|
|
278
|
+
addWorkspaceTodo,
|
|
279
|
+
updateWorkspaceTodo,
|
|
280
|
+
deleteWorkspaceTodo,
|
|
281
|
+
} from '@/lib/db/queries';
|
|
282
|
+
|
|
283
|
+
const TEST_USER = 'test-user'; // matches tests/setup.ts auth mock
|
|
284
|
+
let db: Database.Database;
|
|
285
|
+
let tempDir: string;
|
|
286
|
+
|
|
287
|
+
beforeEach(() => {
|
|
288
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'spaces-todos-test-'));
|
|
289
|
+
const dbPath = path.join(tempDir, 'test.db');
|
|
290
|
+
db = new Database(dbPath);
|
|
291
|
+
db.pragma('journal_mode = WAL');
|
|
292
|
+
db.pragma('foreign_keys = ON');
|
|
293
|
+
|
|
294
|
+
// Recreate just the tables we need for these tests
|
|
295
|
+
const ddl = `
|
|
296
|
+
CREATE TABLE workspaces (
|
|
297
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
298
|
+
name TEXT NOT NULL,
|
|
299
|
+
color TEXT DEFAULT '#6366f1',
|
|
300
|
+
created TEXT DEFAULT (datetime('now'))
|
|
301
|
+
);
|
|
302
|
+
CREATE TABLE workspace_todos (
|
|
303
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
304
|
+
workspace_id INTEGER NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
305
|
+
text TEXT NOT NULL,
|
|
306
|
+
completed INTEGER NOT NULL DEFAULT 0,
|
|
307
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
308
|
+
created TEXT NOT NULL DEFAULT (datetime('now')),
|
|
309
|
+
completed_at TEXT
|
|
310
|
+
);
|
|
311
|
+
CREATE INDEX idx_workspace_todos_workspace ON workspace_todos(workspace_id);
|
|
312
|
+
`;
|
|
313
|
+
// Multi-statement DDL goes through the driver's batch API
|
|
314
|
+
(db as any).exec(ddl);
|
|
315
|
+
|
|
316
|
+
// Inject our test DB into the module cache so getDb() returns it
|
|
317
|
+
__testing.dbs.set(TEST_USER, db);
|
|
318
|
+
|
|
319
|
+
// Seed one workspace
|
|
320
|
+
db.prepare("INSERT INTO workspaces (id, name) VALUES (1, 'Test WS')").run();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
afterEach(() => {
|
|
324
|
+
try { db.close(); } catch {}
|
|
325
|
+
__testing.dbs.delete(TEST_USER);
|
|
326
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
describe('workspace todos query helpers', () => {
|
|
330
|
+
it('adds a todo with incrementing sort_order among uncompleted rows', () => {
|
|
331
|
+
const a = addWorkspaceTodo(1, 'first');
|
|
332
|
+
const b = addWorkspaceTodo(1, 'second');
|
|
333
|
+
const c = addWorkspaceTodo(1, 'third');
|
|
334
|
+
|
|
335
|
+
expect(a.sortOrder).toBe(0);
|
|
336
|
+
expect(b.sortOrder).toBe(1);
|
|
337
|
+
expect(c.sortOrder).toBe(2);
|
|
338
|
+
expect(a.completed).toBe(false);
|
|
339
|
+
expect(a.text).toBe('first');
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('trims text and rejects empty', () => {
|
|
343
|
+
const t = addWorkspaceTodo(1, ' hello ');
|
|
344
|
+
expect(t.text).toBe('hello');
|
|
345
|
+
|
|
346
|
+
expect(() => addWorkspaceTodo(1, ' ')).toThrow();
|
|
347
|
+
expect(() => addWorkspaceTodo(1, '')).toThrow();
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('toggles completed and sets/clears completed_at', () => {
|
|
351
|
+
const t = addWorkspaceTodo(1, 'toggle me');
|
|
352
|
+
expect(t.completedAt).toBeNull();
|
|
353
|
+
|
|
354
|
+
const done = updateWorkspaceTodo(t.id, { completed: true });
|
|
355
|
+
expect(done?.completed).toBe(true);
|
|
356
|
+
expect(done?.completedAt).not.toBeNull();
|
|
357
|
+
|
|
358
|
+
const undone = updateWorkspaceTodo(t.id, { completed: false });
|
|
359
|
+
expect(undone?.completed).toBe(false);
|
|
360
|
+
expect(undone?.completedAt).toBeNull();
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('updates text, trims, rejects empty', () => {
|
|
364
|
+
const t = addWorkspaceTodo(1, 'original');
|
|
365
|
+
const upd = updateWorkspaceTodo(t.id, { text: ' edited ' });
|
|
366
|
+
expect(upd?.text).toBe('edited');
|
|
367
|
+
|
|
368
|
+
expect(() => updateWorkspaceTodo(t.id, { text: ' ' })).toThrow();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('lists todos: uncompleted first by sort_order, completed last by completed_at desc', () => {
|
|
372
|
+
const a = addWorkspaceTodo(1, 'a');
|
|
373
|
+
const b = addWorkspaceTodo(1, 'b');
|
|
374
|
+
const c = addWorkspaceTodo(1, 'c');
|
|
375
|
+
|
|
376
|
+
// Complete b first, then a. Expected completed order: a (newest completed) then b.
|
|
377
|
+
updateWorkspaceTodo(b.id, { completed: true });
|
|
378
|
+
updateWorkspaceTodo(a.id, { completed: true });
|
|
379
|
+
|
|
380
|
+
const list = getWorkspaceTodos(1);
|
|
381
|
+
expect(list.map(t => t.text)).toEqual(['c', 'a', 'b']);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('deletes a todo', () => {
|
|
385
|
+
const t = addWorkspaceTodo(1, 'doomed');
|
|
386
|
+
deleteWorkspaceTodo(t.id);
|
|
387
|
+
expect(getWorkspaceTodos(1)).toHaveLength(0);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('cascades when the workspace is deleted', () => {
|
|
391
|
+
addWorkspaceTodo(1, 'x');
|
|
392
|
+
addWorkspaceTodo(1, 'y');
|
|
393
|
+
db.prepare('DELETE FROM workspaces WHERE id = 1').run();
|
|
394
|
+
const rows = db.prepare('SELECT COUNT(*) AS n FROM workspace_todos').get() as { n: number };
|
|
395
|
+
expect(rows.n).toBe(0);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('scopes listing by workspace', () => {
|
|
399
|
+
db.prepare("INSERT INTO workspaces (id, name) VALUES (2, 'Other')").run();
|
|
400
|
+
addWorkspaceTodo(1, 'ws-1-a');
|
|
401
|
+
addWorkspaceTodo(2, 'ws-2-a');
|
|
402
|
+
addWorkspaceTodo(1, 'ws-1-b');
|
|
403
|
+
|
|
404
|
+
const ws1 = getWorkspaceTodos(1);
|
|
405
|
+
const ws2 = getWorkspaceTodos(2);
|
|
406
|
+
expect(ws1.map(t => t.text).sort()).toEqual(['ws-1-a', 'ws-1-b']);
|
|
407
|
+
expect(ws2.map(t => t.text)).toEqual(['ws-2-a']);
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
- [ ] **Step 3: Run the tests**
|
|
413
|
+
|
|
414
|
+
Run:
|
|
415
|
+
```bash
|
|
416
|
+
npx vitest run tests/db/workspace-todos.test.ts
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Expected: 8 tests pass.
|
|
420
|
+
|
|
421
|
+
If any test fails, re-check that Task 2's queries were added correctly and that the `__testing` accessor from Step 1 exports the same `_dbs` Map used internally by `getDb()`.
|
|
422
|
+
|
|
423
|
+
- [ ] **Step 4: Commit**
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
git add tests/db/workspace-todos.test.ts src/lib/db/schema.ts
|
|
427
|
+
git commit -m "test(db): cover workspace-todo query helpers and ordering invariants"
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## Task 4: API route — `GET`/`POST /api/workspaces/:id/todos`
|
|
433
|
+
|
|
434
|
+
**Files:**
|
|
435
|
+
- Create: `src/app/api/workspaces/[id]/todos/route.ts`
|
|
436
|
+
|
|
437
|
+
- [ ] **Step 1: Create the route file**
|
|
438
|
+
|
|
439
|
+
Create `src/app/api/workspaces/[id]/todos/route.ts` with:
|
|
440
|
+
|
|
441
|
+
```ts
|
|
442
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
443
|
+
import { getAuthUser, withUser } from '@/lib/auth';
|
|
444
|
+
import { ensureInitialized } from '@/lib/db/init';
|
|
445
|
+
import { getWorkspaceTodos, addWorkspaceTodo } from '@/lib/db/queries';
|
|
446
|
+
|
|
447
|
+
export async function GET(
|
|
448
|
+
request: NextRequest,
|
|
449
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
450
|
+
) {
|
|
451
|
+
const user = getAuthUser(request);
|
|
452
|
+
return withUser(user, async () => {
|
|
453
|
+
await ensureInitialized();
|
|
454
|
+
const { id } = await params;
|
|
455
|
+
const wsId = parseInt(id, 10);
|
|
456
|
+
if (!Number.isFinite(wsId)) {
|
|
457
|
+
return NextResponse.json({ error: 'invalid workspace id' }, { status: 400 });
|
|
458
|
+
}
|
|
459
|
+
const todos = getWorkspaceTodos(wsId);
|
|
460
|
+
return NextResponse.json(todos);
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export async function POST(
|
|
465
|
+
request: NextRequest,
|
|
466
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
467
|
+
) {
|
|
468
|
+
const user = getAuthUser(request);
|
|
469
|
+
return withUser(user, async () => {
|
|
470
|
+
await ensureInitialized();
|
|
471
|
+
const { id } = await params;
|
|
472
|
+
const wsId = parseInt(id, 10);
|
|
473
|
+
if (!Number.isFinite(wsId)) {
|
|
474
|
+
return NextResponse.json({ error: 'invalid workspace id' }, { status: 400 });
|
|
475
|
+
}
|
|
476
|
+
const body = await request.json().catch(() => null);
|
|
477
|
+
const text = typeof body?.text === 'string' ? body.text : '';
|
|
478
|
+
if (!text.trim()) {
|
|
479
|
+
return NextResponse.json({ error: 'text is required' }, { status: 400 });
|
|
480
|
+
}
|
|
481
|
+
const todo = addWorkspaceTodo(wsId, text);
|
|
482
|
+
return NextResponse.json(todo, { status: 201 });
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
- [ ] **Step 2: Smoke-test against the running server**
|
|
488
|
+
|
|
489
|
+
In one terminal:
|
|
490
|
+
```bash
|
|
491
|
+
npm run dev
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
In another (replace `1` with an existing workspace id from your DB):
|
|
495
|
+
```bash
|
|
496
|
+
curl -s http://localhost:3457/api/workspaces/1/todos
|
|
497
|
+
curl -s -X POST http://localhost:3457/api/workspaces/1/todos \
|
|
498
|
+
-H 'content-type: application/json' \
|
|
499
|
+
-d '{"text":"wire up API"}'
|
|
500
|
+
curl -s http://localhost:3457/api/workspaces/1/todos
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Expected:
|
|
504
|
+
1. First GET returns `[]`.
|
|
505
|
+
2. POST returns a JSON todo with `id`, `workspaceId: 1`, `text: "wire up API"`, `completed: false`.
|
|
506
|
+
3. Second GET returns an array containing that todo.
|
|
507
|
+
|
|
508
|
+
Stop the dev server.
|
|
509
|
+
|
|
510
|
+
- [ ] **Step 3: Commit**
|
|
511
|
+
|
|
512
|
+
```bash
|
|
513
|
+
git add "src/app/api/workspaces/[id]/todos/route.ts"
|
|
514
|
+
git commit -m "feat(api): add GET/POST /api/workspaces/:id/todos"
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Task 5: API route — `PATCH`/`DELETE /api/workspaces/:id/todos/:todoId`
|
|
520
|
+
|
|
521
|
+
**Files:**
|
|
522
|
+
- Create: `src/app/api/workspaces/[id]/todos/[todoId]/route.ts`
|
|
523
|
+
|
|
524
|
+
- [ ] **Step 1: Create the route file**
|
|
525
|
+
|
|
526
|
+
Create `src/app/api/workspaces/[id]/todos/[todoId]/route.ts` with:
|
|
527
|
+
|
|
528
|
+
```ts
|
|
529
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
530
|
+
import { getAuthUser, withUser } from '@/lib/auth';
|
|
531
|
+
import { ensureInitialized } from '@/lib/db/init';
|
|
532
|
+
import { updateWorkspaceTodo, deleteWorkspaceTodo } from '@/lib/db/queries';
|
|
533
|
+
|
|
534
|
+
export async function PATCH(
|
|
535
|
+
request: NextRequest,
|
|
536
|
+
{ params }: { params: Promise<{ id: string; todoId: string }> }
|
|
537
|
+
) {
|
|
538
|
+
const user = getAuthUser(request);
|
|
539
|
+
return withUser(user, async () => {
|
|
540
|
+
await ensureInitialized();
|
|
541
|
+
const { todoId } = await params;
|
|
542
|
+
const tid = parseInt(todoId, 10);
|
|
543
|
+
if (!Number.isFinite(tid)) {
|
|
544
|
+
return NextResponse.json({ error: 'invalid todo id' }, { status: 400 });
|
|
545
|
+
}
|
|
546
|
+
const body = await request.json().catch(() => null);
|
|
547
|
+
if (!body || (body.text === undefined && body.completed === undefined)) {
|
|
548
|
+
return NextResponse.json({ error: 'text or completed required' }, { status: 400 });
|
|
549
|
+
}
|
|
550
|
+
try {
|
|
551
|
+
const updated = updateWorkspaceTodo(tid, {
|
|
552
|
+
text: typeof body.text === 'string' ? body.text : undefined,
|
|
553
|
+
completed: typeof body.completed === 'boolean' ? body.completed : undefined,
|
|
554
|
+
});
|
|
555
|
+
if (!updated) {
|
|
556
|
+
return NextResponse.json({ error: 'not found' }, { status: 404 });
|
|
557
|
+
}
|
|
558
|
+
return NextResponse.json(updated);
|
|
559
|
+
} catch (err: any) {
|
|
560
|
+
return NextResponse.json({ error: err.message || 'update failed' }, { status: 400 });
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
export async function DELETE(
|
|
566
|
+
request: NextRequest,
|
|
567
|
+
{ params }: { params: Promise<{ id: string; todoId: string }> }
|
|
568
|
+
) {
|
|
569
|
+
const user = getAuthUser(request);
|
|
570
|
+
return withUser(user, async () => {
|
|
571
|
+
await ensureInitialized();
|
|
572
|
+
const { todoId } = await params;
|
|
573
|
+
const tid = parseInt(todoId, 10);
|
|
574
|
+
if (!Number.isFinite(tid)) {
|
|
575
|
+
return NextResponse.json({ error: 'invalid todo id' }, { status: 400 });
|
|
576
|
+
}
|
|
577
|
+
deleteWorkspaceTodo(tid);
|
|
578
|
+
return NextResponse.json({ ok: true });
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
- [ ] **Step 2: Smoke-test**
|
|
584
|
+
|
|
585
|
+
With `npm run dev` running, and assuming you still have the todo from Task 4 (example id=1):
|
|
586
|
+
|
|
587
|
+
```bash
|
|
588
|
+
# Toggle completed
|
|
589
|
+
curl -s -X PATCH http://localhost:3457/api/workspaces/1/todos/1 \
|
|
590
|
+
-H 'content-type: application/json' \
|
|
591
|
+
-d '{"completed":true}'
|
|
592
|
+
|
|
593
|
+
# Edit text
|
|
594
|
+
curl -s -X PATCH http://localhost:3457/api/workspaces/1/todos/1 \
|
|
595
|
+
-H 'content-type: application/json' \
|
|
596
|
+
-d '{"text":"wire up the API (done)"}'
|
|
597
|
+
|
|
598
|
+
# Delete
|
|
599
|
+
curl -s -X DELETE http://localhost:3457/api/workspaces/1/todos/1
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
Expected: each call returns the updated todo JSON (or `{"ok":true}` for DELETE); a final GET on the list endpoint returns `[]`.
|
|
603
|
+
|
|
604
|
+
Stop the dev server.
|
|
605
|
+
|
|
606
|
+
- [ ] **Step 3: Commit**
|
|
607
|
+
|
|
608
|
+
```bash
|
|
609
|
+
git add "src/app/api/workspaces/[id]/todos/[todoId]/route.ts"
|
|
610
|
+
git commit -m "feat(api): add PATCH/DELETE /api/workspaces/:id/todos/:todoId"
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
---
|
|
614
|
+
|
|
615
|
+
## Task 6: UI component — `<WorkspaceTodos />`
|
|
616
|
+
|
|
617
|
+
**Files:**
|
|
618
|
+
- Create: `src/components/workspace/workspace-todos.tsx`
|
|
619
|
+
|
|
620
|
+
- [ ] **Step 1: Write the component**
|
|
621
|
+
|
|
622
|
+
Create `src/components/workspace/workspace-todos.tsx` with:
|
|
623
|
+
|
|
624
|
+
```tsx
|
|
625
|
+
'use client';
|
|
626
|
+
|
|
627
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
628
|
+
import { Check, Plus, X, ListTodo, Eye, EyeOff } from 'lucide-react';
|
|
629
|
+
import { api } from '@/lib/api';
|
|
630
|
+
import type { WorkspaceTodo } from '@/types/claude';
|
|
631
|
+
|
|
632
|
+
export function WorkspaceTodos({ workspaceId }: { workspaceId: number | null }) {
|
|
633
|
+
const [todos, setTodos] = useState<WorkspaceTodo[]>([]);
|
|
634
|
+
const [loading, setLoading] = useState(false);
|
|
635
|
+
const [input, setInput] = useState('');
|
|
636
|
+
const [showCompleted, setShowCompleted] = useState(true);
|
|
637
|
+
const [editingId, setEditingId] = useState<number | null>(null);
|
|
638
|
+
const [editText, setEditText] = useState('');
|
|
639
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
640
|
+
|
|
641
|
+
// Load on workspace change
|
|
642
|
+
useEffect(() => {
|
|
643
|
+
if (workspaceId == null) { setTodos([]); return; }
|
|
644
|
+
let cancelled = false;
|
|
645
|
+
setLoading(true);
|
|
646
|
+
fetch(api(`/api/workspaces/${workspaceId}/todos`))
|
|
647
|
+
.then(r => r.json())
|
|
648
|
+
.then((data: WorkspaceTodo[]) => {
|
|
649
|
+
if (!cancelled) setTodos(Array.isArray(data) ? data : []);
|
|
650
|
+
})
|
|
651
|
+
.catch(() => { if (!cancelled) setTodos([]); })
|
|
652
|
+
.finally(() => { if (!cancelled) setLoading(false); });
|
|
653
|
+
return () => { cancelled = true; };
|
|
654
|
+
}, [workspaceId]);
|
|
655
|
+
|
|
656
|
+
const addTodo = useCallback(async () => {
|
|
657
|
+
const text = input.trim();
|
|
658
|
+
if (!text || workspaceId == null) return;
|
|
659
|
+
|
|
660
|
+
// Optimistic: append a temp row with a negative id and max sort order
|
|
661
|
+
const tempId = -Date.now();
|
|
662
|
+
const temp: WorkspaceTodo = {
|
|
663
|
+
id: tempId,
|
|
664
|
+
workspaceId,
|
|
665
|
+
text,
|
|
666
|
+
completed: false,
|
|
667
|
+
sortOrder: Number.MAX_SAFE_INTEGER,
|
|
668
|
+
created: new Date().toISOString(),
|
|
669
|
+
completedAt: null,
|
|
670
|
+
};
|
|
671
|
+
setTodos(prev => [...prev, temp]);
|
|
672
|
+
setInput('');
|
|
673
|
+
inputRef.current?.focus();
|
|
674
|
+
|
|
675
|
+
try {
|
|
676
|
+
const res = await fetch(api(`/api/workspaces/${workspaceId}/todos`), {
|
|
677
|
+
method: 'POST',
|
|
678
|
+
headers: { 'Content-Type': 'application/json' },
|
|
679
|
+
body: JSON.stringify({ text }),
|
|
680
|
+
});
|
|
681
|
+
if (!res.ok) throw new Error(await res.text());
|
|
682
|
+
const real: WorkspaceTodo = await res.json();
|
|
683
|
+
setTodos(prev => prev.map(t => t.id === tempId ? real : t));
|
|
684
|
+
} catch (err) {
|
|
685
|
+
console.error('failed to add todo', err);
|
|
686
|
+
setTodos(prev => prev.filter(t => t.id !== tempId));
|
|
687
|
+
}
|
|
688
|
+
}, [input, workspaceId]);
|
|
689
|
+
|
|
690
|
+
const toggleTodo = useCallback(async (todo: WorkspaceTodo) => {
|
|
691
|
+
if (workspaceId == null) return;
|
|
692
|
+
const next = !todo.completed;
|
|
693
|
+
setTodos(prev => prev.map(t => t.id === todo.id
|
|
694
|
+
? { ...t, completed: next, completedAt: next ? new Date().toISOString() : null }
|
|
695
|
+
: t
|
|
696
|
+
));
|
|
697
|
+
|
|
698
|
+
try {
|
|
699
|
+
const res = await fetch(api(`/api/workspaces/${workspaceId}/todos/${todo.id}`), {
|
|
700
|
+
method: 'PATCH',
|
|
701
|
+
headers: { 'Content-Type': 'application/json' },
|
|
702
|
+
body: JSON.stringify({ completed: next }),
|
|
703
|
+
});
|
|
704
|
+
if (!res.ok) throw new Error();
|
|
705
|
+
const real: WorkspaceTodo = await res.json();
|
|
706
|
+
setTodos(prev => prev.map(t => t.id === todo.id ? real : t));
|
|
707
|
+
} catch {
|
|
708
|
+
setTodos(prev => prev.map(t => t.id === todo.id ? todo : t));
|
|
709
|
+
}
|
|
710
|
+
}, [workspaceId]);
|
|
711
|
+
|
|
712
|
+
const deleteTodo = useCallback(async (todo: WorkspaceTodo) => {
|
|
713
|
+
if (workspaceId == null) return;
|
|
714
|
+
const snapshot = todos;
|
|
715
|
+
setTodos(prev => prev.filter(t => t.id !== todo.id));
|
|
716
|
+
try {
|
|
717
|
+
const res = await fetch(api(`/api/workspaces/${workspaceId}/todos/${todo.id}`), {
|
|
718
|
+
method: 'DELETE',
|
|
719
|
+
});
|
|
720
|
+
if (!res.ok) throw new Error();
|
|
721
|
+
} catch {
|
|
722
|
+
setTodos(snapshot);
|
|
723
|
+
}
|
|
724
|
+
}, [workspaceId, todos]);
|
|
725
|
+
|
|
726
|
+
const saveEdit = useCallback(async (todo: WorkspaceTodo) => {
|
|
727
|
+
const text = editText.trim();
|
|
728
|
+
setEditingId(null);
|
|
729
|
+
if (!text || text === todo.text || workspaceId == null) return;
|
|
730
|
+
|
|
731
|
+
const snapshot = todos;
|
|
732
|
+
setTodos(cur => cur.map(t => t.id === todo.id ? { ...t, text } : t));
|
|
733
|
+
try {
|
|
734
|
+
const res = await fetch(api(`/api/workspaces/${workspaceId}/todos/${todo.id}`), {
|
|
735
|
+
method: 'PATCH',
|
|
736
|
+
headers: { 'Content-Type': 'application/json' },
|
|
737
|
+
body: JSON.stringify({ text }),
|
|
738
|
+
});
|
|
739
|
+
if (!res.ok) throw new Error();
|
|
740
|
+
} catch {
|
|
741
|
+
setTodos(snapshot);
|
|
742
|
+
}
|
|
743
|
+
}, [editText, workspaceId, todos]);
|
|
744
|
+
|
|
745
|
+
// Client-side sort — matches server ordering and accommodates optimistic temp rows
|
|
746
|
+
const sorted = [...todos].sort((a, b) => {
|
|
747
|
+
if (a.completed !== b.completed) return a.completed ? 1 : -1;
|
|
748
|
+
if (!a.completed) return a.sortOrder - b.sortOrder;
|
|
749
|
+
const aT = a.completedAt ?? '';
|
|
750
|
+
const bT = b.completedAt ?? '';
|
|
751
|
+
return bT.localeCompare(aT);
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
const visible = showCompleted ? sorted : sorted.filter(t => !t.completed);
|
|
755
|
+
const completedCount = sorted.filter(t => t.completed).length;
|
|
756
|
+
|
|
757
|
+
return (
|
|
758
|
+
<div className="border-b border-zinc-800 bg-zinc-900/40 px-4 py-2 flex-shrink-0">
|
|
759
|
+
<div className="flex items-center gap-2 mb-1.5">
|
|
760
|
+
<ListTodo className="w-3.5 h-3.5 text-zinc-500" />
|
|
761
|
+
<span className="text-[11px] font-medium text-zinc-400 uppercase tracking-wider">Todos</span>
|
|
762
|
+
{completedCount > 0 && (
|
|
763
|
+
<button
|
|
764
|
+
onClick={() => setShowCompleted(s => !s)}
|
|
765
|
+
className="ml-auto flex items-center gap-1 text-[10px] text-zinc-500 hover:text-zinc-300 transition-colors"
|
|
766
|
+
title={showCompleted ? 'Hide completed' : 'Show completed'}
|
|
767
|
+
>
|
|
768
|
+
{showCompleted ? <Eye className="w-3 h-3" /> : <EyeOff className="w-3 h-3" />}
|
|
769
|
+
{completedCount} done
|
|
770
|
+
</button>
|
|
771
|
+
)}
|
|
772
|
+
</div>
|
|
773
|
+
|
|
774
|
+
<div className="flex items-center gap-1.5 mb-1">
|
|
775
|
+
<Plus className="w-3.5 h-3.5 text-zinc-600" />
|
|
776
|
+
<input
|
|
777
|
+
ref={inputRef}
|
|
778
|
+
value={input}
|
|
779
|
+
onChange={e => setInput(e.target.value)}
|
|
780
|
+
onKeyDown={e => { if (e.key === 'Enter') addTodo(); }}
|
|
781
|
+
placeholder={workspaceId == null ? 'Select a workspace to add todos…' : 'Add todo…'}
|
|
782
|
+
disabled={workspaceId == null}
|
|
783
|
+
className="flex-1 bg-transparent text-sm text-white placeholder:text-zinc-600 focus:outline-none disabled:opacity-50"
|
|
784
|
+
/>
|
|
785
|
+
</div>
|
|
786
|
+
|
|
787
|
+
{loading && visible.length === 0 ? null : (
|
|
788
|
+
<ul className="space-y-0.5 max-h-[40vh] overflow-y-auto">
|
|
789
|
+
{visible.map(todo => (
|
|
790
|
+
<li
|
|
791
|
+
key={todo.id}
|
|
792
|
+
className="group flex items-center gap-2 px-1.5 py-1 rounded hover:bg-zinc-800/50 transition-colors"
|
|
793
|
+
>
|
|
794
|
+
<button
|
|
795
|
+
onClick={() => toggleTodo(todo)}
|
|
796
|
+
className={`w-3.5 h-3.5 rounded border flex items-center justify-center transition-colors flex-shrink-0 ${
|
|
797
|
+
todo.completed
|
|
798
|
+
? 'bg-indigo-500 border-indigo-500'
|
|
799
|
+
: 'border-zinc-600 hover:border-zinc-400'
|
|
800
|
+
}`}
|
|
801
|
+
aria-label={todo.completed ? 'Mark incomplete' : 'Mark complete'}
|
|
802
|
+
>
|
|
803
|
+
{todo.completed && <Check className="w-2.5 h-2.5 text-white" />}
|
|
804
|
+
</button>
|
|
805
|
+
|
|
806
|
+
{editingId === todo.id ? (
|
|
807
|
+
<input
|
|
808
|
+
autoFocus
|
|
809
|
+
value={editText}
|
|
810
|
+
onChange={e => setEditText(e.target.value)}
|
|
811
|
+
onBlur={() => saveEdit(todo)}
|
|
812
|
+
onKeyDown={e => {
|
|
813
|
+
if (e.key === 'Enter') saveEdit(todo);
|
|
814
|
+
if (e.key === 'Escape') setEditingId(null);
|
|
815
|
+
}}
|
|
816
|
+
className="flex-1 bg-zinc-800 border border-zinc-700 rounded px-1.5 py-0.5 text-sm text-white focus:outline-none focus:border-indigo-500"
|
|
817
|
+
/>
|
|
818
|
+
) : (
|
|
819
|
+
<button
|
|
820
|
+
onClick={() => { setEditingId(todo.id); setEditText(todo.text); }}
|
|
821
|
+
className={`flex-1 text-left text-sm truncate ${
|
|
822
|
+
todo.completed ? 'text-zinc-500 line-through' : 'text-zinc-200'
|
|
823
|
+
}`}
|
|
824
|
+
>
|
|
825
|
+
{todo.text}
|
|
826
|
+
</button>
|
|
827
|
+
)}
|
|
828
|
+
|
|
829
|
+
<button
|
|
830
|
+
onClick={() => deleteTodo(todo)}
|
|
831
|
+
className="p-0.5 text-zinc-600 hover:text-red-400 opacity-0 group-hover:opacity-100 transition-opacity"
|
|
832
|
+
aria-label="Delete todo"
|
|
833
|
+
>
|
|
834
|
+
<X className="w-3 h-3" />
|
|
835
|
+
</button>
|
|
836
|
+
</li>
|
|
837
|
+
))}
|
|
838
|
+
</ul>
|
|
839
|
+
)}
|
|
840
|
+
</div>
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
- [ ] **Step 2: Type-check**
|
|
846
|
+
|
|
847
|
+
Run:
|
|
848
|
+
```bash
|
|
849
|
+
npx tsc --noEmit
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
Expected: no new errors.
|
|
853
|
+
|
|
854
|
+
- [ ] **Step 3: Commit**
|
|
855
|
+
|
|
856
|
+
```bash
|
|
857
|
+
git add src/components/workspace/workspace-todos.tsx
|
|
858
|
+
git commit -m "feat(ui): add WorkspaceTodos component"
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
---
|
|
862
|
+
|
|
863
|
+
## Task 7: Mount `<WorkspaceTodos>` on the terminal page
|
|
864
|
+
|
|
865
|
+
**Files:**
|
|
866
|
+
- Modify: `src/app/(desktop)/terminal/page.tsx`
|
|
867
|
+
|
|
868
|
+
- [ ] **Step 1: Import and place the component**
|
|
869
|
+
|
|
870
|
+
Open `src/app/(desktop)/terminal/page.tsx`. Near the other `import` statements at the top (around line 30, next to the `ProjectWizard` import), add:
|
|
871
|
+
|
|
872
|
+
```tsx
|
|
873
|
+
import { WorkspaceTodos } from '@/components/workspace/workspace-todos';
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
Then in the entered-workspace render path, mount it directly **below** the popped-out-panes indicator row and **above** the `{/* File Explorer + Terminal grid + Activity Panel */}` div. Look for this block around lines 1194-1221:
|
|
877
|
+
|
|
878
|
+
```tsx
|
|
879
|
+
{/* Popped-out pane indicators */}
|
|
880
|
+
{poppedOut.size > 0 && (
|
|
881
|
+
<div className="flex items-center gap-2 px-4 py-1.5 border-b border-zinc-800 bg-zinc-900/50">
|
|
882
|
+
...
|
|
883
|
+
</div>
|
|
884
|
+
)}
|
|
885
|
+
|
|
886
|
+
{/* File Explorer + Terminal grid + Activity Panel */}
|
|
887
|
+
<div className="flex-1 flex overflow-hidden min-h-0">
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
Insert between them:
|
|
891
|
+
|
|
892
|
+
```tsx
|
|
893
|
+
{/* Workspace todos */}
|
|
894
|
+
<WorkspaceTodos workspaceId={activeWorkspace?.id ?? null} />
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
- [ ] **Step 2: Start the dev server for manual QA**
|
|
898
|
+
|
|
899
|
+
Run:
|
|
900
|
+
```bash
|
|
901
|
+
npm run dev
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
Open `http://localhost:3457/terminal`, enter a workspace.
|
|
905
|
+
|
|
906
|
+
- [ ] **Step 3: Walk through the QA checklist**
|
|
907
|
+
|
|
908
|
+
1. Todos bar renders above the pane grid.
|
|
909
|
+
2. Type "first todo" + Enter → appears in list. Input clears and stays focused.
|
|
910
|
+
3. Add "second todo", "third todo". They stack top-to-bottom in creation order.
|
|
911
|
+
4. Click the checkbox on "second todo" → strikes through, sinks below the uncompleted pair. A "1 done" counter + eye toggle appears in the header.
|
|
912
|
+
5. Click the eye icon → "second todo" hides. Click again → reappears.
|
|
913
|
+
6. Click the text of "first todo" → inline edit. Type "first todo (edited)", Enter → text updates. Reload the page → edit persisted.
|
|
914
|
+
7. Hover any todo → `×` button appears; click → deletes.
|
|
915
|
+
8. Open the workspace chooser, switch to another workspace → todo list swaps. Switch back → original list intact.
|
|
916
|
+
9. Delete a workspace that has todos (ensure it's not the active one): its todos should be gone from the DB.
|
|
917
|
+
```bash
|
|
918
|
+
sqlite3 ~/.spaces/*.db "SELECT COUNT(*) FROM workspace_todos WHERE workspace_id = <deleted-id>;"
|
|
919
|
+
```
|
|
920
|
+
Expected: `0`.
|
|
921
|
+
|
|
922
|
+
Stop the dev server after QA passes.
|
|
923
|
+
|
|
924
|
+
- [ ] **Step 4: Commit**
|
|
925
|
+
|
|
926
|
+
```bash
|
|
927
|
+
git add "src/app/(desktop)/terminal/page.tsx"
|
|
928
|
+
git commit -m "feat(terminal): mount WorkspaceTodos above pane grid"
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
---
|
|
932
|
+
|
|
933
|
+
## Task 8: Feature doc
|
|
934
|
+
|
|
935
|
+
**Files:**
|
|
936
|
+
- Create: `docs/features/workspace-todos.md`
|
|
937
|
+
|
|
938
|
+
- [ ] **Step 1: Write the feature doc**
|
|
939
|
+
|
|
940
|
+
Create `docs/features/workspace-todos.md`:
|
|
941
|
+
|
|
942
|
+
````markdown
|
|
943
|
+
# Workspace Todos
|
|
944
|
+
|
|
945
|
+
**Status:** stable
|
|
946
|
+
**Owner:** jlongo
|
|
947
|
+
**Last updated:** 2026-04-21
|
|
948
|
+
|
|
949
|
+
## Overview
|
|
950
|
+
|
|
951
|
+
A lightweight todo list attached to each workspace. Renders inline at the top of `/terminal` above the pane grid, always visible. Minimal model: text + checkbox + delete. Scoped per-workspace — switching workspaces swaps the list. Not in scope: agent read/write, drag-reorder, priorities/due dates, cross-workspace views.
|
|
952
|
+
|
|
953
|
+
## User-facing behavior
|
|
954
|
+
|
|
955
|
+
- Visit `/terminal`, enter any workspace.
|
|
956
|
+
- A "Todos" bar renders above the pane grid with an "Add todo…" input.
|
|
957
|
+
- Enter adds a todo at the bottom of the uncompleted list. Input stays focused.
|
|
958
|
+
- Click the checkbox → strike-through + sink to the completed section.
|
|
959
|
+
- Click text → inline edit. Enter saves, Esc cancels, blur saves.
|
|
960
|
+
- Hover row → `×` delete button.
|
|
961
|
+
- Eye icon in the header toggles visibility of completed todos (ephemeral, resets on reload).
|
|
962
|
+
|
|
963
|
+
## Architecture
|
|
964
|
+
|
|
965
|
+
UI (`src/components/workspace/workspace-todos.tsx`) → API routes (`src/app/api/workspaces/[id]/todos/*`) → DB helpers (`src/lib/db/queries.ts`) → SQLite (`workspace_todos` table in the per-user DB at `~/.spaces/<user>.db`).
|
|
966
|
+
|
|
967
|
+
Mutations are optimistic client-side with rollback on failure. Workspace switch triggers a fresh refetch keyed on `workspaceId`.
|
|
968
|
+
|
|
969
|
+
## Key files
|
|
970
|
+
|
|
971
|
+
| Path | Role |
|
|
972
|
+
|---|---|
|
|
973
|
+
| `src/components/workspace/workspace-todos.tsx` | Inline UI component — fetching, optimistic CRUD, rendering |
|
|
974
|
+
| `src/app/api/workspaces/[id]/todos/route.ts` | `GET` list, `POST` create |
|
|
975
|
+
| `src/app/api/workspaces/[id]/todos/[todoId]/route.ts` | `PATCH` update, `DELETE` remove |
|
|
976
|
+
| `src/lib/db/schema.ts` | `workspace_todos` table declaration inside `initSchema()` |
|
|
977
|
+
| `src/lib/db/queries.ts` | `getWorkspaceTodos`, `addWorkspaceTodo`, `updateWorkspaceTodo`, `deleteWorkspaceTodo` |
|
|
978
|
+
| `src/types/claude.ts` (or wherever `Workspace` is defined) | `WorkspaceTodo` type |
|
|
979
|
+
| `src/app/(desktop)/terminal/page.tsx` | Mounts `<WorkspaceTodos>` above the pane grid |
|
|
980
|
+
| `tests/db/workspace-todos.test.ts` | Query-layer tests — ordering, cascade, scoping |
|
|
981
|
+
|
|
982
|
+
## Data model
|
|
983
|
+
|
|
984
|
+
### `workspace_todos`
|
|
985
|
+
|
|
986
|
+
| Column | Type | Notes |
|
|
987
|
+
|---|---|---|
|
|
988
|
+
| `id` | INTEGER PK | autoincrement |
|
|
989
|
+
| `workspace_id` | INTEGER NOT NULL | FK → `workspaces(id)` ON DELETE CASCADE |
|
|
990
|
+
| `text` | TEXT NOT NULL | trimmed on insert/update; empty rejected |
|
|
991
|
+
| `completed` | INTEGER NOT NULL DEFAULT 0 | 0 or 1 |
|
|
992
|
+
| `sort_order` | INTEGER NOT NULL DEFAULT 0 | meaningful only among uncompleted rows |
|
|
993
|
+
| `created` | TEXT NOT NULL DEFAULT datetime('now') | |
|
|
994
|
+
| `completed_at` | TEXT | set when completed flips 0→1, cleared on 1→0 |
|
|
995
|
+
|
|
996
|
+
Index: `idx_workspace_todos_workspace` on `workspace_id`.
|
|
997
|
+
|
|
998
|
+
### TypeScript
|
|
999
|
+
|
|
1000
|
+
```ts
|
|
1001
|
+
interface WorkspaceTodo {
|
|
1002
|
+
id: number;
|
|
1003
|
+
workspaceId: number;
|
|
1004
|
+
text: string;
|
|
1005
|
+
completed: boolean;
|
|
1006
|
+
sortOrder: number;
|
|
1007
|
+
created: string;
|
|
1008
|
+
completedAt: string | null;
|
|
1009
|
+
}
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
### Invariants
|
|
1013
|
+
|
|
1014
|
+
- Listing order: uncompleted first by `sort_order` ASC, then completed by `completed_at` DESC.
|
|
1015
|
+
- New todos get `sort_order = MAX(sort_order WHERE completed=0) + 1`.
|
|
1016
|
+
- `completed_at` is `NULL` iff `completed = 0`.
|
|
1017
|
+
- Deleting a workspace deletes its todos via FK cascade.
|
|
1018
|
+
|
|
1019
|
+
## APIs / contracts
|
|
1020
|
+
|
|
1021
|
+
### Internal endpoints
|
|
1022
|
+
|
|
1023
|
+
| Method | Path | Request | Response |
|
|
1024
|
+
|---|---|---|---|
|
|
1025
|
+
| `GET` | `/api/workspaces/:id/todos` | — | `WorkspaceTodo[]` |
|
|
1026
|
+
| `POST` | `/api/workspaces/:id/todos` | `{ text: string }` | `201 WorkspaceTodo` or `400 { error }` |
|
|
1027
|
+
| `PATCH` | `/api/workspaces/:id/todos/:todoId` | `{ text?, completed? }` | `WorkspaceTodo` or `400`/`404` |
|
|
1028
|
+
| `DELETE` | `/api/workspaces/:id/todos/:todoId` | — | `{ ok: true }` |
|
|
1029
|
+
|
|
1030
|
+
Auth via `getAuthUser` + `withUser`, same pattern as sibling workspace routes.
|
|
1031
|
+
|
|
1032
|
+
### External calls
|
|
1033
|
+
N/A.
|
|
1034
|
+
|
|
1035
|
+
## Configuration & environment
|
|
1036
|
+
None. Always-on Community-tier feature. No env vars, flags, or tier gates.
|
|
1037
|
+
|
|
1038
|
+
## How to run / test locally
|
|
1039
|
+
|
|
1040
|
+
```bash
|
|
1041
|
+
npm install
|
|
1042
|
+
npm run build
|
|
1043
|
+
npm start
|
|
1044
|
+
# visit http://localhost:3457/terminal, enter any workspace
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
Automated:
|
|
1048
|
+
```bash
|
|
1049
|
+
npx vitest run tests/db/workspace-todos.test.ts
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
## Known quirks & decisions
|
|
1053
|
+
|
|
1054
|
+
- **Schema lives in `initSchema()`, no migration file.** Matches the pattern in `src/lib/db/schema.ts`; the repo has no separate migration system.
|
|
1055
|
+
- **No agent write path in v1.** The coworker's "pi /todos" plugin that inspired this lets agents add todos from inside the session — deferred because it requires an MCP tool or CLI surface.
|
|
1056
|
+
- **Optimistic updates without toasts.** Failures `console.error` and revert local state; this is a local-only app, so silent revert is acceptable for v1.
|
|
1057
|
+
- **`sort_order = Number.MAX_SAFE_INTEGER` for optimistic inserts.** The temp row sorts to the bottom of uncompleted; the server returns the real `sort_order` on response and replaces the temp.
|
|
1058
|
+
- **No confirm on delete.** Minimal ethos. Add undo toast in a follow-up if this proves painful.
|
|
1059
|
+
- **Direct-to-main workflow.** Per repo convention for this codebase; no PR flow.
|
|
1060
|
+
|
|
1061
|
+
## Open questions / TODOs
|
|
1062
|
+
|
|
1063
|
+
- [ ] Agent write access via MCP tool once UI v1 proves out.
|
|
1064
|
+
- [ ] Optional: persist the "show completed" toggle in localStorage.
|
|
1065
|
+
- [ ] Optional: keyboard shortcut to focus the add-input from anywhere on `/terminal`.
|
|
1066
|
+
|
|
1067
|
+
## Changelog
|
|
1068
|
+
|
|
1069
|
+
- 2026-04-21 — Initial implementation. v1 ships direct to `main`.
|
|
1070
|
+
````
|
|
1071
|
+
|
|
1072
|
+
- [ ] **Step 2: Commit**
|
|
1073
|
+
|
|
1074
|
+
```bash
|
|
1075
|
+
git add docs/features/workspace-todos.md
|
|
1076
|
+
git commit -m "docs(features): add workspace-todos feature doc"
|
|
1077
|
+
```
|
|
1078
|
+
|
|
1079
|
+
---
|
|
1080
|
+
|
|
1081
|
+
## Self-Review Notes (filled in after writing)
|
|
1082
|
+
|
|
1083
|
+
**Spec coverage:** every section in `docs/superpowers/specs/2026-04-21-workspace-todos-design.md` has a task:
|
|
1084
|
+
- Data model → Task 1
|
|
1085
|
+
- Type + query helpers → Task 2
|
|
1086
|
+
- Query tests → Task 3
|
|
1087
|
+
- `GET`/`POST` routes → Task 4
|
|
1088
|
+
- `PATCH`/`DELETE` routes → Task 5
|
|
1089
|
+
- UI component → Task 6
|
|
1090
|
+
- Terminal page integration → Task 7
|
|
1091
|
+
- Feature doc → Task 8
|
|
1092
|
+
|
|
1093
|
+
**Placeholder scan:** no TBD/TODO outside the explicit "Open questions / TODOs" section of the feature doc, which is itself a required section per `FEATURE_DOC_GUIDE.md`.
|
|
1094
|
+
|
|
1095
|
+
**Type consistency:** `WorkspaceTodo` shape is identical across Task 2 (declaration), Task 3 (test assertions), Task 4/5 (route returns), Task 6 (UI import), Task 8 (doc). Function names `getWorkspaceTodos` / `addWorkspaceTodo` / `updateWorkspaceTodo` / `deleteWorkspaceTodo` used consistently.
|
|
1096
|
+
|
|
1097
|
+
**Manual QA gate:** Task 7 Step 3 is the final smoke test — requires a running server and a browser session.
|