orbital-command 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +396 -0
  3. package/bin/orbital.js +362 -0
  4. package/dist/assets/WorkflowVisualizer-BZ21PIIF.js +84 -0
  5. package/dist/assets/WorkflowVisualizer-BZV40eAE.css +1 -0
  6. package/dist/assets/charts-D__PA1zp.js +72 -0
  7. package/dist/assets/index-D1G6i0nS.css +1 -0
  8. package/dist/assets/index-DpItvKpf.js +419 -0
  9. package/dist/assets/ui-BvF022GT.js +53 -0
  10. package/dist/assets/vendor-Dzv9lrRc.js +59 -0
  11. package/dist/index.html +19 -0
  12. package/dist/scanner-sweep.png +0 -0
  13. package/dist/server/server/adapters/index.js +34 -0
  14. package/dist/server/server/adapters/iterm2-adapter.js +29 -0
  15. package/dist/server/server/adapters/subprocess-adapter.js +21 -0
  16. package/dist/server/server/adapters/terminal-adapter.js +1 -0
  17. package/dist/server/server/config.js +156 -0
  18. package/dist/server/server/database.js +90 -0
  19. package/dist/server/server/index.js +372 -0
  20. package/dist/server/server/init.js +811 -0
  21. package/dist/server/server/parsers/event-parser.js +64 -0
  22. package/dist/server/server/parsers/scope-parser.js +188 -0
  23. package/dist/server/server/routes/config-routes.js +163 -0
  24. package/dist/server/server/routes/data-routes.js +461 -0
  25. package/dist/server/server/routes/dispatch-routes.js +215 -0
  26. package/dist/server/server/routes/git-routes.js +92 -0
  27. package/dist/server/server/routes/scope-routes.js +215 -0
  28. package/dist/server/server/routes/sprint-routes.js +116 -0
  29. package/dist/server/server/routes/version-routes.js +130 -0
  30. package/dist/server/server/routes/workflow-routes.js +185 -0
  31. package/dist/server/server/schema.js +90 -0
  32. package/dist/server/server/services/batch-orchestrator.js +253 -0
  33. package/dist/server/server/services/claude-session-service.js +352 -0
  34. package/dist/server/server/services/config-service.js +132 -0
  35. package/dist/server/server/services/deploy-service.js +51 -0
  36. package/dist/server/server/services/event-service.js +63 -0
  37. package/dist/server/server/services/gate-service.js +83 -0
  38. package/dist/server/server/services/git-service.js +309 -0
  39. package/dist/server/server/services/github-service.js +145 -0
  40. package/dist/server/server/services/readiness-service.js +184 -0
  41. package/dist/server/server/services/scope-cache.js +72 -0
  42. package/dist/server/server/services/scope-service.js +424 -0
  43. package/dist/server/server/services/sprint-orchestrator.js +312 -0
  44. package/dist/server/server/services/sprint-service.js +293 -0
  45. package/dist/server/server/services/workflow-service.js +397 -0
  46. package/dist/server/server/utils/cc-hooks-parser.js +49 -0
  47. package/dist/server/server/utils/dispatch-utils.js +305 -0
  48. package/dist/server/server/utils/logger.js +86 -0
  49. package/dist/server/server/utils/terminal-launcher.js +388 -0
  50. package/dist/server/server/utils/worktree-manager.js +98 -0
  51. package/dist/server/server/watchers/event-watcher.js +81 -0
  52. package/dist/server/server/watchers/scope-watcher.js +33 -0
  53. package/dist/server/shared/api-types.js +5 -0
  54. package/dist/server/shared/default-workflow.json +616 -0
  55. package/dist/server/shared/workflow-config.js +44 -0
  56. package/dist/server/shared/workflow-engine.js +353 -0
  57. package/index.html +15 -0
  58. package/package.json +110 -0
  59. package/postcss.config.js +6 -0
  60. package/schemas/orbital.config.schema.json +83 -0
  61. package/scripts/postinstall.js +24 -0
  62. package/scripts/start.sh +20 -0
  63. package/server/adapters/index.ts +41 -0
  64. package/server/adapters/iterm2-adapter.ts +37 -0
  65. package/server/adapters/subprocess-adapter.ts +25 -0
  66. package/server/adapters/terminal-adapter.ts +24 -0
  67. package/server/config.ts +234 -0
  68. package/server/database.ts +107 -0
  69. package/server/index.ts +452 -0
  70. package/server/init.ts +891 -0
  71. package/server/parsers/event-parser.ts +74 -0
  72. package/server/parsers/scope-parser.ts +240 -0
  73. package/server/routes/config-routes.ts +182 -0
  74. package/server/routes/data-routes.ts +548 -0
  75. package/server/routes/dispatch-routes.ts +275 -0
  76. package/server/routes/git-routes.ts +112 -0
  77. package/server/routes/scope-routes.ts +262 -0
  78. package/server/routes/sprint-routes.ts +142 -0
  79. package/server/routes/version-routes.ts +156 -0
  80. package/server/routes/workflow-routes.ts +198 -0
  81. package/server/schema.ts +90 -0
  82. package/server/services/batch-orchestrator.ts +286 -0
  83. package/server/services/claude-session-service.ts +441 -0
  84. package/server/services/config-service.ts +151 -0
  85. package/server/services/deploy-service.ts +98 -0
  86. package/server/services/event-service.ts +98 -0
  87. package/server/services/gate-service.ts +126 -0
  88. package/server/services/git-service.ts +391 -0
  89. package/server/services/github-service.ts +183 -0
  90. package/server/services/readiness-service.ts +250 -0
  91. package/server/services/scope-cache.ts +81 -0
  92. package/server/services/scope-service.ts +476 -0
  93. package/server/services/sprint-orchestrator.ts +361 -0
  94. package/server/services/sprint-service.ts +415 -0
  95. package/server/services/workflow-service.ts +461 -0
  96. package/server/utils/cc-hooks-parser.ts +70 -0
  97. package/server/utils/dispatch-utils.ts +395 -0
  98. package/server/utils/logger.ts +109 -0
  99. package/server/utils/terminal-launcher.ts +462 -0
  100. package/server/utils/worktree-manager.ts +104 -0
  101. package/server/watchers/event-watcher.ts +100 -0
  102. package/server/watchers/scope-watcher.ts +38 -0
  103. package/shared/api-types.ts +20 -0
  104. package/shared/default-workflow.json +616 -0
  105. package/shared/workflow-config.ts +170 -0
  106. package/shared/workflow-engine.ts +427 -0
  107. package/src/App.tsx +33 -0
  108. package/src/components/AgentBadge.tsx +40 -0
  109. package/src/components/BatchPreflightModal.tsx +115 -0
  110. package/src/components/CardDisplayToggle.tsx +74 -0
  111. package/src/components/ColumnHeaderActions.tsx +55 -0
  112. package/src/components/ColumnMenu.tsx +99 -0
  113. package/src/components/DeployHistory.tsx +141 -0
  114. package/src/components/DispatchModal.tsx +164 -0
  115. package/src/components/DispatchPopover.tsx +139 -0
  116. package/src/components/DragOverlay.tsx +25 -0
  117. package/src/components/DriftSidebar.tsx +140 -0
  118. package/src/components/EnvironmentStrip.tsx +88 -0
  119. package/src/components/ErrorBoundary.tsx +62 -0
  120. package/src/components/FilterChip.tsx +105 -0
  121. package/src/components/GateIndicator.tsx +33 -0
  122. package/src/components/IdeaDetailModal.tsx +190 -0
  123. package/src/components/IdeaFormDialog.tsx +113 -0
  124. package/src/components/KanbanColumn.tsx +201 -0
  125. package/src/components/MarkdownRenderer.tsx +114 -0
  126. package/src/components/NeonGrid.tsx +128 -0
  127. package/src/components/PromotionQueue.tsx +89 -0
  128. package/src/components/ScopeCard.tsx +234 -0
  129. package/src/components/ScopeDetailModal.tsx +255 -0
  130. package/src/components/ScopeFilterBar.tsx +152 -0
  131. package/src/components/SearchInput.tsx +102 -0
  132. package/src/components/SessionPanel.tsx +335 -0
  133. package/src/components/SprintContainer.tsx +303 -0
  134. package/src/components/SprintDependencyDialog.tsx +78 -0
  135. package/src/components/SprintPreflightModal.tsx +138 -0
  136. package/src/components/StatusBar.tsx +168 -0
  137. package/src/components/SwimCell.tsx +67 -0
  138. package/src/components/SwimLaneRow.tsx +94 -0
  139. package/src/components/SwimlaneBoardView.tsx +108 -0
  140. package/src/components/VersionBadge.tsx +139 -0
  141. package/src/components/ViewModeSelector.tsx +114 -0
  142. package/src/components/config/AgentChip.tsx +53 -0
  143. package/src/components/config/AgentCreateDialog.tsx +321 -0
  144. package/src/components/config/AgentEditor.tsx +175 -0
  145. package/src/components/config/DirectoryTree.tsx +582 -0
  146. package/src/components/config/FileEditor.tsx +550 -0
  147. package/src/components/config/HookChip.tsx +50 -0
  148. package/src/components/config/StageCard.tsx +198 -0
  149. package/src/components/config/TransitionZone.tsx +173 -0
  150. package/src/components/config/UnifiedWorkflowPipeline.tsx +216 -0
  151. package/src/components/config/WorkflowPipeline.tsx +161 -0
  152. package/src/components/source-control/BranchList.tsx +93 -0
  153. package/src/components/source-control/BranchPanel.tsx +105 -0
  154. package/src/components/source-control/CommitLog.tsx +100 -0
  155. package/src/components/source-control/CommitRow.tsx +47 -0
  156. package/src/components/source-control/GitHubPanel.tsx +110 -0
  157. package/src/components/source-control/GitHubSetupGuide.tsx +52 -0
  158. package/src/components/source-control/GitOverviewBar.tsx +101 -0
  159. package/src/components/source-control/PullRequestList.tsx +69 -0
  160. package/src/components/source-control/WorktreeList.tsx +80 -0
  161. package/src/components/ui/badge.tsx +41 -0
  162. package/src/components/ui/button.tsx +55 -0
  163. package/src/components/ui/card.tsx +78 -0
  164. package/src/components/ui/dialog.tsx +94 -0
  165. package/src/components/ui/popover.tsx +33 -0
  166. package/src/components/ui/scroll-area.tsx +54 -0
  167. package/src/components/ui/separator.tsx +28 -0
  168. package/src/components/ui/tabs.tsx +52 -0
  169. package/src/components/ui/toggle-switch.tsx +35 -0
  170. package/src/components/ui/tooltip.tsx +27 -0
  171. package/src/components/workflow/AddEdgeDialog.tsx +217 -0
  172. package/src/components/workflow/AddListDialog.tsx +201 -0
  173. package/src/components/workflow/ChecklistEditor.tsx +239 -0
  174. package/src/components/workflow/CommandPrefixManager.tsx +118 -0
  175. package/src/components/workflow/ConfigSettingsPanel.tsx +189 -0
  176. package/src/components/workflow/DirectionSelector.tsx +133 -0
  177. package/src/components/workflow/DispatchConfigPanel.tsx +180 -0
  178. package/src/components/workflow/EdgeDetailPanel.tsx +236 -0
  179. package/src/components/workflow/EdgePropertyEditor.tsx +251 -0
  180. package/src/components/workflow/EditToolbar.tsx +138 -0
  181. package/src/components/workflow/HookDetailPanel.tsx +250 -0
  182. package/src/components/workflow/HookExecutionLog.tsx +24 -0
  183. package/src/components/workflow/HookSourceModal.tsx +129 -0
  184. package/src/components/workflow/HooksDashboard.tsx +363 -0
  185. package/src/components/workflow/ListPropertyEditor.tsx +251 -0
  186. package/src/components/workflow/MigrationPreviewDialog.tsx +237 -0
  187. package/src/components/workflow/MovementRulesPanel.tsx +188 -0
  188. package/src/components/workflow/NodeDetailPanel.tsx +245 -0
  189. package/src/components/workflow/PresetSelector.tsx +414 -0
  190. package/src/components/workflow/SkillCommandBuilder.tsx +174 -0
  191. package/src/components/workflow/WorkflowEdgeComponent.tsx +145 -0
  192. package/src/components/workflow/WorkflowNode.tsx +147 -0
  193. package/src/components/workflow/graphLayout.ts +186 -0
  194. package/src/components/workflow/mergeHooks.ts +85 -0
  195. package/src/components/workflow/useEditHistory.ts +88 -0
  196. package/src/components/workflow/useWorkflowEditor.ts +262 -0
  197. package/src/components/workflow/validateConfig.ts +70 -0
  198. package/src/hooks/useActiveDispatches.ts +198 -0
  199. package/src/hooks/useBoardSettings.ts +170 -0
  200. package/src/hooks/useCardDisplay.ts +57 -0
  201. package/src/hooks/useCcHooks.ts +24 -0
  202. package/src/hooks/useConfigTree.ts +51 -0
  203. package/src/hooks/useEnforcementRules.ts +46 -0
  204. package/src/hooks/useEvents.ts +59 -0
  205. package/src/hooks/useFileEditor.ts +165 -0
  206. package/src/hooks/useGates.ts +57 -0
  207. package/src/hooks/useIdeaActions.ts +53 -0
  208. package/src/hooks/useKanbanDnd.ts +410 -0
  209. package/src/hooks/useOrbitalConfig.ts +54 -0
  210. package/src/hooks/usePipeline.ts +47 -0
  211. package/src/hooks/usePipelineData.ts +338 -0
  212. package/src/hooks/useReconnect.ts +25 -0
  213. package/src/hooks/useScopeFilters.ts +125 -0
  214. package/src/hooks/useScopeSessions.ts +44 -0
  215. package/src/hooks/useScopes.ts +67 -0
  216. package/src/hooks/useSearch.ts +67 -0
  217. package/src/hooks/useSettings.tsx +187 -0
  218. package/src/hooks/useSocket.ts +25 -0
  219. package/src/hooks/useSourceControl.ts +105 -0
  220. package/src/hooks/useSprintPreflight.ts +55 -0
  221. package/src/hooks/useSprints.ts +154 -0
  222. package/src/hooks/useStatusBarHighlight.ts +18 -0
  223. package/src/hooks/useSwimlaneBoardSettings.ts +104 -0
  224. package/src/hooks/useTheme.ts +9 -0
  225. package/src/hooks/useTransitionReadiness.ts +53 -0
  226. package/src/hooks/useVersion.ts +155 -0
  227. package/src/hooks/useViolations.ts +65 -0
  228. package/src/hooks/useWorkflow.tsx +125 -0
  229. package/src/hooks/useZoomModifier.ts +19 -0
  230. package/src/index.css +797 -0
  231. package/src/layouts/DashboardLayout.tsx +113 -0
  232. package/src/lib/collisionDetection.ts +20 -0
  233. package/src/lib/scope-fields.ts +61 -0
  234. package/src/lib/swimlane.ts +146 -0
  235. package/src/lib/utils.ts +15 -0
  236. package/src/main.tsx +19 -0
  237. package/src/socket.ts +11 -0
  238. package/src/types/index.ts +497 -0
  239. package/src/views/AgentFeed.tsx +339 -0
  240. package/src/views/DeployPipeline.tsx +59 -0
  241. package/src/views/EnforcementView.tsx +378 -0
  242. package/src/views/PrimitivesConfig.tsx +500 -0
  243. package/src/views/QualityGates.tsx +1012 -0
  244. package/src/views/ScopeBoard.tsx +454 -0
  245. package/src/views/SessionTimeline.tsx +516 -0
  246. package/src/views/Settings.tsx +183 -0
  247. package/src/views/SourceControl.tsx +95 -0
  248. package/src/views/WorkflowVisualizer.tsx +382 -0
  249. package/tailwind.config.js +161 -0
  250. package/templates/agents/AUTO-INVOKE.md +180 -0
  251. package/templates/agents/CONFLICT-RESOLUTION.md +128 -0
  252. package/templates/agents/QUICK-REFERENCE.md +122 -0
  253. package/templates/agents/README.md +188 -0
  254. package/templates/agents/SKILL-TRIGGERS.md +100 -0
  255. package/templates/agents/blue-team/frontend-designer.md +424 -0
  256. package/templates/agents/green-team/architect.md +526 -0
  257. package/templates/agents/green-team/rules-enforcer.md +131 -0
  258. package/templates/agents/red-team/attacker-learned.md +24 -0
  259. package/templates/agents/red-team/attacker.md +486 -0
  260. package/templates/agents/red-team/chaos.md +548 -0
  261. package/templates/agents/reference/component-registry.md +82 -0
  262. package/templates/agents/workflows/full-mode.md +218 -0
  263. package/templates/agents/workflows/quick-mode.md +118 -0
  264. package/templates/agents/workflows/security-mode.md +283 -0
  265. package/templates/anti-patterns/dangerous-shortcuts.md +427 -0
  266. package/templates/config/agent-triggers.json +92 -0
  267. package/templates/hooks/agent-team-gate.sh +31 -0
  268. package/templates/hooks/agent-trigger.sh +97 -0
  269. package/templates/hooks/block-push.sh +66 -0
  270. package/templates/hooks/block-workarounds.sh +61 -0
  271. package/templates/hooks/blocker-check.sh +28 -0
  272. package/templates/hooks/completion-checklist.sh +28 -0
  273. package/templates/hooks/decision-capture.sh +15 -0
  274. package/templates/hooks/dependency-check.sh +27 -0
  275. package/templates/hooks/end-session.sh +31 -0
  276. package/templates/hooks/exploration-logger.sh +37 -0
  277. package/templates/hooks/files-changed-summary.sh +37 -0
  278. package/templates/hooks/get-session-id.sh +49 -0
  279. package/templates/hooks/git-commit-guard.sh +34 -0
  280. package/templates/hooks/init-session.sh +93 -0
  281. package/templates/hooks/orbital-emit.sh +79 -0
  282. package/templates/hooks/orbital-report-deploy.sh +78 -0
  283. package/templates/hooks/orbital-report-gates.sh +40 -0
  284. package/templates/hooks/orbital-report-violation.sh +36 -0
  285. package/templates/hooks/orbital-scope-update.sh +53 -0
  286. package/templates/hooks/phase-verify-reminder.sh +26 -0
  287. package/templates/hooks/review-gate-check.sh +82 -0
  288. package/templates/hooks/scope-commit-logger.sh +37 -0
  289. package/templates/hooks/scope-create-cleanup.sh +36 -0
  290. package/templates/hooks/scope-create-gate.sh +80 -0
  291. package/templates/hooks/scope-create-tracker.sh +17 -0
  292. package/templates/hooks/scope-file-sync.sh +53 -0
  293. package/templates/hooks/scope-gate.sh +35 -0
  294. package/templates/hooks/scope-helpers.sh +188 -0
  295. package/templates/hooks/scope-lifecycle-gate.sh +139 -0
  296. package/templates/hooks/scope-prepare.sh +244 -0
  297. package/templates/hooks/scope-transition.sh +172 -0
  298. package/templates/hooks/session-enforcer.sh +143 -0
  299. package/templates/hooks/time-tracker.sh +33 -0
  300. package/templates/lessons-learned.md +15 -0
  301. package/templates/orbital.config.json +35 -0
  302. package/templates/presets/development.json +42 -0
  303. package/templates/presets/gitflow.json +712 -0
  304. package/templates/presets/minimal.json +23 -0
  305. package/templates/quick/rules.md +218 -0
  306. package/templates/scopes/_template.md +255 -0
  307. package/templates/settings-hooks.json +98 -0
  308. package/templates/skills/git-commit/SKILL.md +85 -0
  309. package/templates/skills/git-dev/SKILL.md +99 -0
  310. package/templates/skills/git-hotfix/SKILL.md +223 -0
  311. package/templates/skills/git-main/SKILL.md +84 -0
  312. package/templates/skills/git-production/SKILL.md +165 -0
  313. package/templates/skills/git-staging/SKILL.md +112 -0
  314. package/templates/skills/scope-create/SKILL.md +81 -0
  315. package/templates/skills/scope-fix-review/SKILL.md +168 -0
  316. package/templates/skills/scope-implement/SKILL.md +110 -0
  317. package/templates/skills/scope-post-review/SKILL.md +144 -0
  318. package/templates/skills/scope-pre-review/SKILL.md +211 -0
  319. package/templates/skills/scope-verify/SKILL.md +201 -0
  320. package/templates/skills/session-init/SKILL.md +62 -0
  321. package/templates/skills/session-resume/SKILL.md +201 -0
  322. package/templates/skills/test-checks/SKILL.md +171 -0
  323. package/templates/skills/test-code-review/SKILL.md +252 -0
  324. package/tsconfig.json +25 -0
  325. package/vite.config.ts +38 -0
