@slycode/slycode 0.2.21 → 0.2.23
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/dist/bridge/api.d.ts +2 -1
- package/dist/bridge/api.js +114 -1
- package/dist/bridge/api.js.map +1 -1
- package/dist/bridge/git-utils.d.ts +9 -0
- package/dist/bridge/git-utils.js +49 -0
- package/dist/bridge/git-utils.js.map +1 -0
- package/dist/bridge/index.js +8 -2
- package/dist/bridge/index.js.map +1 -1
- package/dist/bridge/provider-utils.d.ts +10 -0
- package/dist/bridge/provider-utils.js +5 -1
- package/dist/bridge/provider-utils.js.map +1 -1
- package/dist/bridge/response-store.d.ts +46 -0
- package/dist/bridge/response-store.js +95 -0
- package/dist/bridge/response-store.js.map +1 -0
- package/dist/bridge/session-manager.d.ts +33 -1
- package/dist/bridge/session-manager.js +191 -2
- package/dist/bridge/session-manager.js.map +1 -1
- package/dist/bridge/types.d.ts +41 -0
- package/dist/data/scaffold-templates/tutorial-project/documentation/kanban.json +1 -1
- package/dist/messaging/bridge-client.d.ts +7 -3
- package/dist/messaging/bridge-client.js +26 -5
- package/dist/messaging/bridge-client.js.map +1 -1
- package/dist/messaging/index.js +149 -30
- package/dist/messaging/index.js.map +1 -1
- package/dist/messaging/state.d.ts +3 -0
- package/dist/messaging/state.js +13 -0
- package/dist/messaging/state.js.map +1 -1
- package/dist/messaging/types.d.ts +3 -0
- package/dist/scripts/kanban.js +448 -2
- package/dist/store/actions/checkpoint.md +1 -1
- package/dist/store/actions/context.md +1 -1
- package/dist/store/actions/create-card.md +1 -1
- package/dist/store/actions/deep-design.md +13 -3
- package/dist/store/actions/design-requirements.md +11 -1
- package/dist/store/actions/explore.md +1 -1
- package/dist/store/actions/onboard.md +2 -4
- package/dist/store/actions/summarize.md +1 -1
- package/dist/store/skills/kanban/SKILL.md +77 -3
- package/dist/web/.next/BUILD_ID +1 -1
- package/dist/web/.next/app-path-routes-manifest.json +1 -0
- package/dist/web/.next/build-manifest.json +2 -2
- package/dist/web/.next/prerender-manifest.json +3 -3
- package/dist/web/.next/routes-manifest.json +6 -0
- package/dist/web/.next/server/app/_global-error.html +2 -2
- package/dist/web/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/_not-found.html +1 -1
- package/dist/web/.next/server/app/_not-found.rsc +10 -10
- package/dist/web/.next/server/app/_not-found.segments/_full.segment.rsc +10 -10
- package/dist/web/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/dist/web/.next/server/app/_not-found.segments/_index.segment.rsc +5 -5
- package/dist/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/dist/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/dist/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/dist/web/.next/server/app/api/areas/route.js +1 -1
- package/dist/web/.next/server/app/api/areas/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/bridge/[...path]/route.js +1 -1
- package/dist/web/.next/server/app/api/bridge/[...path]/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/changelog/route/app-paths-manifest.json +3 -0
- package/dist/web/.next/server/app/api/changelog/route/build-manifest.json +11 -0
- package/dist/web/.next/server/app/api/changelog/route/server-reference-manifest.json +4 -0
- package/dist/web/.next/server/app/api/changelog/route.js +7 -0
- package/dist/web/.next/server/app/api/changelog/route.js.map +5 -0
- package/dist/web/.next/server/app/api/changelog/route.js.nft.json +1 -0
- package/dist/web/.next/server/app/api/changelog/route_client-reference-manifest.js +2 -0
- package/dist/web/.next/server/app/api/cli-assets/assistant/route.js +1 -1
- package/dist/web/.next/server/app/api/cli-assets/assistant/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/fix/route.js +1 -1
- package/dist/web/.next/server/app/api/cli-assets/fix/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/import/route.js +1 -1
- package/dist/web/.next/server/app/api/cli-assets/import/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/route.js +2 -2
- package/dist/web/.next/server/app/api/cli-assets/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/store/preview/route.js +1 -1
- package/dist/web/.next/server/app/api/cli-assets/store/preview/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/store/route.js +2 -2
- package/dist/web/.next/server/app/api/cli-assets/store/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/sync/route.js +1 -1
- package/dist/web/.next/server/app/api/cli-assets/sync/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/cli-assets/updates/route.js +2 -2
- package/dist/web/.next/server/app/api/cli-assets/updates/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/dashboard/route.js +2 -2
- package/dist/web/.next/server/app/api/dashboard/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/events/route.js +1 -1
- package/dist/web/.next/server/app/api/events/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/file/route.js +1 -1
- package/dist/web/.next/server/app/api/file/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/git-status/route.js +1 -1
- package/dist/web/.next/server/app/api/git-status/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/kanban/route.js +1 -1
- package/dist/web/.next/server/app/api/kanban/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/kanban/stream/route.js +1 -1
- package/dist/web/.next/server/app/api/kanban/stream/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/projects/[id]/route.js +2 -2
- package/dist/web/.next/server/app/api/projects/[id]/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/projects/analyze/route.js +1 -1
- package/dist/web/.next/server/app/api/projects/analyze/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/projects/reorder/route.js +2 -2
- package/dist/web/.next/server/app/api/projects/reorder/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/projects/route.js +2 -2
- package/dist/web/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/providers/route.js +1 -1
- package/dist/web/.next/server/app/api/providers/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/scheduler/route.js +1 -1
- package/dist/web/.next/server/app/api/scheduler/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/search/route.js +1 -1
- package/dist/web/.next/server/app/api/search/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/settings/route.js +1 -1
- package/dist/web/.next/server/app/api/settings/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/sly-actions/invalidate/route.js +1 -1
- package/dist/web/.next/server/app/api/sly-actions/invalidate/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/sly-actions/route.js +1 -1
- package/dist/web/.next/server/app/api/sly-actions/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/sly-actions/stream/route.js +1 -1
- package/dist/web/.next/server/app/api/sly-actions/stream/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/system-stats/route.js +1 -1
- package/dist/web/.next/server/app/api/system-stats/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/terminal-classes/route.js +1 -1
- package/dist/web/.next/server/app/api/terminal-classes/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/transcribe/route.js +5 -5
- package/dist/web/.next/server/app/api/transcribe/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/api/version-check/route.js +1 -1
- package/dist/web/.next/server/app/api/version-check/route.js.nft.json +1 -1
- package/dist/web/.next/server/app/page.js +1 -1
- package/dist/web/.next/server/app/page.js.nft.json +1 -1
- package/dist/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app/project/[id]/page.js +2 -2
- package/dist/web/.next/server/app/project/[id]/page.js.nft.json +1 -1
- package/dist/web/.next/server/app/project/[id]/page_client-reference-manifest.js +1 -1
- package/dist/web/.next/server/app-paths-manifest.json +1 -0
- package/dist/web/.next/server/chunks/{[externals]__c6831f39._.js → [externals]__78e522ea._.js} +2 -2
- package/dist/web/.next/server/chunks/{[root-of-the-server]__1ec21ccc._.js → [root-of-the-server]__029203cd._.js} +3 -3
- package/dist/web/.next/server/chunks/{[root-of-the-server]__4297cb97._.js → [root-of-the-server]__0d6d4443._.js} +1 -1
- package/dist/web/.next/server/chunks/[root-of-the-server]__172ad0b1._.js +18 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__1c5f4ef9._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__1cab11f0._.js +3 -0
- package/dist/web/.next/server/chunks/{[root-of-the-server]__0f69c28a._.js → [root-of-the-server]__1eb3f172._.js} +2 -2
- package/dist/web/.next/server/chunks/[root-of-the-server]__22cba275._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__2543e413._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__2c42a835._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__2ed0ff47._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__35454eea._.js +27 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__35768b56._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__3880228a._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__42322d88._.js +3 -0
- package/dist/web/.next/server/chunks/{[root-of-the-server]__d0f4efec._.js → [root-of-the-server]__5152eeff._.js} +3 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__527c7f57._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__5c5dac4b._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__5cb130f2._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__68927e75._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__719517c7._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__73cf49c2._.js +3 -0
- package/dist/web/.next/server/chunks/{[root-of-the-server]__f5dae2ad._.js → [root-of-the-server]__7af4ab09._.js} +1 -1
- package/dist/web/.next/server/chunks/{[root-of-the-server]__4244617a._.js → [root-of-the-server]__7e6860e0._.js} +3 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__88bf5e22._.js +3 -0
- package/dist/web/.next/server/chunks/{[root-of-the-server]__f97e93fa._.js → [root-of-the-server]__8b4259cb._.js} +3 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__92f81907._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__967603e9._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__9e4bd28f._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__a259539f._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__ba1d2e56._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__c942d872._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__d7893622._.js +3 -0
- package/dist/web/.next/server/chunks/{[root-of-the-server]__3b9d3e43._.js → [root-of-the-server]__d843611b._.js} +6 -6
- package/dist/web/.next/server/chunks/[root-of-the-server]__f4d2627f._.js +3 -0
- package/dist/web/.next/server/chunks/[root-of-the-server]__f597835d._.js +3 -0
- package/dist/web/.next/server/chunks/{[root-of-the-server]__cf14e306._.js → [root-of-the-server]__fe8b9abd._.js} +1 -1
- package/dist/web/.next/server/chunks/_next-internal_server_app_api_changelog_route_actions_d6e239bf.js +3 -0
- package/dist/web/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_18324462.js +1 -1
- package/dist/web/.next/server/chunks/src_677020aa._.js +1 -1
- package/dist/web/.next/server/chunks/src_lib_scheduler_ts_03988e3e._.js +1 -1
- package/dist/web/.next/server/chunks/src_lib_scheduler_ts_7120457c._.js +1 -1
- package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__1f5fc489._.js +4 -3
- package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__43d93717._.js +3 -0
- package/dist/web/.next/server/chunks/ssr/{[root-of-the-server]__077f472c._.js → [root-of-the-server]__6183d28c._.js} +1 -1
- package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__90f82e6d._.js +3 -0
- package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__bcbe4bf2._.js +4 -3
- package/dist/web/.next/server/chunks/ssr/src_components_Dashboard_tsx_efc4dc27._.js +1 -1
- package/dist/web/.next/server/chunks/ssr/src_components_c4135402._.js +1 -1
- package/dist/web/.next/server/chunks/ssr/src_contexts_VoiceContext_tsx_cfba7292._.js +1 -1
- package/dist/web/.next/server/chunks/ssr/src_lib_registry_ts_2fc87c9c._.js +1 -1
- package/dist/web/.next/server/pages/404.html +1 -1
- package/dist/web/.next/server/pages/500.html +2 -2
- package/dist/web/.next/server/server-reference-manifest.js +1 -1
- package/dist/web/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/.next/static/chunks/293449b828207656.css +1 -0
- package/dist/web/.next/static/chunks/{f55f3c8c1a52f80c.js → 3859477038c381ad.js} +1 -1
- package/dist/web/.next/static/chunks/3a5721af09d1c753.js +5 -0
- package/dist/web/.next/static/chunks/{8fb2a99c64580de7.js → 98311243e9a5a0ec.js} +1 -1
- package/dist/web/.next/static/chunks/{b8e0c1aeea4a14bc.js → a47f36b030917d1f.js} +1 -1
- package/dist/web/.next/static/chunks/d60c422421920130.js +5 -0
- package/dist/web/.next/static/chunks/{4049cceee6a49323.js → fa78afe3ceed998b.js} +1 -1
- package/dist/web/src/app/api/changelog/route.ts +45 -0
- package/dist/web/src/app/api/kanban/route.ts +52 -12
- package/dist/web/src/app/api/projects/analyze/route.ts +12 -2
- package/dist/web/src/app/api/projects/route.ts +1 -11
- package/dist/web/src/app/api/providers/route.ts +4 -0
- package/dist/web/src/components/ActivityFeed.tsx +3 -0
- package/dist/web/src/components/BranchTab.tsx +115 -0
- package/dist/web/src/components/ChangelogModal.tsx +234 -0
- package/dist/web/src/components/ClaudeTerminalPanel.tsx +124 -70
- package/dist/web/src/components/Dashboard.tsx +11 -0
- package/dist/web/src/components/GlobalClaudePanel.tsx +6 -0
- package/dist/web/src/components/ProjectKanban.tsx +38 -8
- package/dist/web/src/components/VersionUpdateToast.tsx +6 -4
- package/dist/web/src/components/VoiceControlBar.tsx +1 -1
- package/dist/web/src/lib/paths.ts +14 -0
- package/dist/web/src/lib/scheduler.ts +2 -1
- package/dist/web/src/lib/types.ts +24 -0
- package/dist/web/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/templates/changelog.json +242 -0
- package/templates/kanban-seed.json +1 -1
- package/templates/store/actions/checkpoint.md +1 -1
- package/templates/store/actions/context.md +1 -1
- package/templates/store/actions/create-card.md +1 -1
- package/templates/store/actions/deep-design.md +13 -3
- package/templates/store/actions/design-requirements.md +11 -1
- package/templates/store/actions/explore.md +1 -1
- package/templates/store/actions/onboard.md +2 -4
- package/templates/store/actions/summarize.md +1 -1
- package/templates/store/skills/kanban/SKILL.md +77 -3
- package/templates/tutorial-project/documentation/kanban.json +1 -1
- package/templates/updates/actions/checkpoint.md +1 -1
- package/templates/updates/actions/context.md +1 -1
- package/templates/updates/actions/create-card.md +1 -1
- package/templates/updates/actions/deep-design.md +13 -3
- package/templates/updates/actions/design-requirements.md +11 -1
- package/templates/updates/actions/explore.md +1 -1
- package/templates/updates/actions/onboard.md +2 -4
- package/templates/updates/actions/summarize.md +1 -1
- package/templates/updates/skills/kanban/SKILL.md +77 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__09aec55a._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__12f6cd6f._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__15fc9266._.js +0 -18
- package/dist/web/.next/server/chunks/[root-of-the-server]__198f01e0._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__279e9bf3._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__2b639eab._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__2d1f0ed9._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__3f239285._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__47dd878e._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__5b8c9374._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__5e08b942._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__6ffce934._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__71bb3374._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__7603305e._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__7c476ad6._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__846ca56f._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__98d88050._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__b273cc05._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__b90bbd70._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__d5272169._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__d56e68cb._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__d6362272._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__de1277ee._.js +0 -27
- package/dist/web/.next/server/chunks/[root-of-the-server]__e88a19d2._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__f3e501b6._.js +0 -3
- package/dist/web/.next/server/chunks/[root-of-the-server]__f59af2bc._.js +0 -3
- package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__9ac6ea25._.js +0 -3
- package/dist/web/.next/server/chunks/ssr/[root-of-the-server]__dfe2728c._.js +0 -3
- package/dist/web/.next/static/chunks/3d5195b57fc05540.js +0 -4
- package/dist/web/.next/static/chunks/59fb302a5bfd2dc0.js +0 -4
- package/dist/web/.next/static/chunks/747f5e5f9dcf2621.css +0 -1
- /package/dist/web/.next/static/{0sPAbk-Qw-InZ0rdHjHnC → aN-jqftQVvSm0qVskLybH}/_buildManifest.js +0 -0
- /package/dist/web/.next/static/{0sPAbk-Qw-InZ0rdHjHnC → aN-jqftQVvSm0qVskLybH}/_clientMiddlewareManifest.json +0 -0
- /package/dist/web/.next/static/{0sPAbk-Qw-InZ0rdHjHnC → aN-jqftQVvSm0qVskLybH}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { getSlycodeRoot } from '@/lib/paths';
|
|
5
|
+
import type { ChangelogVersion } from '@/lib/types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* GET /api/changelog
|
|
9
|
+
*
|
|
10
|
+
* Returns the changelog as a JSON array of ChangelogVersion entries,
|
|
11
|
+
* ordered newest-first.
|
|
12
|
+
*
|
|
13
|
+
* Path resolution:
|
|
14
|
+
* Prod: <workspace>/node_modules/@slycode/slycode/templates/changelog.json
|
|
15
|
+
* Dev: <workspace>/data/changelog.json
|
|
16
|
+
*
|
|
17
|
+
* Returns an empty array on missing/malformed file (graceful degradation).
|
|
18
|
+
*/
|
|
19
|
+
export async function GET() {
|
|
20
|
+
const root = getSlycodeRoot();
|
|
21
|
+
|
|
22
|
+
const candidatePaths = [
|
|
23
|
+
// Prod: shipped inside the package templates
|
|
24
|
+
path.join(root, 'node_modules', '@slycode', 'slycode', 'templates', 'changelog.json'),
|
|
25
|
+
// Dev: source of truth at data/
|
|
26
|
+
path.join(root, 'data', 'changelog.json'),
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
for (const p of candidatePaths) {
|
|
30
|
+
if (!existsSync(p)) continue;
|
|
31
|
+
try {
|
|
32
|
+
const raw = readFileSync(p, 'utf-8');
|
|
33
|
+
const data = JSON.parse(raw);
|
|
34
|
+
if (!Array.isArray(data)) {
|
|
35
|
+
// Malformed shape — fall through to empty
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
return NextResponse.json(data as ChangelogVersion[]);
|
|
39
|
+
} catch {
|
|
40
|
+
// Malformed JSON — fall through to next candidate or empty
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return NextResponse.json([] as ChangelogVersion[]);
|
|
45
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import type { KanbanBoard, KanbanStages, KanbanCard, KanbanStage } from '@/lib/types';
|
|
4
|
+
import type { KanbanBoard, KanbanStages, KanbanCard, KanbanStage, ChangedCard } from '@/lib/types';
|
|
5
5
|
import { getNextRun } from '@/lib/scheduler';
|
|
6
6
|
import { appendEvent } from '@/lib/event-log';
|
|
7
7
|
import {
|
|
@@ -230,10 +230,11 @@ export async function GET(request: NextRequest) {
|
|
|
230
230
|
export async function POST(request: NextRequest) {
|
|
231
231
|
try {
|
|
232
232
|
const body = await request.json();
|
|
233
|
-
const { projectId, stages: incomingStages, changedCardIds } = body as {
|
|
233
|
+
const { projectId, stages: incomingStages, changedCardIds, changedCards } = body as {
|
|
234
234
|
projectId: string;
|
|
235
235
|
stages: KanbanStages;
|
|
236
236
|
changedCardIds?: string[];
|
|
237
|
+
changedCards?: ChangedCard[];
|
|
237
238
|
};
|
|
238
239
|
let stages = incomingStages;
|
|
239
240
|
|
|
@@ -288,21 +289,60 @@ export async function POST(request: NextRequest) {
|
|
|
288
289
|
// Clean up old numbered backups from previous system (one-time migration)
|
|
289
290
|
await cleanupLegacyBackups(projectId);
|
|
290
291
|
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
|
|
292
|
+
// Type-aware merge: changedCards carries per-card operation types (move/edit/create/delete).
|
|
293
|
+
// For "move" cards, preserve disk content and overlay only positional fields.
|
|
294
|
+
// For "edit"/"create" cards, use the frontend version fully.
|
|
295
|
+
// Falls back to changedCardIds (untyped) for backward compatibility — treats all as "edit".
|
|
296
|
+
const effectiveChangedIds = changedCards?.map((c) => c.id) ?? changedCardIds;
|
|
297
|
+
if (effectiveChangedIds && effectiveChangedIds.length > 0) {
|
|
295
298
|
try {
|
|
296
299
|
const currentContent = await fs.readFile(kanbanPath, 'utf-8');
|
|
297
300
|
const currentData = JSON.parse(currentContent) as KanbanBoard;
|
|
298
301
|
const diskStages = currentData.stages || EMPTY_STAGES;
|
|
299
302
|
|
|
303
|
+
// Build type lookup: cardId → change type (default "edit" for backward compat)
|
|
304
|
+
const changeTypeMap = new Map<string, string>();
|
|
305
|
+
if (changedCards) {
|
|
306
|
+
for (const cc of changedCards) {
|
|
307
|
+
changeTypeMap.set(cc.id, cc.type);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Build disk card lookup for move operations
|
|
312
|
+
const diskCardMap = new Map<string, KanbanCard>();
|
|
313
|
+
for (const [, cards] of Object.entries(diskStages) as [KanbanStage, KanbanCard[]][]) {
|
|
314
|
+
for (const card of cards || []) {
|
|
315
|
+
diskCardMap.set(card.id, card);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
300
319
|
// Build lookup of changed cards from frontend payload
|
|
301
|
-
const
|
|
320
|
+
const changedCardMap = new Map<string, { card: KanbanCard; stage: KanbanStage }>();
|
|
302
321
|
for (const [stage, cards] of Object.entries(stages) as [KanbanStage, KanbanCard[]][]) {
|
|
303
322
|
for (const card of cards || []) {
|
|
304
|
-
if (
|
|
305
|
-
|
|
323
|
+
if (effectiveChangedIds.includes(card.id)) {
|
|
324
|
+
const changeType = changeTypeMap.get(card.id) || 'edit';
|
|
325
|
+
let mergedCard: KanbanCard;
|
|
326
|
+
|
|
327
|
+
if (changeType === 'move') {
|
|
328
|
+
// Move: preserve disk content, overlay only positional fields
|
|
329
|
+
const diskCard = diskCardMap.get(card.id);
|
|
330
|
+
if (!diskCard) {
|
|
331
|
+
// Card deleted on disk during pending move — drop silently
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
mergedCard = {
|
|
335
|
+
...diskCard,
|
|
336
|
+
order: card.order,
|
|
337
|
+
updated_at: card.updated_at,
|
|
338
|
+
last_modified_by: 'web',
|
|
339
|
+
};
|
|
340
|
+
} else {
|
|
341
|
+
// Edit/create: use frontend version fully
|
|
342
|
+
mergedCard = { ...card, last_modified_by: 'web' };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
changedCardMap.set(card.id, { card: mergedCard, stage: stage as KanbanStage });
|
|
306
346
|
}
|
|
307
347
|
}
|
|
308
348
|
}
|
|
@@ -317,14 +357,14 @@ export async function POST(request: NextRequest) {
|
|
|
317
357
|
};
|
|
318
358
|
|
|
319
359
|
// Remove changed cards from their current disk positions
|
|
320
|
-
for (const cardId of
|
|
360
|
+
for (const cardId of effectiveChangedIds) {
|
|
321
361
|
for (const stage of Object.keys(mergedStages) as KanbanStage[]) {
|
|
322
362
|
mergedStages[stage] = mergedStages[stage].filter((c) => c.id !== cardId);
|
|
323
363
|
}
|
|
324
364
|
}
|
|
325
365
|
|
|
326
366
|
// Re-add changed cards to their target stages (absent = deleted)
|
|
327
|
-
for (const [, { card, stage }] of
|
|
367
|
+
for (const [, { card, stage }] of changedCardMap) {
|
|
328
368
|
mergedStages[stage] = [...mergedStages[stage], card];
|
|
329
369
|
}
|
|
330
370
|
|
|
@@ -409,7 +449,7 @@ export async function POST(request: NextRequest) {
|
|
|
409
449
|
}
|
|
410
450
|
}
|
|
411
451
|
|
|
412
|
-
return NextResponse.json({ success: true, last_updated: data.last_updated });
|
|
452
|
+
return NextResponse.json({ success: true, last_updated: data.last_updated, stages: normalizedStages });
|
|
413
453
|
} catch (error) {
|
|
414
454
|
console.error('Failed to save kanban:', error);
|
|
415
455
|
return NextResponse.json({ error: 'Failed to save' }, { status: 500 });
|
|
@@ -2,7 +2,7 @@ import { NextResponse } from 'next/server';
|
|
|
2
2
|
import { execFile } from 'child_process';
|
|
3
3
|
import { promisify } from 'util';
|
|
4
4
|
import path from 'path';
|
|
5
|
-
import { getPackageDir } from '@/lib/paths';
|
|
5
|
+
import { getPackageDir, expandTilde } from '@/lib/paths';
|
|
6
6
|
|
|
7
7
|
const execFileAsync = promisify(execFile);
|
|
8
8
|
|
|
@@ -23,11 +23,21 @@ export async function POST(request: Request) {
|
|
|
23
23
|
);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// Expand tilde and validate absolute path
|
|
27
|
+
const expanded = expandTilde(targetPath);
|
|
28
|
+
if (!path.isAbsolute(expanded)) {
|
|
29
|
+
return NextResponse.json(
|
|
30
|
+
{ error: 'Please enter an absolute path (e.g. ~/Dev/myproject or /home/user/Dev/myproject)' },
|
|
31
|
+
{ status: 400 }
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const resolvedPath = path.resolve(expanded);
|
|
35
|
+
|
|
26
36
|
const scaffoldScript = path.join(getPackageDir(), 'scripts', 'scaffold.js');
|
|
27
37
|
const args = [
|
|
28
38
|
scaffoldScript,
|
|
29
39
|
'analyze',
|
|
30
|
-
'--path',
|
|
40
|
+
'--path', resolvedPath,
|
|
31
41
|
'--json',
|
|
32
42
|
];
|
|
33
43
|
if (providers && Array.isArray(providers) && providers.length > 0) {
|
|
@@ -3,22 +3,12 @@ import { loadRegistry, saveRegistry } from '@/lib/registry';
|
|
|
3
3
|
import { execFile } from 'child_process';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import path from 'path';
|
|
6
|
-
import
|
|
7
|
-
import { getSlycodeRoot, getPackageDir } from '@/lib/paths';
|
|
6
|
+
import { getSlycodeRoot, getPackageDir, expandTilde } from '@/lib/paths';
|
|
8
7
|
|
|
9
8
|
const execFileAsync = promisify(execFile);
|
|
10
9
|
|
|
11
10
|
export const dynamic = 'force-dynamic';
|
|
12
11
|
|
|
13
|
-
function expandTilde(p: string): string {
|
|
14
|
-
// Normalize Unicode tildes (U+02DC small tilde, U+FF5E fullwidth tilde) to ASCII
|
|
15
|
-
p = p.replace(/^[\u02dc\uff5e]/, '~');
|
|
16
|
-
if (p.startsWith('~/') || p === '~') {
|
|
17
|
-
return p.replace(/^~/, os.homedir());
|
|
18
|
-
}
|
|
19
|
-
return p;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
12
|
function toKebabCase(str: string): string {
|
|
23
13
|
return str
|
|
24
14
|
.toLowerCase()
|
|
@@ -31,6 +31,10 @@ function validateProviderDefault(
|
|
|
31
31
|
if (typeof d.skipPermissions !== 'boolean') {
|
|
32
32
|
return `${label}.skipPermissions must be a boolean`;
|
|
33
33
|
}
|
|
34
|
+
// Optional model field — pass through to CLI (no validation against available list)
|
|
35
|
+
if ('model' in d && d.model !== undefined && typeof d.model !== 'string') {
|
|
36
|
+
return `${label}.model must be a string if provided`;
|
|
37
|
+
}
|
|
34
38
|
return null;
|
|
35
39
|
}
|
|
36
40
|
|
|
@@ -80,6 +80,9 @@ function renderMovedDetail(detail: string): React.ReactNode {
|
|
|
80
80
|
|
|
81
81
|
function renderDetail(event: ActivityEvent): React.ReactNode {
|
|
82
82
|
if (event.type === 'card_moved') return renderMovedDetail(event.detail);
|
|
83
|
+
if (typeof event.detail === 'object' && event.detail !== null) {
|
|
84
|
+
return JSON.stringify(event.detail);
|
|
85
|
+
}
|
|
83
86
|
return event.detail;
|
|
84
87
|
}
|
|
85
88
|
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
4
|
+
|
|
5
|
+
interface BranchTabProps {
|
|
6
|
+
projectPath: string;
|
|
7
|
+
isTerminalExpanded: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface GitStatus {
|
|
11
|
+
branch: string | null;
|
|
12
|
+
uncommitted: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate a deterministic hue (0-360) from a string using DJB2 hash.
|
|
17
|
+
* Different branch names produce visually distinct colors.
|
|
18
|
+
*/
|
|
19
|
+
function branchToHue(branch: string): number {
|
|
20
|
+
let hash = 5381;
|
|
21
|
+
for (let i = 0; i < branch.length; i++) {
|
|
22
|
+
hash = ((hash << 5) + hash + branch.charCodeAt(i)) >>> 0;
|
|
23
|
+
}
|
|
24
|
+
return hash % 360;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Detect if dark mode is active by checking for .dark class on <html>.
|
|
29
|
+
*/
|
|
30
|
+
function isDarkMode(): boolean {
|
|
31
|
+
if (typeof document === 'undefined') return true;
|
|
32
|
+
return document.documentElement.classList.contains('dark');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function BranchTab({ projectPath, isTerminalExpanded }: BranchTabProps) {
|
|
36
|
+
const [gitStatus, setGitStatus] = useState<GitStatus | null>(null);
|
|
37
|
+
const [dark, setDark] = useState(true);
|
|
38
|
+
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
39
|
+
|
|
40
|
+
const fetchBranch = useCallback(async () => {
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetch(`/api/bridge/git-status?cwd=${encodeURIComponent(projectPath)}`);
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
setGitStatus(null);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const data = await res.json() as GitStatus;
|
|
48
|
+
setGitStatus(data.branch ? data : null);
|
|
49
|
+
} catch {
|
|
50
|
+
setGitStatus(null);
|
|
51
|
+
}
|
|
52
|
+
}, [projectPath]);
|
|
53
|
+
|
|
54
|
+
// Fetch on mount + poll every 30s
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
fetchBranch();
|
|
57
|
+
intervalRef.current = setInterval(fetchBranch, 30000);
|
|
58
|
+
return () => {
|
|
59
|
+
if (intervalRef.current) clearInterval(intervalRef.current);
|
|
60
|
+
};
|
|
61
|
+
}, [fetchBranch]);
|
|
62
|
+
|
|
63
|
+
// Watch for theme changes
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
setDark(isDarkMode());
|
|
66
|
+
const observer = new MutationObserver(() => setDark(isDarkMode()));
|
|
67
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
|
|
68
|
+
return () => observer.disconnect();
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
if (!gitStatus) return null;
|
|
72
|
+
|
|
73
|
+
const hue = branchToHue(gitStatus.branch!);
|
|
74
|
+
|
|
75
|
+
const bg = dark ? `hsla(${hue}, 55%, 22%, 0.9)` : `hsla(${hue}, 40%, 48%, 0.92)`;
|
|
76
|
+
const border = dark ? `hsla(${hue}, 60%, 45%, 0.5)` : `hsla(${hue}, 45%, 38%, 0.4)`;
|
|
77
|
+
const glow = dark
|
|
78
|
+
? `0 -3px 12px hsla(${hue}, 70%, 50%, 0.2), 0 0 6px hsla(${hue}, 60%, 40%, 0.1)`
|
|
79
|
+
: `0 -2px 8px hsla(${hue}, 50%, 50%, 0.15)`;
|
|
80
|
+
|
|
81
|
+
// Position: to the left of GlobalClaudePanel
|
|
82
|
+
// Collapsed panel: right-4 w-64 → branch tab right = 16px + 256px + 8px gap = 280px
|
|
83
|
+
// Expanded panel (sm+): right-4 w-[700px] → branch tab right = 16px + 700px + 8px gap = 724px
|
|
84
|
+
const rightPos = isTerminalExpanded
|
|
85
|
+
? 'right-4 sm:right-[724px]'
|
|
86
|
+
: 'right-[280px]';
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div
|
|
90
|
+
onClick={fetchBranch}
|
|
91
|
+
title={`${gitStatus.branch}${gitStatus.uncommitted > 0 ? ` — ${gitStatus.uncommitted} uncommitted` : ''}\nClick to refresh`}
|
|
92
|
+
className={`fixed z-40 bottom-0 cursor-pointer select-none transition-all duration-300 ease-in-out ${rightPos}`}
|
|
93
|
+
>
|
|
94
|
+
<div
|
|
95
|
+
className="rounded-t-md px-3 py-1.5 backdrop-blur-sm flex flex-col"
|
|
96
|
+
style={{
|
|
97
|
+
background: bg,
|
|
98
|
+
borderWidth: '1px 1px 0 1px',
|
|
99
|
+
borderStyle: 'solid',
|
|
100
|
+
borderColor: border,
|
|
101
|
+
boxShadow: glow,
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
<span className="max-w-[200px] truncate text-xs font-semibold text-white/90">
|
|
105
|
+
{gitStatus.branch}
|
|
106
|
+
</span>
|
|
107
|
+
{gitStatus.uncommitted > 0 && (
|
|
108
|
+
<span className="text-[10px] leading-tight text-white/55">
|
|
109
|
+
{gitStatus.uncommitted} uncommitted
|
|
110
|
+
</span>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback } from 'react';
|
|
4
|
+
import type { ChangelogVersion, ChangelogChangeType } from '@/lib/types';
|
|
5
|
+
|
|
6
|
+
interface ChangelogModalProps {
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Type styling
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
const TYPE_LABELS: Record<ChangelogChangeType, string> = {
|
|
15
|
+
feature: 'Feature',
|
|
16
|
+
bugfix: 'Fix',
|
|
17
|
+
improvement: 'Improved',
|
|
18
|
+
chore: 'Chore',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const TYPE_STYLES: Record<ChangelogChangeType, string> = {
|
|
22
|
+
feature:
|
|
23
|
+
'border-neon-blue-400/40 bg-neon-blue-400/15 text-neon-blue-600 dark:text-neon-blue-400',
|
|
24
|
+
bugfix:
|
|
25
|
+
'border-red-400/40 bg-red-400/15 text-red-600 dark:text-red-400',
|
|
26
|
+
improvement:
|
|
27
|
+
'border-emerald-400/40 bg-emerald-400/15 text-emerald-600 dark:text-emerald-400',
|
|
28
|
+
chore:
|
|
29
|
+
'border-void-400/40 bg-void-400/15 text-void-600 dark:text-void-300',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Component
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
export function ChangelogModal({ onClose }: ChangelogModalProps) {
|
|
37
|
+
const [data, setData] = useState<ChangelogVersion[] | null>(null);
|
|
38
|
+
const [loading, setLoading] = useState(true);
|
|
39
|
+
const [error, setError] = useState<string | null>(null);
|
|
40
|
+
|
|
41
|
+
// Fetch changelog on mount
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
let cancelled = false;
|
|
44
|
+
setLoading(true);
|
|
45
|
+
fetch('/api/changelog')
|
|
46
|
+
.then((r) => {
|
|
47
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
48
|
+
return r.json();
|
|
49
|
+
})
|
|
50
|
+
.then((json) => {
|
|
51
|
+
if (cancelled) return;
|
|
52
|
+
setData(Array.isArray(json) ? (json as ChangelogVersion[]) : []);
|
|
53
|
+
setError(null);
|
|
54
|
+
})
|
|
55
|
+
.catch((err: unknown) => {
|
|
56
|
+
if (cancelled) return;
|
|
57
|
+
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
58
|
+
setError(message);
|
|
59
|
+
setData([]);
|
|
60
|
+
})
|
|
61
|
+
.finally(() => {
|
|
62
|
+
if (cancelled) return;
|
|
63
|
+
setLoading(false);
|
|
64
|
+
});
|
|
65
|
+
return () => {
|
|
66
|
+
cancelled = true;
|
|
67
|
+
};
|
|
68
|
+
}, []);
|
|
69
|
+
|
|
70
|
+
// Escape to close
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
function handleKey(e: KeyboardEvent) {
|
|
73
|
+
if (e.key === 'Escape') {
|
|
74
|
+
e.stopPropagation();
|
|
75
|
+
onClose();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
document.addEventListener('keydown', handleKey);
|
|
79
|
+
return () => document.removeEventListener('keydown', handleKey);
|
|
80
|
+
}, [onClose]);
|
|
81
|
+
|
|
82
|
+
const formatDate = useCallback((iso: string): string => {
|
|
83
|
+
if (!iso) return '';
|
|
84
|
+
try {
|
|
85
|
+
const d = new Date(`${iso}T00:00:00`);
|
|
86
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
87
|
+
return d.toLocaleDateString('en-US', {
|
|
88
|
+
year: 'numeric',
|
|
89
|
+
month: 'short',
|
|
90
|
+
day: 'numeric',
|
|
91
|
+
});
|
|
92
|
+
} catch {
|
|
93
|
+
return iso;
|
|
94
|
+
}
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<div
|
|
99
|
+
className="fixed inset-0 z-[55] flex items-center justify-center bg-black/60 p-4 backdrop-blur-sm"
|
|
100
|
+
onClick={(e) => {
|
|
101
|
+
if (e.target === e.currentTarget) onClose();
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
<div className="flex w-full max-w-3xl max-h-[85vh] flex-col overflow-hidden rounded-xl border border-void-200 bg-white shadow-(--shadow-overlay) dark:border-void-700 dark:bg-void-850">
|
|
105
|
+
{/* Header */}
|
|
106
|
+
<div className="flex items-start justify-between border-b border-void-200 px-6 py-5 dark:border-void-800">
|
|
107
|
+
<div>
|
|
108
|
+
<div className="font-mono text-[10px] uppercase tracking-[0.22em] text-neon-blue-600 dark:text-neon-blue-400">
|
|
109
|
+
Release History
|
|
110
|
+
</div>
|
|
111
|
+
<h2 className="mt-1 text-2xl font-semibold tracking-tight text-void-900 dark:text-void-100">
|
|
112
|
+
Changelog
|
|
113
|
+
</h2>
|
|
114
|
+
</div>
|
|
115
|
+
<button
|
|
116
|
+
type="button"
|
|
117
|
+
onClick={onClose}
|
|
118
|
+
aria-label="Close changelog"
|
|
119
|
+
className="rounded-md p-1.5 text-void-500 transition-colors hover:bg-void-100 hover:text-void-900 dark:text-void-400 dark:hover:bg-void-800 dark:hover:text-void-100"
|
|
120
|
+
>
|
|
121
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden>
|
|
122
|
+
<path
|
|
123
|
+
d="M5 5l10 10M15 5L5 15"
|
|
124
|
+
stroke="currentColor"
|
|
125
|
+
strokeWidth="1.5"
|
|
126
|
+
strokeLinecap="round"
|
|
127
|
+
/>
|
|
128
|
+
</svg>
|
|
129
|
+
</button>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
{/* Content */}
|
|
133
|
+
<div className="flex-1 overflow-y-auto px-6 py-6">
|
|
134
|
+
{loading && (
|
|
135
|
+
<div className="flex items-center justify-center py-16">
|
|
136
|
+
<div className="font-mono text-xs uppercase tracking-widest text-void-400 dark:text-void-500">
|
|
137
|
+
Loading…
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
)}
|
|
141
|
+
|
|
142
|
+
{!loading && error && (
|
|
143
|
+
<div className="rounded-lg border border-red-300/60 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/50 dark:bg-red-950/30 dark:text-red-300">
|
|
144
|
+
Couldn't load the changelog: {error}
|
|
145
|
+
</div>
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
{!loading && !error && data && data.length === 0 && (
|
|
149
|
+
<div className="flex flex-col items-center justify-center py-16 text-center">
|
|
150
|
+
<div className="mb-2 font-mono text-[10px] uppercase tracking-[0.22em] text-void-400 dark:text-void-600">
|
|
151
|
+
No entries
|
|
152
|
+
</div>
|
|
153
|
+
<p className="text-sm text-void-500 dark:text-void-400">
|
|
154
|
+
The changelog is empty. Future releases will appear here.
|
|
155
|
+
</p>
|
|
156
|
+
</div>
|
|
157
|
+
)}
|
|
158
|
+
|
|
159
|
+
{!loading && !error && data && data.length > 0 && (
|
|
160
|
+
<ol className="relative space-y-9">
|
|
161
|
+
{/* Timeline rail */}
|
|
162
|
+
<div
|
|
163
|
+
className="absolute left-[7px] top-2 bottom-2 w-px bg-gradient-to-b from-neon-blue-400/50 via-void-300 to-transparent dark:from-neon-blue-400/40 dark:via-void-700"
|
|
164
|
+
aria-hidden
|
|
165
|
+
/>
|
|
166
|
+
|
|
167
|
+
{data.map((version, idx) => (
|
|
168
|
+
<li key={version.version} className="relative pl-8">
|
|
169
|
+
{/* Timeline dot */}
|
|
170
|
+
<div
|
|
171
|
+
className={`absolute left-0 top-[6px] h-[15px] w-[15px] rounded-full border-2 ${
|
|
172
|
+
idx === 0
|
|
173
|
+
? 'border-neon-blue-400 bg-neon-blue-400/20'
|
|
174
|
+
: 'border-void-300 bg-white dark:border-void-600 dark:bg-void-900'
|
|
175
|
+
}`}
|
|
176
|
+
aria-hidden
|
|
177
|
+
>
|
|
178
|
+
{idx === 0 && (
|
|
179
|
+
<div className="absolute inset-[2px] animate-pulse rounded-full bg-neon-blue-400/70" />
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
{/* Version header */}
|
|
184
|
+
<div className="mb-3 flex flex-wrap items-baseline gap-x-3 gap-y-1">
|
|
185
|
+
<span className="font-mono text-lg font-semibold text-void-900 dark:text-void-100">
|
|
186
|
+
v{version.version}
|
|
187
|
+
</span>
|
|
188
|
+
<span className="font-mono text-[11px] uppercase tracking-[0.15em] text-void-500 dark:text-void-500">
|
|
189
|
+
{formatDate(version.date)}
|
|
190
|
+
</span>
|
|
191
|
+
{idx === 0 && (
|
|
192
|
+
<span className="rounded border border-neon-blue-400/50 bg-neon-blue-400/15 px-1.5 py-[2px] font-mono text-[9px] font-medium uppercase tracking-[0.15em] text-neon-blue-600 dark:text-neon-blue-400">
|
|
193
|
+
Latest
|
|
194
|
+
</span>
|
|
195
|
+
)}
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
{/* Changes */}
|
|
199
|
+
<ul className="space-y-2.5">
|
|
200
|
+
{version.changes.map((change, ci) => (
|
|
201
|
+
<li key={ci} className="flex items-start gap-3">
|
|
202
|
+
<span
|
|
203
|
+
className={`mt-[2px] inline-flex w-20 shrink-0 items-center justify-center rounded border px-1.5 py-[3px] font-mono text-[10px] font-medium uppercase tracking-wider ${TYPE_STYLES[change.type]}`}
|
|
204
|
+
>
|
|
205
|
+
{TYPE_LABELS[change.type]}
|
|
206
|
+
</span>
|
|
207
|
+
<span className="text-sm leading-relaxed text-void-700 dark:text-void-300">
|
|
208
|
+
{change.description}
|
|
209
|
+
</span>
|
|
210
|
+
</li>
|
|
211
|
+
))}
|
|
212
|
+
</ul>
|
|
213
|
+
</li>
|
|
214
|
+
))}
|
|
215
|
+
</ol>
|
|
216
|
+
)}
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
{/* Footer */}
|
|
220
|
+
<div className="flex items-center justify-center gap-2 border-t border-void-200 bg-void-50 px-6 py-3 dark:border-void-800 dark:bg-void-900/50">
|
|
221
|
+
<span className="font-mono text-[10px] uppercase tracking-[0.18em] text-void-500 dark:text-void-500">
|
|
222
|
+
Press
|
|
223
|
+
</span>
|
|
224
|
+
<kbd className="rounded border border-void-300 bg-white px-1.5 py-[1px] font-mono text-[10px] text-void-700 dark:border-void-700 dark:bg-void-850 dark:text-void-300">
|
|
225
|
+
Esc
|
|
226
|
+
</kbd>
|
|
227
|
+
<span className="font-mono text-[10px] uppercase tracking-[0.18em] text-void-500 dark:text-void-500">
|
|
228
|
+
to close
|
|
229
|
+
</span>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
</div>
|
|
233
|
+
);
|
|
234
|
+
}
|