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,190 @@
1
+ import { useState, useEffect, useCallback, useRef } from 'react';
2
+ import { Trash2, Check, X, Sparkles } from 'lucide-react';
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogHeader,
7
+ } from '@/components/ui/dialog';
8
+ import { Button } from '@/components/ui/button';
9
+ import type { Scope } from '@/types';
10
+
11
+ interface IdeaDetailModalProps {
12
+ scope: Scope | null;
13
+ open: boolean;
14
+ onClose: () => void;
15
+ onDelete: (id: number) => void;
16
+ onApprove: (id: number) => void;
17
+ onReject: (id: number) => void;
18
+ }
19
+
20
+ export function IdeaDetailModal({ scope, open, onClose, onDelete, onApprove, onReject }: IdeaDetailModalProps) {
21
+ const [title, setTitle] = useState('');
22
+ const [description, setDescription] = useState('');
23
+ const [savedTitle, setSavedTitle] = useState('');
24
+ const [savedDescription, setSavedDescription] = useState('');
25
+ const [saving, setSaving] = useState(false);
26
+ const [confirmDelete, setConfirmDelete] = useState(false);
27
+ const titleRef = useRef<HTMLInputElement>(null);
28
+
29
+ const isGhost = !!scope?.is_ghost;
30
+ const isDirty = title !== savedTitle || description !== savedDescription;
31
+
32
+ // Sync state when scope changes
33
+ useEffect(() => {
34
+ if (scope && open) {
35
+ const t = scope.title ?? '';
36
+ const d = scope.raw_content ?? '';
37
+ setTitle(t);
38
+ setDescription(d);
39
+ setSavedTitle(t);
40
+ setSavedDescription(d);
41
+ setConfirmDelete(false);
42
+ if (!isGhost) setTimeout(() => titleRef.current?.focus(), 100);
43
+ }
44
+ }, [scope?.id, open]); // eslint-disable-line react-hooks/exhaustive-deps
45
+
46
+ const save = useCallback(async () => {
47
+ if (!scope || !isDirty || saving || isGhost) return;
48
+ setSaving(true);
49
+ try {
50
+ const res = await fetch(`/api/orbital/ideas/${scope.id}`, {
51
+ method: 'PATCH',
52
+ headers: { 'Content-Type': 'application/json' },
53
+ body: JSON.stringify({ title, description }),
54
+ });
55
+ if (res.ok) {
56
+ setSavedTitle(title);
57
+ setSavedDescription(description);
58
+ }
59
+ } finally {
60
+ setSaving(false);
61
+ }
62
+ }, [scope, title, description, isDirty, saving, isGhost]);
63
+
64
+ // Auto-save every 10s when dirty (not for ghosts)
65
+ useEffect(() => {
66
+ if (!isDirty || !open || isGhost) return;
67
+ const timer = setInterval(() => { save(); }, 10_000);
68
+ return () => clearInterval(timer);
69
+ }, [isDirty, open, save, isGhost]);
70
+
71
+ function handleClose() {
72
+ if (isDirty && !isGhost) {
73
+ if (window.confirm('Save changes before closing?')) {
74
+ save().then(onClose);
75
+ return;
76
+ }
77
+ }
78
+ onClose();
79
+ }
80
+
81
+ function handleDelete() {
82
+ if (!confirmDelete) {
83
+ setConfirmDelete(true);
84
+ return;
85
+ }
86
+ if (scope) onDelete(scope.id);
87
+ }
88
+
89
+ if (!scope) return null;
90
+
91
+ return (
92
+ <Dialog open={open} onOpenChange={(isOpen) => { if (!isOpen) handleClose(); }}>
93
+ <DialogContent className="max-w-md p-0 gap-0 flex flex-col max-h-[70vh]">
94
+ {/* Header */}
95
+ <DialogHeader className="px-4 pt-3 pb-2">
96
+ <div className="flex items-center gap-2 pr-8">
97
+ <div className="flex-1 min-w-0">
98
+ {isGhost ? (
99
+ <div className="flex items-center gap-2">
100
+ <Sparkles className="h-3.5 w-3.5 text-purple-400 shrink-0" />
101
+ <span className="text-sm font-normal text-foreground truncate">{title}</span>
102
+ </div>
103
+ ) : (
104
+ <input
105
+ ref={titleRef}
106
+ className="w-full bg-transparent text-sm font-normal text-foreground border-none focus:outline-none focus:ring-0 placeholder:text-muted-foreground"
107
+ placeholder="Idea title..."
108
+ value={title}
109
+ onChange={(e) => setTitle(e.target.value)}
110
+ />
111
+ )}
112
+ </div>
113
+ {isGhost ? (
114
+ <span className="shrink-0 rounded border border-purple-500/30 bg-purple-500/10 px-1.5 py-0.5 text-[10px] text-purple-400 uppercase">
115
+ ai suggestion
116
+ </span>
117
+ ) : confirmDelete ? (
118
+ <div className="flex items-center gap-1">
119
+ <Button size="sm" variant="destructive" className="h-6 text-xxs" onClick={handleDelete}>
120
+ Confirm
121
+ </Button>
122
+ <Button size="sm" variant="ghost" className="h-6 text-xxs" onClick={() => setConfirmDelete(false)}>
123
+ No
124
+ </Button>
125
+ </div>
126
+ ) : (
127
+ <Button
128
+ variant="ghost"
129
+ size="icon"
130
+ className="h-6 w-6 shrink-0 text-muted-foreground hover:text-destructive"
131
+ onClick={handleDelete}
132
+ title="Delete idea"
133
+ >
134
+ <Trash2 className="h-3.5 w-3.5" />
135
+ </Button>
136
+ )}
137
+ </div>
138
+ </DialogHeader>
139
+
140
+ {/* Description */}
141
+ <div className="flex-1 min-h-0 px-4 pb-4">
142
+ {isGhost ? (
143
+ <div className="w-full min-h-[200px] rounded bg-muted/20 px-3 py-2.5 text-xs text-foreground/80 border border-border/50 whitespace-pre-wrap">
144
+ {description || <span className="text-muted-foreground italic">No description</span>}
145
+ </div>
146
+ ) : (
147
+ <textarea
148
+ className="w-full h-full min-h-[200px] rounded bg-muted/30 px-3 py-2.5 text-xs text-foreground border border-border focus:outline-none focus:ring-1 focus:ring-primary/50 resize-none placeholder:text-muted-foreground"
149
+ placeholder="Describe the idea... What problem does it solve? Any notes on approach?"
150
+ value={description}
151
+ onChange={(e) => setDescription(e.target.value)}
152
+ />
153
+ )}
154
+ </div>
155
+
156
+ {/* Footer */}
157
+ {isGhost ? (
158
+ <div className="px-4 pb-3 flex items-center gap-2">
159
+ <Button
160
+ size="sm"
161
+ className="flex-1 bg-green-600/20 border border-green-500/30 text-green-400 hover:bg-green-600/30 hover:text-green-300"
162
+ onClick={() => onApprove(scope.id)}
163
+ >
164
+ <Check className="h-3.5 w-3.5 mr-1.5" />
165
+ Approve
166
+ </Button>
167
+ <Button
168
+ size="sm"
169
+ variant="ghost"
170
+ className="text-red-400 hover:text-red-300 hover:bg-red-500/10"
171
+ onClick={() => onReject(scope.id)}
172
+ >
173
+ <X className="h-3.5 w-3.5 mr-1.5" />
174
+ Reject
175
+ </Button>
176
+ </div>
177
+ ) : (
178
+ <div className="px-4 pb-3 flex items-center justify-between text-xxs text-muted-foreground">
179
+ <span>
180
+ {saving ? 'Saving...' : isDirty ? 'Unsaved changes' : 'Saved'}
181
+ </span>
182
+ <Button size="sm" variant="ghost" className="h-6" onClick={() => save()} disabled={!isDirty || saving}>
183
+ Save
184
+ </Button>
185
+ </div>
186
+ )}
187
+ </DialogContent>
188
+ </Dialog>
189
+ );
190
+ }
@@ -0,0 +1,113 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import { Sparkles, Loader2 } from 'lucide-react';
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogDescription,
7
+ DialogHeader,
8
+ DialogTitle,
9
+ } from '@/components/ui/dialog';
10
+ import { Button } from '@/components/ui/button';
11
+
12
+ interface IdeaFormDialogProps {
13
+ open: boolean;
14
+ loading: boolean;
15
+ onSubmit: (title: string, description: string) => void;
16
+ onCancel: () => void;
17
+ onSurprise: () => void;
18
+ surpriseLoading: boolean;
19
+ }
20
+
21
+ export function IdeaFormDialog({ open, loading, onSubmit, onCancel, onSurprise, surpriseLoading }: IdeaFormDialogProps) {
22
+ const [title, setTitle] = useState('');
23
+ const [description, setDescription] = useState('');
24
+ const inputRef = useRef<HTMLInputElement>(null);
25
+
26
+ // Reset fields and focus on open
27
+ useEffect(() => {
28
+ if (open) {
29
+ setTitle('');
30
+ setDescription('');
31
+ setTimeout(() => inputRef.current?.focus(), 100);
32
+ }
33
+ }, [open]);
34
+
35
+ function handleSubmit() {
36
+ if (!title.trim()) return;
37
+ onSubmit(title.trim(), description.trim());
38
+ }
39
+
40
+ function handleKeyDown(e: React.KeyboardEvent) {
41
+ if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
42
+ e.preventDefault();
43
+ handleSubmit();
44
+ }
45
+ }
46
+
47
+ return (
48
+ <Dialog open={open} onOpenChange={(isOpen) => { if (!isOpen) onCancel(); }}>
49
+ <DialogContent className="max-w-lg p-5 gap-0" onKeyDown={handleKeyDown}>
50
+ <DialogHeader className="mb-4">
51
+ <DialogTitle className="text-sm font-normal">New Idea</DialogTitle>
52
+ <DialogDescription className="text-xxs text-muted-foreground">
53
+ Capture a feature idea for the icebox
54
+ </DialogDescription>
55
+ </DialogHeader>
56
+
57
+ <input
58
+ ref={inputRef}
59
+ className="mb-3 w-full rounded bg-muted/50 px-3 py-2 text-sm text-foreground border border-border focus:outline-none focus:ring-1 focus:ring-primary/50 placeholder:text-muted-foreground"
60
+ placeholder="Feature name..."
61
+ value={title}
62
+ onChange={(e) => setTitle(e.target.value)}
63
+ />
64
+
65
+ <textarea
66
+ className="mb-4 w-full rounded bg-muted/50 px-3 py-2.5 text-xs text-foreground border border-border focus:outline-none focus:ring-1 focus:ring-primary/50 resize-y placeholder:text-muted-foreground"
67
+ placeholder="Describe the idea... What problem does it solve? Any notes on approach?"
68
+ rows={6}
69
+ value={description}
70
+ onChange={(e) => setDescription(e.target.value)}
71
+ />
72
+
73
+ <div className="flex items-center gap-2">
74
+ <Button size="sm" onClick={handleSubmit} disabled={loading || !title.trim()} className="flex-1">
75
+ {loading ? 'Creating...' : 'Create'}
76
+ </Button>
77
+ <Button size="sm" variant="ghost" onClick={onCancel} disabled={loading}>
78
+ Cancel
79
+ </Button>
80
+ <span className="ml-auto text-[10px] text-muted-foreground/50">
81
+ {'\u2318'}+Enter
82
+ </span>
83
+ </div>
84
+
85
+ {/* Surprise Me */}
86
+ <div className="mt-4 pt-4 border-t border-border">
87
+ <Button
88
+ size="sm"
89
+ variant="outline"
90
+ className="w-full text-purple-400 border-purple-500/30 hover:bg-purple-500/10 hover:border-purple-500/50"
91
+ onClick={onSurprise}
92
+ disabled={surpriseLoading}
93
+ >
94
+ {surpriseLoading ? (
95
+ <>
96
+ <Loader2 className="h-3.5 w-3.5 mr-2 animate-spin" />
97
+ Generating ideas...
98
+ </>
99
+ ) : (
100
+ <>
101
+ <Sparkles className="h-3.5 w-3.5 mr-2" />
102
+ Surprise Me
103
+ </>
104
+ )}
105
+ </Button>
106
+ <p className="mt-1.5 text-center text-[10px] text-muted-foreground">
107
+ AI analyzes the codebase and suggests feature ideas
108
+ </p>
109
+ </div>
110
+ </DialogContent>
111
+ </Dialog>
112
+ );
113
+ }
@@ -0,0 +1,201 @@
1
+ import { useDroppable } from '@dnd-kit/core';
2
+ import type { Scope, ScopeStatus, Sprint, CardDisplayConfig } from '@/types';
3
+ import type { SortField, SortDirection } from '@/hooks/useBoardSettings';
4
+ import { ScopeCard } from './ScopeCard';
5
+ import { SprintContainer } from './SprintContainer';
6
+ import { ColumnMenu } from './ColumnMenu';
7
+ import { useTheme } from '@/hooks/useTheme';
8
+ import { cn } from '@/lib/utils';
9
+
10
+ interface KanbanColumnProps {
11
+ id: ScopeStatus;
12
+ label: string;
13
+ color: string;
14
+ scopes: Scope[];
15
+ /** Sprints to render in this column (assembling in Ready, active in Implementing) */
16
+ sprints?: Sprint[];
17
+ scopeLookup?: Map<number, Scope>;
18
+ /** Global set of scope IDs in active groups across ALL columns (cross-column dedup) */
19
+ globalSprintScopeIds?: Set<number>;
20
+ onScopeClick?: (scope: Scope) => void;
21
+ onDeleteSprint?: (id: number) => void;
22
+ onDispatchSprint?: (id: number) => void;
23
+ onRenameSprint?: (id: number, name: string) => void;
24
+ /** ID of a sprint that was just created and should start with name editing */
25
+ editingSprintId?: number | null;
26
+ onSprintEditingDone?: () => void;
27
+ isValidDrop?: boolean;
28
+ isDragActive?: boolean;
29
+ headerExtra?: React.ReactNode;
30
+ collapsed?: boolean;
31
+ onToggleCollapse?: () => void;
32
+ sortField?: SortField;
33
+ sortDirection?: SortDirection;
34
+ onSetSort?: (field: SortField) => void;
35
+ cardDisplay?: CardDisplayConfig;
36
+ dimmedIds?: Set<number>;
37
+ onAddAllToSprint?: (sprintId: number, scopeIds: number[]) => void;
38
+ }
39
+
40
+ export function KanbanColumn({
41
+ id,
42
+ label,
43
+ color,
44
+ scopes,
45
+ sprints = [],
46
+ scopeLookup = new Map(),
47
+ globalSprintScopeIds,
48
+ onScopeClick,
49
+ onDeleteSprint,
50
+ onDispatchSprint,
51
+ onRenameSprint,
52
+ editingSprintId,
53
+ onSprintEditingDone,
54
+ isValidDrop,
55
+ isDragActive,
56
+ headerExtra,
57
+ collapsed,
58
+ onToggleCollapse,
59
+ sortField,
60
+ sortDirection,
61
+ onSetSort,
62
+ cardDisplay,
63
+ dimmedIds,
64
+ onAddAllToSprint,
65
+ }: KanbanColumnProps) {
66
+ const { neonGlass } = useTheme();
67
+ const { setNodeRef, isOver } = useDroppable({ id });
68
+
69
+ // Scopes that are in a sprint/batch should not appear as loose cards.
70
+ // Use the global set (cross-column dedup) if available, otherwise fall back to local.
71
+ const sprintScopeIds = globalSprintScopeIds ?? new Set(sprints.flatMap((s) => s.scope_ids));
72
+ const looseScopes = scopes.filter((s) => !sprintScopeIds.has(s.id));
73
+ const looseScopeIds = looseScopes.filter((s) => !s.is_ghost).map((s) => s.id);
74
+ const totalCount = scopes.length;
75
+
76
+ const showCollapsed = collapsed;
77
+
78
+ return (
79
+ <div
80
+ ref={setNodeRef}
81
+ className={cn(
82
+ 'flex h-full flex-shrink-0 flex-col rounded border bg-card/50 overflow-hidden transition-[width] duration-300 ease-in-out',
83
+ showCollapsed ? 'w-10 cursor-pointer items-center' : 'w-72',
84
+ neonGlass && 'card-glass neon-border-blue',
85
+ isDragActive && isOver && isValidDrop && 'ring-2 ring-green-500/60 border-green-500/40 bg-green-500/5',
86
+ isDragActive && isOver && !isValidDrop && 'ring-2 ring-red-500/50 border-red-500/30 bg-red-500/5',
87
+ isDragActive && !isOver && isValidDrop && 'border-green-500/20',
88
+ )}
89
+ onClick={showCollapsed ? onToggleCollapse : undefined}
90
+ >
91
+ {showCollapsed ? (
92
+ <>
93
+ {/* Menu at top */}
94
+ <div className="flex items-center justify-center py-1.5" onClick={(e) => e.stopPropagation()}>
95
+ {sortField && sortDirection && onSetSort && onToggleCollapse && (
96
+ <ColumnMenu
97
+ sortField={sortField}
98
+ sortDirection={sortDirection}
99
+ onSetSort={onSetSort}
100
+ collapsed
101
+ onToggleCollapse={onToggleCollapse}
102
+ />
103
+ )}
104
+ </div>
105
+
106
+ {/* Rotated label */}
107
+ {/* Rotated label */}
108
+ <div className="flex items-start justify-center overflow-hidden pt-2">
109
+ <div className="flex items-center gap-2 [writing-mode:vertical-lr]">
110
+ <div className={cn('h-2.5 w-2.5 rounded-full shrink-0', neonGlass && 'animate-glow-pulse')} style={{ backgroundColor: `hsl(${color})` }} />
111
+ <span className="text-xxs uppercase tracking-wider font-normal text-muted-foreground whitespace-nowrap">
112
+ {label}
113
+ </span>
114
+ </div>
115
+ </div>
116
+
117
+ {/* Count badge — upright, below label */}
118
+ <span className="mt-2 rounded-full bg-muted px-1.5 py-0.5 text-[10px] font-normal text-muted-foreground">
119
+ {totalCount}
120
+ </span>
121
+ </>
122
+ ) : (
123
+ <>
124
+ {/* Column header — click to collapse */}
125
+ <div className="flex items-center gap-2 border-b px-2.5 py-1.5 cursor-pointer" onClick={onToggleCollapse}>
126
+ <div className={cn('h-2.5 w-2.5 rounded-full', neonGlass && 'animate-glow-pulse')} style={{ backgroundColor: `hsl(${color})` }} />
127
+ <h2 className="text-xxs uppercase tracking-wider font-normal text-muted-foreground">{label}</h2>
128
+ <span className="ml-auto rounded-full bg-muted px-2 py-0.5 text-xs font-normal text-muted-foreground">
129
+ {totalCount}
130
+ </span>
131
+ {headerExtra && <span onClick={(e) => e.stopPropagation()}>{headerExtra}</span>}
132
+ {sortField && sortDirection && onSetSort && onToggleCollapse && (
133
+ <span onClick={(e) => e.stopPropagation()}>
134
+ <ColumnMenu
135
+ sortField={sortField}
136
+ sortDirection={sortDirection}
137
+ onSetSort={onSetSort}
138
+ collapsed={false}
139
+ onToggleCollapse={onToggleCollapse}
140
+ />
141
+ </span>
142
+ )}
143
+ </div>
144
+
145
+ {/* Cards */}
146
+ <div className="min-h-0 flex-1 overflow-y-auto p-2">
147
+ <div className="space-y-1.5">
148
+ {sprints.map((sprint) => (
149
+ <SprintContainer
150
+ key={`sprint-${sprint.id}`}
151
+ sprint={sprint}
152
+ scopeLookup={scopeLookup}
153
+ onDelete={onDeleteSprint}
154
+ onDispatch={onDispatchSprint}
155
+ onRename={onRenameSprint}
156
+ onScopeClick={onScopeClick}
157
+ cardDisplay={cardDisplay}
158
+ dimmedIds={dimmedIds}
159
+ editingName={sprint.id === editingSprintId}
160
+ onEditingDone={onSprintEditingDone}
161
+ looseCount={sprint.status === 'assembling' ? looseScopeIds.length : 0}
162
+ onAddAll={sprint.status === 'assembling' && onAddAllToSprint
163
+ ? (sprintId) => onAddAllToSprint(sprintId, looseScopeIds)
164
+ : undefined
165
+ }
166
+ />
167
+ ))}
168
+
169
+ {looseScopes.filter((s) => !s.is_ghost).map((scope) => (
170
+ <ScopeCard
171
+ key={scope.id}
172
+ scope={scope}
173
+ onClick={onScopeClick}
174
+ cardDisplay={cardDisplay}
175
+ dimmed={dimmedIds?.has(scope.id)}
176
+ />
177
+ ))}
178
+ {looseScopes.some((s) => s.is_ghost) && looseScopes.some((s) => !s.is_ghost) && (
179
+ <div className="my-2 border-t border-dashed border-purple-500/20" />
180
+ )}
181
+ {looseScopes.filter((s) => s.is_ghost).map((scope) => (
182
+ <ScopeCard
183
+ key={scope.id}
184
+ scope={scope}
185
+ onClick={onScopeClick}
186
+ cardDisplay={cardDisplay}
187
+ dimmed={dimmedIds?.has(scope.id)}
188
+ />
189
+ ))}
190
+ {totalCount === 0 && isDragActive && isOver && isValidDrop && (
191
+ <p className="py-8 text-center text-xs text-muted-foreground/50">
192
+ Drop here
193
+ </p>
194
+ )}
195
+ </div>
196
+ </div>
197
+ </>
198
+ )}
199
+ </div>
200
+ );
201
+ }
@@ -0,0 +1,114 @@
1
+ import ReactMarkdown from 'react-markdown';
2
+ import remarkGfm from 'remark-gfm';
3
+ import { cn } from '@/lib/utils';
4
+
5
+ interface MarkdownRendererProps {
6
+ content: string;
7
+ className?: string;
8
+ }
9
+
10
+ export function MarkdownRenderer({ content, className }: MarkdownRendererProps) {
11
+ return (
12
+ <div className={cn(
13
+ 'space-y-4 text-[13px] leading-[1.7]',
14
+ className,
15
+ )} style={{ fontFamily: "'Inter', system-ui, sans-serif" }}>
16
+ <ReactMarkdown
17
+ remarkPlugins={[remarkGfm]}
18
+ components={{
19
+ h1: ({ children }) => (
20
+ <h1 className="text-base font-semibold text-foreground border-b border-border pb-2 mb-3">{children}</h1>
21
+ ),
22
+ h2: ({ children }) => (
23
+ <h2 className="text-sm font-semibold text-foreground mt-6 mb-2 border-b border-border/50 pb-1.5">{children}</h2>
24
+ ),
25
+ h3: ({ children }) => (
26
+ <h3 className="text-[13px] font-semibold text-foreground mt-5 mb-1.5">{children}</h3>
27
+ ),
28
+ h4: ({ children }) => (
29
+ <h4 className="text-[13px] font-medium text-foreground/90 mt-4 mb-1">{children}</h4>
30
+ ),
31
+ p: ({ children }) => (
32
+ <p className="text-foreground/70 mb-2">{children}</p>
33
+ ),
34
+ a: ({ href, children }) => (
35
+ <a href={href} className="text-accent-blue hover:text-accent-blue/80 underline underline-offset-2" target="_blank" rel="noopener noreferrer">
36
+ {children}
37
+ </a>
38
+ ),
39
+ ul: ({ children }) => (
40
+ <ul className="ml-5 space-y-1.5 list-disc text-foreground/70 marker:text-muted-foreground">{children}</ul>
41
+ ),
42
+ ol: ({ children }) => (
43
+ <ol className="ml-5 space-y-1.5 list-decimal text-foreground/70 marker:text-muted-foreground">{children}</ol>
44
+ ),
45
+ li: ({ children, ...props }) => {
46
+ const checkbox = props.node?.properties?.className;
47
+ if (checkbox && String(checkbox).includes('task-list-item')) {
48
+ return <li className="list-none ml-[-1.25rem] flex items-start gap-2">{children}</li>;
49
+ }
50
+ return <li className="pl-1">{children}</li>;
51
+ },
52
+ input: ({ checked }) => (
53
+ <input
54
+ type="checkbox"
55
+ checked={checked}
56
+ readOnly
57
+ className="mt-1 h-3.5 w-3.5 rounded border-border accent-primary"
58
+ />
59
+ ),
60
+ code: ({ children, className: codeClassName }) => {
61
+ const isBlock = codeClassName?.startsWith('language-');
62
+ if (isBlock) {
63
+ return (
64
+ <code className={cn(
65
+ 'block rounded p-3 font-mono text-xxs overflow-x-auto',
66
+ 'bg-[#0d0d14] border border-border/50',
67
+ codeClassName,
68
+ )}>
69
+ {children}
70
+ </code>
71
+ );
72
+ }
73
+ return (
74
+ <code className="bg-[#0d0d14] border border-border/30 rounded px-1.5 py-0.5 font-mono text-xxs text-accent-blue/90">
75
+ {children}
76
+ </code>
77
+ );
78
+ },
79
+ pre: ({ children }) => (
80
+ <pre className="overflow-x-auto my-3">{children}</pre>
81
+ ),
82
+ blockquote: ({ children }) => (
83
+ <blockquote className="border-l-2 border-accent-blue/40 pl-4 py-1 text-foreground/60 italic bg-surface/50 rounded-r">
84
+ {children}
85
+ </blockquote>
86
+ ),
87
+ table: ({ children }) => (
88
+ <div className="overflow-x-auto my-3 rounded border border-border">
89
+ <table className="w-full text-xs">{children}</table>
90
+ </div>
91
+ ),
92
+ thead: ({ children }) => (
93
+ <thead className="bg-[#0d0d14] text-muted-foreground">{children}</thead>
94
+ ),
95
+ tr: ({ children }) => (
96
+ <tr className="border-b border-border/50 even:bg-surface/30">{children}</tr>
97
+ ),
98
+ th: ({ children }) => (
99
+ <th className="px-3 py-2 text-left text-xxs font-medium uppercase tracking-wider">{children}</th>
100
+ ),
101
+ td: ({ children }) => (
102
+ <td className="px-3 py-2 text-foreground/70">{children}</td>
103
+ ),
104
+ hr: () => <hr className="border-border/50 my-4" />,
105
+ strong: ({ children }) => (
106
+ <strong className="font-semibold text-foreground">{children}</strong>
107
+ ),
108
+ }}
109
+ >
110
+ {content}
111
+ </ReactMarkdown>
112
+ </div>
113
+ );
114
+ }