@@ -0,0 +1,128 @@
1
+ import { useRef, useEffect } from 'react';
2
+
3
+ const GAP = 28;
4
+ const DOT_RADIUS = 1;
5
+ const DOT_COLOR = 'rgba(255, 255, 255, 0.12)';
6
+ const DOT_COLOR_BRIGHT = 'rgba(255, 255, 255, 0.35)';
7
+
8
+ // How far the cursor influence reaches (px)
9
+ const INFLUENCE_RADIUS = 180;
10
+ // How much dots get pushed away (px)
11
+ const PUSH_STRENGTH = 14;
12
+
13
+ /**
14
+ * Full-screen dot grid that warps around the cursor.
15
+ * Renders to a <canvas> — event-driven, zero CPU when idle.
16
+ * pointer-events: none — it never blocks clicks.
17
+ */
18
+ const CURSOR_OFFSCREEN = { x: -9999, y: -9999 };
19
+
20
+ export function NeonGrid() {
21
+ const canvasRef = useRef<HTMLCanvasElement>(null);
22
+ const mouseRef = useRef(CURSOR_OFFSCREEN);
23
+ const pendingRef = useRef(false);
24
+ const rafIdRef = useRef(0);
25
+
26
+ useEffect(() => {
27
+ const canvas = canvasRef.current;
28
+ if (!canvas) return;
29
+ const ctx = canvas.getContext('2d');
30
+ if (!ctx) return;
31
+
32
+ function draw() {
33
+ pendingRef.current = false;
34
+ if (!ctx || !canvas) return;
35
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
36
+
37
+ const mx = mouseRef.current.x;
38
+ const my = mouseRef.current.y;
39
+ const r2 = INFLUENCE_RADIUS * INFLUENCE_RADIUS;
40
+
41
+ const cols = Math.ceil(canvas.width / GAP) + 1;
42
+ const rows = Math.ceil(canvas.height / GAP) + 1;
43
+
44
+ for (let row = 0; row < rows; row++) {
45
+ for (let col = 0; col < cols; col++) {
46
+ const baseX = col * GAP;
47
+ const baseY = row * GAP;
48
+
49
+ const dx = baseX - mx;
50
+ const dy = baseY - my;
51
+ const dist2 = dx * dx + dy * dy;
52
+
53
+ let drawX = baseX;
54
+ let drawY = baseY;
55
+ let color = DOT_COLOR;
56
+ let radius = DOT_RADIUS;
57
+
58
+ if (dist2 < r2) {
59
+ const dist = Math.sqrt(dist2);
60
+ const t = 1 - dist / INFLUENCE_RADIUS;
61
+ const ease = t * t;
62
+ const push = ease * PUSH_STRENGTH;
63
+
64
+ if (dist > 0.1) {
65
+ drawX += (dx / dist) * push;
66
+ drawY += (dy / dist) * push;
67
+ }
68
+
69
+ color = DOT_COLOR_BRIGHT;
70
+ radius = DOT_RADIUS + ease * 0.6;
71
+ }
72
+
73
+ ctx.beginPath();
74
+ ctx.arc(drawX, drawY, radius, 0, Math.PI * 2);
75
+ ctx.fillStyle = color;
76
+ ctx.fill();
77
+ }
78
+ }
79
+ }
80
+
81
+ function scheduleFrame() {
82
+ if (!pendingRef.current) {
83
+ pendingRef.current = true;
84
+ rafIdRef.current = requestAnimationFrame(draw);
85
+ }
86
+ }
87
+
88
+ function resize() {
89
+ if (!canvas) return;
90
+ canvas.width = window.innerWidth;
91
+ canvas.height = window.innerHeight;
92
+ scheduleFrame();
93
+ }
94
+
95
+ function onMouseMove(e: MouseEvent) {
96
+ mouseRef.current = { x: e.clientX, y: e.clientY };
97
+ scheduleFrame();
98
+ }
99
+
100
+ // document (not window) — fires reliably when cursor leaves the viewport
101
+ function onMouseLeave() {
102
+ mouseRef.current = CURSOR_OFFSCREEN;
103
+ scheduleFrame();
104
+ }
105
+
106
+ resize();
107
+ scheduleFrame(); // Initial static grid render
108
+
109
+ window.addEventListener('resize', resize);
110
+ window.addEventListener('mousemove', onMouseMove);
111
+ document.addEventListener('mouseleave', onMouseLeave);
112
+
113
+ return () => {
114
+ cancelAnimationFrame(rafIdRef.current);
115
+ window.removeEventListener('resize', resize);
116
+ window.removeEventListener('mousemove', onMouseMove);
117
+ document.removeEventListener('mouseleave', onMouseLeave);
118
+ };
119
+ }, []);
120
+
121
+ return (
122
+ <canvas
123
+ ref={canvasRef}
124
+ className="neon-grid-canvas"
125
+ style={{ pointerEvents: 'none' }}
126
+ />
127
+ );
128
+ }
@@ -0,0 +1,89 @@
1
+ import { CheckCircle2 } from 'lucide-react';
2
+ import { formatDistanceToNow } from 'date-fns';
3
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
4
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
5
+ import { ScrollArea } from '@/components/ui/scroll-area';
6
+ import type { DriftCommit } from '@/types';
7
+
8
+ interface Props {
9
+ devToStaging: { count: number; commits: DriftCommit[] };
10
+ stagingToMain: { count: number; commits: DriftCommit[] };
11
+ }
12
+
13
+ function CommitRow({ commit }: { commit: DriftCommit }) {
14
+ return (
15
+ <div className="flex items-center gap-4 rounded px-2.5 py-1.5 transition-colors hover:bg-surface-light">
16
+ <code className="font-mono text-xs text-accent-blue shrink-0">
17
+ {commit.sha.slice(0, 7)}
18
+ </code>
19
+ <span className="flex-1 truncate text-xs font-normal text-foreground">
20
+ {commit.message}
21
+ </span>
22
+ <span className="shrink-0 text-xs text-muted-foreground">
23
+ {commit.author}
24
+ </span>
25
+ <span className="shrink-0 text-[10px] text-muted-foreground/60">
26
+ {formatDistanceToNow(new Date(commit.date), { addSuffix: true })}
27
+ </span>
28
+ </div>
29
+ );
30
+ }
31
+
32
+ function EmptySync() {
33
+ return (
34
+ <div className="flex flex-col items-center justify-center py-12">
35
+ <CheckCircle2 className="mb-2 h-8 w-8 text-bid-green" />
36
+ <span className="text-sm text-bid-green">In sync</span>
37
+ </div>
38
+ );
39
+ }
40
+
41
+ export function PromotionQueue({ devToStaging, stagingToMain }: Props) {
42
+ return (
43
+ <Card className="lg:col-span-2">
44
+ <CardHeader>
45
+ <CardTitle className="text-base">Promotion Queue</CardTitle>
46
+ </CardHeader>
47
+ <CardContent>
48
+ <Tabs defaultValue="dev-staging">
49
+ <TabsList>
50
+ <TabsTrigger value="dev-staging">
51
+ dev &rarr; staging ({devToStaging.count})
52
+ </TabsTrigger>
53
+ <TabsTrigger value="staging-main">
54
+ staging &rarr; main ({stagingToMain.count})
55
+ </TabsTrigger>
56
+ </TabsList>
57
+
58
+ <TabsContent value="dev-staging">
59
+ {devToStaging.commits.length === 0 ? (
60
+ <EmptySync />
61
+ ) : (
62
+ <ScrollArea className="max-h-[400px]">
63
+ <div className="space-y-0.5">
64
+ {devToStaging.commits.map((c) => (
65
+ <CommitRow key={c.sha} commit={c} />
66
+ ))}
67
+ </div>
68
+ </ScrollArea>
69
+ )}
70
+ </TabsContent>
71
+
72
+ <TabsContent value="staging-main">
73
+ {stagingToMain.commits.length === 0 ? (
74
+ <EmptySync />
75
+ ) : (
76
+ <ScrollArea className="max-h-[400px]">
77
+ <div className="space-y-0.5">
78
+ {stagingToMain.commits.map((c) => (
79
+ <CommitRow key={c.sha} commit={c} />
80
+ ))}
81
+ </div>
82
+ </ScrollArea>
83
+ )}
84
+ </TabsContent>
85
+ </Tabs>
86
+ </CardContent>
87
+ </Card>
88
+ );
89
+ }
@@ -0,0 +1,234 @@
1
+ import { useDraggable } from '@dnd-kit/core';
2
+ import { CSS } from '@dnd-kit/utilities';
3
+ import { Lightbulb, Sparkles, AlertTriangle, Undo2, X } from 'lucide-react';
4
+ import type { Scope, CardDisplayConfig } from '@/types';
5
+ import { Card, CardContent } from '@/components/ui/card';
6
+ import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover';
7
+ import { cn, formatScopeId } from '@/lib/utils';
8
+ import { useActiveDispatches } from '@/hooks/useActiveDispatches';
9
+ import { useWorkflow } from '@/hooks/useWorkflow';
10
+
11
+ interface ScopeCardProps {
12
+ scope: Scope;
13
+ onClick?: (scope: Scope) => void;
14
+ isDragOverlay?: boolean;
15
+ cardDisplay?: CardDisplayConfig;
16
+ dimmed?: boolean;
17
+ }
18
+
19
+ const PRIORITY_COLOR: Record<string, string> = {
20
+ critical: 'border-ask-red text-ask-red',
21
+ high: 'border-warning-amber text-warning-amber',
22
+ medium: 'border-accent-blue text-accent-blue',
23
+ low: 'border-muted-foreground/30 text-muted-foreground',
24
+ };
25
+
26
+ const CATEGORY_COLOR: Record<string, string> = {
27
+ 'feature': 'border-category-feature text-category-feature',
28
+ 'bugfix': 'border-category-bugfix text-category-bugfix',
29
+ 'refactor': 'border-category-refactor text-category-refactor',
30
+ 'infrastructure': 'border-category-infrastructure text-category-infrastructure',
31
+ 'docs': 'border-category-docs text-category-docs',
32
+ };
33
+
34
+ const CATEGORY_BORDER: Record<string, string> = {
35
+ 'feature': 'border-l-2 border-l-category-feature scope-cat-feature',
36
+ 'bugfix': 'border-l-2 border-l-category-bugfix scope-cat-bugfix',
37
+ 'refactor': 'border-l-2 border-l-category-refactor scope-cat-refactor',
38
+ 'infrastructure': 'border-l-2 border-l-category-infrastructure scope-cat-infrastructure',
39
+ 'docs': 'border-l-2 border-l-category-docs scope-cat-docs',
40
+ };
41
+
42
+ const GHOST = 'inline-block rounded border px-1.5 py-0 text-[10px] uppercase bg-transparent';
43
+
44
+ interface AbandonedPopoverProps {
45
+ scopeId: number;
46
+ info: { from_status: string | null };
47
+ onRecover: (scopeId: number, fromStatus: string) => Promise<void>;
48
+ onDismiss: (scopeId: number) => Promise<void>;
49
+ }
50
+
51
+ function AbandonedPopover({ scopeId, info, onRecover, onDismiss }: AbandonedPopoverProps) {
52
+ return (
53
+ <Popover>
54
+ <PopoverTrigger asChild onClick={(e) => e.stopPropagation()}>
55
+ <button className="flex items-center gap-0.5 text-amber-500 hover:text-amber-400 transition-colors">
56
+ <AlertTriangle className="h-3 w-3" />
57
+ </button>
58
+ </PopoverTrigger>
59
+ <PopoverContent
60
+ className="w-56 p-3"
61
+ side="top"
62
+ align="end"
63
+ onClick={(e) => e.stopPropagation()}
64
+ >
65
+ <p className="text-xs text-muted-foreground mb-2">
66
+ Session ended without completing work.
67
+ </p>
68
+ <div className="flex flex-col gap-1.5">
69
+ {info.from_status && (
70
+ <button
71
+ className="flex items-center gap-1.5 text-xs px-2 py-1.5 rounded bg-amber-500/10 text-amber-500 hover:bg-amber-500/20 transition-colors"
72
+ onClick={() => onRecover(scopeId, info.from_status!)}
73
+ >
74
+ <Undo2 className="h-3 w-3" />
75
+ Revert to {info.from_status}
76
+ </button>
77
+ )}
78
+ <button
79
+ className="flex items-center gap-1.5 text-xs px-2 py-1.5 rounded bg-muted text-muted-foreground hover:bg-muted/80 transition-colors"
80
+ onClick={() => onDismiss(scopeId)}
81
+ >
82
+ <X className="h-3 w-3" />
83
+ Keep here
84
+ </button>
85
+ </div>
86
+ </PopoverContent>
87
+ </Popover>
88
+ );
89
+ }
90
+
91
+ function abbreviateEffort(raw: string): string {
92
+ const s = raw.toLowerCase().trim();
93
+ const minMatch = s.match(/^~?(\d+)(?:\s*-\s*\d+)?\s*min/);
94
+ if (minMatch) return `${minMatch[1]}M`;
95
+ const hrMatch = s.match(/^~?(\d+(?:\.\d+)?)(?:\s*-\s*\d+(?:\.\d+)?)?\s*hour/);
96
+ if (hrMatch) return `${hrMatch[1]}H`;
97
+ const parenMatch = s.match(/\((\d+(?:\.\d+)?)(?:\s*-\s*\d+(?:\.\d+)?)?\s*(hour|min)/);
98
+ if (parenMatch) return `${parenMatch[1]}${parenMatch[2].startsWith('h') ? 'H' : 'M'}`;
99
+ if (s.includes('large') || s.includes('multi')) return 'LG';
100
+ if (s.includes('medium') || s.includes('half')) return 'MD';
101
+ if (s.includes('small')) return 'SM';
102
+ if (s === 'tbd') return 'TBD';
103
+ return 'TBD';
104
+ }
105
+
106
+ export function ScopeCard({ scope, onClick, isDragOverlay, cardDisplay, dimmed }: ScopeCardProps) {
107
+ const {
108
+ attributes,
109
+ listeners,
110
+ setNodeRef,
111
+ transform,
112
+ isDragging,
113
+ } = useDraggable({
114
+ id: scope.id,
115
+ disabled: isDragOverlay || dimmed,
116
+ });
117
+
118
+ const style = transform
119
+ ? { transform: CSS.Translate.toString(transform) }
120
+ : undefined;
121
+
122
+ const { engine } = useWorkflow();
123
+ const { activeScopes, abandonedScopes, recoverScope, dismissAbandoned } = useActiveDispatches();
124
+ const entryPointId = engine.getEntryPoint().id;
125
+ const isIdea = scope.status === entryPointId;
126
+ const isGhost = isIdea && !!scope.is_ghost;
127
+ const isDispatched = !isIdea && activeScopes.has(scope.id);
128
+ const abandonedInfo = !isIdea ? abandonedScopes.get(scope.id) : undefined;
129
+ const isAbandoned = !!abandonedInfo && !isDispatched;
130
+
131
+ return (
132
+ <Card
133
+ ref={setNodeRef}
134
+ style={style}
135
+ className={cn(
136
+ 'scope-card cursor-grab transition-[colors,opacity] duration-200 hover:bg-surface-light active:cursor-grabbing',
137
+ isGhost
138
+ ? 'scope-card-ghost ghost-shimmer opacity-70'
139
+ : isIdea
140
+ ? 'border-l-2 border-dashed border-l-warning-amber/60'
141
+ : scope.category ? CATEGORY_BORDER[scope.category] : '',
142
+ isDispatched && 'scope-card-dispatched',
143
+ isAbandoned && 'scope-card-abandoned',
144
+ isDragging && 'opacity-30',
145
+ dimmed && !isDragging && 'opacity-30 cursor-default',
146
+ )}
147
+ onClick={() => {
148
+ if (!isDragging) onClick?.(scope);
149
+ }}
150
+ {...attributes}
151
+ {...listeners}
152
+ >
153
+ <CardContent className="px-2.5 py-1.5">
154
+ {/* Header: ID/idea label + badges */}
155
+ <div className="mb-1.5 flex items-center gap-1.5">
156
+ {isGhost ? (
157
+ <span className="flex items-center gap-1 text-xxs text-purple-400">
158
+ <Sparkles className="h-3 w-3" />
159
+ ai suggestion
160
+ </span>
161
+ ) : isIdea ? (
162
+ <span className="flex items-center gap-1 text-xxs text-warning-amber">
163
+ <Lightbulb className="h-3 w-3" />
164
+ idea
165
+ </span>
166
+ ) : (
167
+ <span className="font-mono text-xxs text-muted-foreground flex items-center gap-1">
168
+ {isDispatched && <span className="h-1.5 w-1.5 rounded-full bg-pink-500 dispatch-pulse" />}
169
+ {isAbandoned && <span className="h-1.5 w-1.5 rounded-full bg-amber-500" />}
170
+ {formatScopeId(scope.id)}
171
+ </span>
172
+ )}
173
+ {!isIdea && (
174
+ <div className="ml-auto flex items-center gap-1">
175
+ {isAbandoned && (
176
+ <AbandonedPopover
177
+ scopeId={scope.id}
178
+ info={abandonedInfo}
179
+ onRecover={recoverScope}
180
+ onDismiss={dismissAbandoned}
181
+ />
182
+ )}
183
+ {scope.effort_estimate && cardDisplay?.effort !== false && (
184
+ <span className={cn(GHOST, 'effort-ghost border-muted-foreground/30 text-muted-foreground')}>
185
+ {abbreviateEffort(scope.effort_estimate)}
186
+ </span>
187
+ )}
188
+ {scope.category && cardDisplay?.category !== false && (
189
+ <span className={cn(
190
+ GHOST,
191
+ CATEGORY_COLOR[scope.category] ?? 'border-muted-foreground/30 text-muted-foreground'
192
+ )}>
193
+ {scope.category}
194
+ </span>
195
+ )}
196
+ {scope.priority && cardDisplay?.priority !== false && (
197
+ <span className={cn(
198
+ GHOST,
199
+ PRIORITY_COLOR[scope.priority] ?? 'border-muted-foreground/30 text-muted-foreground'
200
+ )}>
201
+ {scope.priority}
202
+ </span>
203
+ )}
204
+ </div>
205
+ )}
206
+ </div>
207
+
208
+ {/* Title */}
209
+ <h3 className="text-xs font-light leading-snug line-clamp-2">
210
+ {scope.title}
211
+ </h3>
212
+
213
+ {/* Tags (ideas don't have tags) */}
214
+ {!isIdea && scope.tags.length > 0 && cardDisplay?.tags !== false && (
215
+ <div className="mt-2 flex flex-wrap gap-1">
216
+ {scope.tags.slice(0, 3).map((tag) => (
217
+ <span
218
+ key={tag}
219
+ className="glass-pill inline-block rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground"
220
+ >
221
+ {tag}
222
+ </span>
223
+ ))}
224
+ {scope.tags.length > 3 && (
225
+ <span className="text-[10px] text-muted-foreground">
226
+ +{scope.tags.length - 3}
227
+ </span>
228
+ )}
229
+ </div>
230
+ )}
231
+ </CardContent>
232
+ </Card>
233
+ );
234
+ }