@agile-vibe-coding/avc 0.1.1 β†’ 0.3.1

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 (239) hide show
  1. package/cli/agent-loader.js +21 -0
  2. package/cli/agents/agent-selector.md +152 -0
  3. package/cli/agents/architecture-recommender.md +418 -0
  4. package/cli/agents/code-implementer.md +117 -0
  5. package/cli/agents/code-validator.md +80 -0
  6. package/cli/agents/context-reviewer-epic.md +101 -0
  7. package/cli/agents/context-reviewer-story.md +92 -0
  8. package/cli/agents/context-writer-epic.md +145 -0
  9. package/cli/agents/context-writer-story.md +111 -0
  10. package/cli/agents/database-deep-dive.md +470 -0
  11. package/cli/agents/database-recommender.md +634 -0
  12. package/cli/agents/doc-distributor.md +176 -0
  13. package/cli/agents/doc-writer-epic.md +42 -0
  14. package/cli/agents/doc-writer-story.md +43 -0
  15. package/cli/agents/documentation-updater.md +203 -0
  16. package/cli/agents/duplicate-detector.md +110 -0
  17. package/cli/agents/epic-story-decomposer.md +559 -0
  18. package/cli/agents/feature-context-generator.md +91 -0
  19. package/cli/agents/gap-checker-epic.md +52 -0
  20. package/cli/agents/impact-checker-story.md +51 -0
  21. package/cli/agents/migration-guide-generator.md +305 -0
  22. package/cli/agents/mission-scope-generator.md +143 -0
  23. package/cli/agents/mission-scope-validator.md +146 -0
  24. package/cli/agents/project-context-extractor.md +122 -0
  25. package/cli/agents/project-documentation-creator.json +226 -0
  26. package/cli/agents/project-documentation-creator.md +595 -0
  27. package/cli/agents/question-prefiller.md +269 -0
  28. package/cli/agents/refiner-epic.md +39 -0
  29. package/cli/agents/refiner-story.md +42 -0
  30. package/cli/agents/scaffolding-generator.md +99 -0
  31. package/cli/agents/seed-validator.md +71 -0
  32. package/cli/agents/story-doc-enricher.md +133 -0
  33. package/cli/agents/story-scope-reviewer.md +147 -0
  34. package/cli/agents/story-splitter.md +83 -0
  35. package/cli/agents/suggestion-business-analyst.md +88 -0
  36. package/cli/agents/suggestion-deployment-architect.md +263 -0
  37. package/cli/agents/suggestion-product-manager.md +129 -0
  38. package/cli/agents/suggestion-security-specialist.md +156 -0
  39. package/cli/agents/suggestion-technical-architect.md +269 -0
  40. package/cli/agents/suggestion-ux-researcher.md +93 -0
  41. package/cli/agents/task-subtask-decomposer.md +188 -0
  42. package/cli/agents/validator-documentation.json +183 -0
  43. package/cli/agents/validator-documentation.md +455 -0
  44. package/cli/agents/validator-selector.md +211 -0
  45. package/cli/ansi-colors.js +21 -0
  46. package/cli/api-reference-tool.js +368 -0
  47. package/cli/build-docs.js +29 -8
  48. package/cli/ceremony-history.js +369 -0
  49. package/cli/checks/catalog.json +76 -0
  50. package/cli/checks/code/quality.json +26 -0
  51. package/cli/checks/code/testing.json +14 -0
  52. package/cli/checks/code/traceability.json +26 -0
  53. package/cli/checks/cross-refs/epic.json +171 -0
  54. package/cli/checks/cross-refs/story.json +149 -0
  55. package/cli/checks/epic/api.json +114 -0
  56. package/cli/checks/epic/backend.json +126 -0
  57. package/cli/checks/epic/cloud.json +126 -0
  58. package/cli/checks/epic/data.json +102 -0
  59. package/cli/checks/epic/database.json +114 -0
  60. package/cli/checks/epic/developer.json +182 -0
  61. package/cli/checks/epic/devops.json +174 -0
  62. package/cli/checks/epic/frontend.json +162 -0
  63. package/cli/checks/epic/mobile.json +102 -0
  64. package/cli/checks/epic/qa.json +90 -0
  65. package/cli/checks/epic/security.json +184 -0
  66. package/cli/checks/epic/solution-architect.json +192 -0
  67. package/cli/checks/epic/test-architect.json +90 -0
  68. package/cli/checks/epic/ui.json +102 -0
  69. package/cli/checks/epic/ux.json +90 -0
  70. package/cli/checks/fixes/epic-fix-template.md +10 -0
  71. package/cli/checks/fixes/story-fix-template.md +10 -0
  72. package/cli/checks/story/api.json +186 -0
  73. package/cli/checks/story/backend.json +102 -0
  74. package/cli/checks/story/cloud.json +102 -0
  75. package/cli/checks/story/data.json +210 -0
  76. package/cli/checks/story/database.json +102 -0
  77. package/cli/checks/story/developer.json +168 -0
  78. package/cli/checks/story/devops.json +102 -0
  79. package/cli/checks/story/frontend.json +174 -0
  80. package/cli/checks/story/mobile.json +102 -0
  81. package/cli/checks/story/qa.json +210 -0
  82. package/cli/checks/story/security.json +198 -0
  83. package/cli/checks/story/solution-architect.json +230 -0
  84. package/cli/checks/story/test-architect.json +210 -0
  85. package/cli/checks/story/ui.json +102 -0
  86. package/cli/checks/story/ux.json +102 -0
  87. package/cli/coding-order.js +401 -0
  88. package/cli/command-logger.js +49 -12
  89. package/cli/components/static-output.js +63 -0
  90. package/cli/console-output-manager.js +94 -0
  91. package/cli/dependency-checker.js +72 -0
  92. package/cli/docs-sync.js +306 -0
  93. package/cli/epic-story-validator.js +659 -0
  94. package/cli/evaluation-prompts.js +1008 -0
  95. package/cli/execution-context.js +195 -0
  96. package/cli/generate-summary-table.js +340 -0
  97. package/cli/init-model-config.js +704 -0
  98. package/cli/init.js +1737 -278
  99. package/cli/kanban-server-manager.js +227 -0
  100. package/cli/llm-claude.js +150 -1
  101. package/cli/llm-gemini.js +109 -0
  102. package/cli/llm-local.js +493 -0
  103. package/cli/llm-mock.js +233 -0
  104. package/cli/llm-openai.js +454 -0
  105. package/cli/llm-provider.js +379 -3
  106. package/cli/llm-token-limits.js +211 -0
  107. package/cli/llm-verifier.js +662 -0
  108. package/cli/llm-xiaomi.js +143 -0
  109. package/cli/message-constants.js +49 -0
  110. package/cli/message-manager.js +334 -0
  111. package/cli/message-types.js +96 -0
  112. package/cli/messaging-api.js +291 -0
  113. package/cli/micro-check-fixer.js +335 -0
  114. package/cli/micro-check-runner.js +449 -0
  115. package/cli/micro-check-scorer.js +148 -0
  116. package/cli/micro-check-validator.js +538 -0
  117. package/cli/model-pricing.js +192 -0
  118. package/cli/model-query-engine.js +468 -0
  119. package/cli/model-recommendation-analyzer.js +495 -0
  120. package/cli/model-selector.js +270 -0
  121. package/cli/output-buffer.js +107 -0
  122. package/cli/process-manager.js +73 -2
  123. package/cli/prompt-logger.js +57 -0
  124. package/cli/repl-ink.js +4625 -1094
  125. package/cli/repl-old.js +3 -4
  126. package/cli/seed-processor.js +962 -0
  127. package/cli/sprint-planning-processor.js +4162 -0
  128. package/cli/template-processor.js +2149 -105
  129. package/cli/templates/project.md +25 -8
  130. package/cli/templates/vitepress-config.mts.template +5 -4
  131. package/cli/token-tracker.js +547 -0
  132. package/cli/tools/generate-story-validators.js +317 -0
  133. package/cli/tools/generate-validators.js +669 -0
  134. package/cli/update-checker.js +19 -17
  135. package/cli/update-notifier.js +4 -4
  136. package/cli/validation-router.js +667 -0
  137. package/cli/verification-tracker.js +563 -0
  138. package/cli/worktree-runner.js +654 -0
  139. package/kanban/README.md +386 -0
  140. package/kanban/client/README.md +205 -0
  141. package/kanban/client/components.json +20 -0
  142. package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
  143. package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
  144. package/kanban/client/dist/index.html +16 -0
  145. package/kanban/client/dist/vite.svg +1 -0
  146. package/kanban/client/index.html +15 -0
  147. package/kanban/client/package-lock.json +9442 -0
  148. package/kanban/client/package.json +44 -0
  149. package/kanban/client/postcss.config.js +6 -0
  150. package/kanban/client/public/vite.svg +1 -0
  151. package/kanban/client/src/App.jsx +651 -0
  152. package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
  153. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +420 -0
  154. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +629 -0
  155. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +1133 -0
  156. package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
  157. package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
  158. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +686 -0
  159. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +838 -0
  160. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
  161. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +136 -0
  162. package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
  163. package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
  164. package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
  165. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +329 -0
  166. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +249 -0
  167. package/kanban/client/src/components/kanban/CardDetailModal.jsx +646 -0
  168. package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
  169. package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
  170. package/kanban/client/src/components/kanban/GroupingSelector.jsx +63 -0
  171. package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
  172. package/kanban/client/src/components/kanban/KanbanCard.jsx +147 -0
  173. package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
  174. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +784 -0
  175. package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
  176. package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
  177. package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
  178. package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
  179. package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
  180. package/kanban/client/src/components/settings/AgentsTab.jsx +381 -0
  181. package/kanban/client/src/components/settings/ApiKeysTab.jsx +142 -0
  182. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +105 -0
  183. package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
  184. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +95 -0
  185. package/kanban/client/src/components/settings/ModelPricingTab.jsx +269 -0
  186. package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
  187. package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
  188. package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
  189. package/kanban/client/src/components/stats/CostModal.jsx +384 -0
  190. package/kanban/client/src/components/ui/badge.jsx +27 -0
  191. package/kanban/client/src/components/ui/dialog.jsx +121 -0
  192. package/kanban/client/src/components/ui/tabs.jsx +85 -0
  193. package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
  194. package/kanban/client/src/hooks/useGrouping.js +177 -0
  195. package/kanban/client/src/hooks/useWebSocket.js +120 -0
  196. package/kanban/client/src/lib/__tests__/api.test.js +196 -0
  197. package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
  198. package/kanban/client/src/lib/api.js +515 -0
  199. package/kanban/client/src/lib/status-grouping.js +154 -0
  200. package/kanban/client/src/lib/utils.js +11 -0
  201. package/kanban/client/src/main.jsx +10 -0
  202. package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
  203. package/kanban/client/src/store/ceremonyStore.js +172 -0
  204. package/kanban/client/src/store/filterStore.js +201 -0
  205. package/kanban/client/src/store/kanbanStore.js +123 -0
  206. package/kanban/client/src/store/processStore.js +65 -0
  207. package/kanban/client/src/store/sprintPlanningStore.js +33 -0
  208. package/kanban/client/src/styles/globals.css +59 -0
  209. package/kanban/client/tailwind.config.js +77 -0
  210. package/kanban/client/vite.config.js +28 -0
  211. package/kanban/client/vitest.config.js +28 -0
  212. package/kanban/dev-start.sh +47 -0
  213. package/kanban/package.json +12 -0
  214. package/kanban/server/index.js +537 -0
  215. package/kanban/server/routes/ceremony.js +454 -0
  216. package/kanban/server/routes/costs.js +163 -0
  217. package/kanban/server/routes/openai-oauth.js +366 -0
  218. package/kanban/server/routes/processes.js +50 -0
  219. package/kanban/server/routes/settings.js +736 -0
  220. package/kanban/server/routes/websocket.js +281 -0
  221. package/kanban/server/routes/work-items.js +487 -0
  222. package/kanban/server/services/CeremonyService.js +1441 -0
  223. package/kanban/server/services/FileSystemScanner.js +95 -0
  224. package/kanban/server/services/FileWatcher.js +144 -0
  225. package/kanban/server/services/HierarchyBuilder.js +196 -0
  226. package/kanban/server/services/ProcessRegistry.js +122 -0
  227. package/kanban/server/services/TaskRunnerService.js +261 -0
  228. package/kanban/server/services/WorkItemReader.js +123 -0
  229. package/kanban/server/services/WorkItemRefineService.js +510 -0
  230. package/kanban/server/start.js +49 -0
  231. package/kanban/server/utils/kanban-logger.js +132 -0
  232. package/kanban/server/utils/markdown.js +91 -0
  233. package/kanban/server/utils/status-grouping.js +107 -0
  234. package/kanban/server/workers/run-task-worker.js +121 -0
  235. package/kanban/server/workers/seed-worker.js +94 -0
  236. package/kanban/server/workers/sponsor-call-worker.js +92 -0
  237. package/kanban/server/workers/sprint-planning-worker.js +212 -0
  238. package/package.json +19 -7
  239. package/cli/agents/documentation.md +0 -302
@@ -0,0 +1,146 @@
1
+ import { motion } from 'framer-motion';
2
+ import { ChevronDown, ChevronRight } from 'lucide-react';
3
+ import { useState } from 'react';
4
+ import { KanbanColumn } from './KanbanColumn';
5
+ import { COLUMN_ORDER, STATUS_COLUMN_MAPPING } from '../../lib/status-grouping';
6
+ import { getStatusMetadata } from '../../lib/status-grouping';
7
+ import { cn } from '../../lib/utils';
8
+
9
+ /**
10
+ * Epic Section Component
11
+ * Displays an epic with its work items grouped by status columns
12
+ */
13
+ export function EpicSection({ group, columnVisibility, onCardClick }) {
14
+ const [isExpanded, setIsExpanded] = useState(true);
15
+
16
+ const { name, epic, items, columns } = group;
17
+ const isUngrouped = group.type === 'ungrouped';
18
+
19
+ // Calculate epic progress
20
+ const totalItems = items.length;
21
+ const completedItems = items.filter((item) => item.status === 'completed').length;
22
+ const progressPercentage = totalItems > 0 ? (completedItems / totalItems) * 100 : 0;
23
+
24
+ // Get epic status if available
25
+ const epicStatus = epic?.status;
26
+ const epicStatusMeta = epicStatus ? getStatusMetadata(epicStatus) : null;
27
+
28
+ return (
29
+ <div className="mb-8">
30
+ {/* Epic Header */}
31
+ <div
32
+ className={cn(
33
+ 'mb-4 pb-4 border-b-2',
34
+ isUngrouped ? 'border-slate-300' : 'border-indigo-300'
35
+ )}
36
+ >
37
+ <div className="flex items-start justify-between gap-4">
38
+ <div className="flex items-start gap-3 flex-1 min-w-0">
39
+ {/* Expand/Collapse Toggle */}
40
+ <button
41
+ onClick={() => setIsExpanded(!isExpanded)}
42
+ className="text-slate-600 hover:text-slate-900 transition-colors mt-3 flex-shrink-0"
43
+ >
44
+ {isExpanded ? (
45
+ <ChevronDown className="w-5 h-5" />
46
+ ) : (
47
+ <ChevronRight className="w-5 h-5" />
48
+ )}
49
+ </button>
50
+
51
+ {/* Epic Title */}
52
+ {epic && !isUngrouped ? (
53
+ <button
54
+ onClick={() => onCardClick?.(epic)}
55
+ className="flex-1 min-w-0 text-left p-3 border border-indigo-200 rounded-lg hover:border-indigo-400 hover:bg-indigo-50 transition-colors"
56
+ title="View epic details"
57
+ >
58
+ <div className="flex items-start justify-between gap-2">
59
+ <div className="flex-1 min-w-0">
60
+ <div className="flex items-center gap-2 flex-wrap mb-1">
61
+ <span className="text-lg font-bold text-indigo-900">πŸ›οΈ {name}</span>
62
+ {epicStatusMeta && (
63
+ <span
64
+ className={cn(
65
+ 'px-2 py-0.5 rounded-full text-xs font-medium',
66
+ epicStatusMeta.color === 'green' && 'bg-green-100 text-green-700',
67
+ epicStatusMeta.color === 'blue' && 'bg-blue-100 text-blue-700',
68
+ epicStatusMeta.color === 'yellow' && 'bg-yellow-100 text-yellow-700'
69
+ )}
70
+ >
71
+ {epicStatusMeta.icon} {epicStatusMeta.label}
72
+ </span>
73
+ )}
74
+ </div>
75
+ {epic?.description && (
76
+ <p className="text-sm text-slate-500 line-clamp-2">{epic.description}</p>
77
+ )}
78
+ </div>
79
+ <ChevronRight className="w-4 h-4 text-indigo-400 mt-1 flex-shrink-0" />
80
+ </div>
81
+ </button>
82
+ ) : (
83
+ <div className="flex-1 py-2">
84
+ <h2
85
+ className={cn(
86
+ 'text-xl font-bold',
87
+ isUngrouped ? 'text-slate-600' : 'text-indigo-900'
88
+ )}
89
+ >
90
+ {isUngrouped ? 'πŸ“‚' : 'πŸ›οΈ'} {name}
91
+ </h2>
92
+ </div>
93
+ )}
94
+ </div>
95
+
96
+ {/* Progress Stats */}
97
+ <div className="text-right flex-shrink-0">
98
+ <div className="text-sm text-slate-600 mb-1">
99
+ {completedItems} / {totalItems} completed
100
+ </div>
101
+ {/* Progress Bar */}
102
+ <div className="w-48 h-2 bg-slate-200 rounded-full overflow-hidden">
103
+ <motion.div
104
+ initial={{ width: 0 }}
105
+ animate={{ width: `${progressPercentage}%` }}
106
+ transition={{ duration: 0.5, ease: 'easeOut' }}
107
+ className={cn(
108
+ 'h-full',
109
+ isUngrouped ? 'bg-slate-500' : 'bg-indigo-600'
110
+ )}
111
+ />
112
+ </div>
113
+ </div>
114
+ </div>
115
+ </div>
116
+
117
+ {/* Columns */}
118
+ {isExpanded && (
119
+ <motion.div
120
+ initial={{ opacity: 0, height: 0 }}
121
+ animate={{ opacity: 1, height: 'auto' }}
122
+ exit={{ opacity: 0, height: 0 }}
123
+ transition={{ duration: 0.3 }}
124
+ className="flex gap-4 overflow-x-auto pb-4"
125
+ >
126
+ {COLUMN_ORDER.filter((column) => columnVisibility[column]).map(
127
+ (columnName) => {
128
+ const statuses = STATUS_COLUMN_MAPPING[columnName];
129
+ const columnItems = columns[columnName] || [];
130
+
131
+ return (
132
+ <KanbanColumn
133
+ key={`${group.id}-${columnName}`}
134
+ columnName={columnName}
135
+ statuses={statuses}
136
+ workItems={columnItems}
137
+ onCardClick={onCardClick}
138
+ />
139
+ );
140
+ }
141
+ )}
142
+ </motion.div>
143
+ )}
144
+ </div>
145
+ );
146
+ }
@@ -0,0 +1,222 @@
1
+ import { useState } from 'react';
2
+ import { Search, RefreshCw, X, Filter, Eye, EyeOff } from 'lucide-react';
3
+ import { useFilterStore } from '../../store/filterStore';
4
+ import { useKanbanStore } from '../../store/kanbanStore';
5
+ import { GroupingSelector } from './GroupingSelector';
6
+ import { cn } from '../../lib/utils';
7
+
8
+ /**
9
+ * Filter Toolbar Component
10
+ * Provides filtering controls for work items in a single compact row
11
+ */
12
+ export function FilterToolbar() {
13
+ const [searchInput, setSearchInput] = useState('');
14
+
15
+ // Zustand stores
16
+ const {
17
+ typeFilters,
18
+ columnVisibility,
19
+ searchQuery,
20
+ toggleTypeFilter,
21
+ setAllTypeFilters,
22
+ toggleColumnVisibility,
23
+ applyPreset,
24
+ setSearchQuery,
25
+ clearSearch,
26
+ resetFilters,
27
+ } = useFilterStore();
28
+
29
+ const { refresh, loading } = useKanbanStore();
30
+
31
+ // Type filter buttons
32
+ const typeOptions = [
33
+ { key: 'epic', label: 'Epics', icon: 'πŸ›οΈ' },
34
+ { key: 'story', label: 'Stories', icon: 'πŸ“–' },
35
+ { key: 'task', label: 'Tasks', icon: 'βš™οΈ' },
36
+ { key: 'subtask', label: 'Subtasks', icon: 'πŸ“' },
37
+ ];
38
+
39
+ // Column visibility options
40
+ const columnOptions = [
41
+ { key: 'Backlog', label: 'Backlog' },
42
+ { key: 'Ready', label: 'Ready' },
43
+ { key: 'In Progress', label: 'In Progress' },
44
+ { key: 'Review', label: 'Review' },
45
+ { key: 'Done', label: 'Done' },
46
+ ];
47
+
48
+ // Handle search input change (debounced)
49
+ const handleSearchChange = (e) => {
50
+ const value = e.target.value;
51
+ setSearchInput(value);
52
+
53
+ // Simple debounce
54
+ clearTimeout(window.searchDebounce);
55
+ window.searchDebounce = setTimeout(() => {
56
+ setSearchQuery(value);
57
+ }, 300);
58
+ };
59
+
60
+ // Handle search clear
61
+ const handleClearSearch = () => {
62
+ setSearchInput('');
63
+ clearSearch();
64
+ };
65
+
66
+ // Check if all type filters are active
67
+ const allTypesActive = Object.values(typeFilters).every((v) => v);
68
+ const anyTypesActive = Object.values(typeFilters).some((v) => v);
69
+
70
+ return (
71
+ <div className="bg-white border-b border-slate-200 shadow-sm">
72
+ <div className="max-w-full px-4 py-2">
73
+ <div className="flex items-center justify-between gap-3">
74
+
75
+ {/* Left: Type Filters + Group By (single row) */}
76
+ <div className="flex items-center gap-2 min-w-0">
77
+
78
+ {/* Filter icon doubles as toggle-all button */}
79
+ <button
80
+ onClick={() => setAllTypeFilters(!allTypesActive)}
81
+ className={cn(
82
+ '-ml-1.5 p-1.5 rounded-md transition-colors flex-shrink-0',
83
+ allTypesActive
84
+ ? 'text-blue-600 hover:bg-blue-50'
85
+ : anyTypesActive
86
+ ? 'text-slate-500 hover:bg-slate-100'
87
+ : 'text-slate-400 hover:bg-slate-100'
88
+ )}
89
+ title={allTypesActive ? 'Deselect all types' : 'Select all types'}
90
+ >
91
+ <Filter className="w-4 h-4" />
92
+ </button>
93
+
94
+ {/* Type filter buttons */}
95
+ <div className="flex items-center gap-1">
96
+ {typeOptions.map(({ key, label, icon }) => (
97
+ <button
98
+ key={key}
99
+ onClick={() => toggleTypeFilter(key)}
100
+ className={cn(
101
+ 'px-2.5 py-1 rounded-md text-xs font-medium transition-colors',
102
+ 'flex items-center gap-1',
103
+ typeFilters[key]
104
+ ? 'bg-blue-100 text-blue-700 hover:bg-blue-200'
105
+ : 'bg-slate-100 text-slate-400 hover:bg-slate-200'
106
+ )}
107
+ >
108
+ <span className="text-sm leading-none">{icon}</span>
109
+ <span>{label}</span>
110
+ </button>
111
+ ))}
112
+ </div>
113
+
114
+ {/* Divider */}
115
+ <div className="w-px h-5 bg-slate-200 flex-shrink-0" />
116
+
117
+ {/* Group By inline */}
118
+ <GroupingSelector />
119
+ </div>
120
+
121
+ {/* Right: Search + Actions */}
122
+ <div className="flex items-center gap-2 flex-shrink-0">
123
+
124
+ {/* Column visibility dropdown */}
125
+ <div className="relative group">
126
+ <button className="px-2.5 py-1 rounded-md text-xs font-medium bg-slate-100 text-slate-700 hover:bg-slate-200 transition-colors flex items-center gap-1.5">
127
+ <Eye className="w-3.5 h-3.5" />
128
+ Columns
129
+ </button>
130
+
131
+ {/* Dropdown menu */}
132
+ <div className="absolute right-0 top-full mt-2 bg-white border border-slate-200 rounded-lg shadow-lg py-2 min-w-[180px] opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all z-10">
133
+ <div className="px-3 py-2 text-xs font-semibold text-slate-500 border-b border-slate-100">
134
+ COLUMN VISIBILITY
135
+ </div>
136
+ {columnOptions.map(({ key, label }) => (
137
+ <button
138
+ key={key}
139
+ onClick={() => toggleColumnVisibility(key)}
140
+ className="w-full px-3 py-2 text-sm text-left hover:bg-slate-50 flex items-center justify-between"
141
+ >
142
+ <span>{label}</span>
143
+ {columnVisibility[key] ? (
144
+ <Eye className="w-4 h-4 text-blue-600" />
145
+ ) : (
146
+ <EyeOff className="w-4 h-4 text-slate-400" />
147
+ )}
148
+ </button>
149
+ ))}
150
+ <div className="border-t border-slate-100 mt-2 pt-2 px-3">
151
+ <button
152
+ onClick={() => applyPreset('all')}
153
+ className="text-xs text-blue-600 hover:text-blue-700 font-medium"
154
+ >
155
+ Show All
156
+ </button>
157
+ <span className="text-slate-300 mx-2">β€’</span>
158
+ <button
159
+ onClick={() => applyPreset('active')}
160
+ className="text-xs text-blue-600 hover:text-blue-700 font-medium"
161
+ >
162
+ Active Work
163
+ </button>
164
+ <span className="text-slate-300 mx-2">β€’</span>
165
+ <button
166
+ onClick={() => applyPreset('hide-completed')}
167
+ className="text-xs text-blue-600 hover:text-blue-700 font-medium"
168
+ >
169
+ Hide Done
170
+ </button>
171
+ </div>
172
+ </div>
173
+ </div>
174
+
175
+ {/* Search input */}
176
+ <div className="relative">
177
+ <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-slate-400" />
178
+ <input
179
+ type="text"
180
+ placeholder="Search..."
181
+ value={searchInput}
182
+ onChange={handleSearchChange}
183
+ className="pl-8 pr-7 py-1 w-44 border border-slate-200 rounded-md text-xs focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
184
+ />
185
+ {searchInput && (
186
+ <button
187
+ onClick={handleClearSearch}
188
+ className="absolute right-2 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600"
189
+ >
190
+ <X className="w-3.5 h-3.5" />
191
+ </button>
192
+ )}
193
+ </div>
194
+
195
+ {/* Refresh button */}
196
+ <button
197
+ onClick={refresh}
198
+ disabled={loading}
199
+ className={cn(
200
+ 'p-1.5 rounded-md transition-colors',
201
+ loading
202
+ ? 'bg-slate-100 text-slate-400 cursor-not-allowed'
203
+ : 'bg-slate-100 text-slate-700 hover:bg-slate-200'
204
+ )}
205
+ title="Refresh"
206
+ >
207
+ <RefreshCw className={cn('w-3.5 h-3.5', loading && 'animate-spin')} />
208
+ </button>
209
+
210
+ {/* Reset filters button */}
211
+ <button
212
+ onClick={resetFilters}
213
+ className="px-2.5 py-1 rounded-md text-xs font-medium bg-slate-100 text-slate-700 hover:bg-slate-200 transition-colors"
214
+ >
215
+ Reset
216
+ </button>
217
+ </div>
218
+ </div>
219
+ </div>
220
+ </div>
221
+ );
222
+ }
@@ -0,0 +1,63 @@
1
+ import { LayoutGrid, Package, Box, ListOrdered } from 'lucide-react';
2
+ import { useFilterStore } from '../../store/filterStore';
3
+ import { cn } from '../../lib/utils';
4
+
5
+ /**
6
+ * Grouping Selector Component
7
+ * Allows users to change how work items are grouped
8
+ */
9
+ export function GroupingSelector() {
10
+ const { groupBy, setGroupBy } = useFilterStore();
11
+
12
+ const groupingOptions = [
13
+ {
14
+ value: 'status',
15
+ label: 'Status',
16
+ icon: LayoutGrid,
17
+ description: 'Traditional kanban columns',
18
+ },
19
+ {
20
+ value: 'epic',
21
+ label: 'Epic',
22
+ icon: Package,
23
+ description: 'Hierarchical epic sections',
24
+ },
25
+ {
26
+ value: 'type',
27
+ label: 'Type',
28
+ icon: Box,
29
+ description: 'Separate boards by type',
30
+ },
31
+ {
32
+ value: 'phase',
33
+ label: 'Phase',
34
+ icon: ListOrdered,
35
+ description: 'Implementation order by dependencies',
36
+ },
37
+ ];
38
+
39
+ return (
40
+ <div className="flex items-center gap-0.5 bg-slate-100 rounded-md p-0.5">
41
+ {groupingOptions.map(({ value, label, icon: Icon, description }) => {
42
+ const isActive = groupBy === value;
43
+
44
+ return (
45
+ <button
46
+ key={value}
47
+ onClick={() => setGroupBy(value)}
48
+ title={description}
49
+ className={cn(
50
+ 'flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs font-medium transition-all',
51
+ isActive
52
+ ? 'bg-white text-slate-900 shadow-sm'
53
+ : 'text-slate-600 hover:bg-slate-200'
54
+ )}
55
+ >
56
+ <Icon className="w-3.5 h-3.5" />
57
+ <span>{label}</span>
58
+ </button>
59
+ );
60
+ })}
61
+ </div>
62
+ );
63
+ }
@@ -0,0 +1,211 @@
1
+ import { useState, useEffect, useMemo } from 'react';
2
+ import { AnimatePresence, motion } from 'framer-motion';
3
+ import { KanbanColumn } from './KanbanColumn';
4
+ import { EpicSection } from './EpicSection';
5
+ import { useKanbanStore } from '../../store/kanbanStore';
6
+ import { useFilterStore } from '../../store/filterStore';
7
+ import { useGrouping } from '../../hooks/useGrouping';
8
+ import {
9
+ groupItemsByColumn,
10
+ COLUMN_ORDER,
11
+ STATUS_COLUMN_MAPPING,
12
+ } from '../../lib/status-grouping';
13
+
14
+ /**
15
+ * Kanban Board Component
16
+ * Main board container with columns and filtering
17
+ */
18
+ export function KanbanBoard({ onCardClick, onStartProject, projectFilesReady, onEditProjectDoc, onStartSprintPlanning, onOpenSprintPlanningSelection, sponsorCallRunning }) {
19
+ const [selectedItem, setSelectedItem] = useState(null);
20
+
21
+ // Zustand stores
22
+ const { workItems, loading } = useKanbanStore();
23
+ const { typeFilters, columnVisibility, searchQuery, groupBy } = useFilterStore();
24
+
25
+ // Filter work items
26
+ const filteredItems = useMemo(() => {
27
+ // Apply type filters
28
+ let filtered = workItems.filter((item) => typeFilters[item.type]);
29
+
30
+ // Apply search filter
31
+ if (searchQuery.trim()) {
32
+ const query = searchQuery.toLowerCase();
33
+ filtered = filtered.filter(
34
+ (item) =>
35
+ item.name.toLowerCase().includes(query) ||
36
+ item.id.toLowerCase().includes(query) ||
37
+ (item.description && item.description.toLowerCase().includes(query)) ||
38
+ (item.epicName && item.epicName.toLowerCase().includes(query))
39
+ );
40
+ }
41
+
42
+ return filtered;
43
+ }, [workItems, typeFilters, searchQuery]);
44
+
45
+ // Group work items based on grouping mode
46
+ const groupedData = useGrouping(filteredItems, groupBy);
47
+
48
+ // Handle card click
49
+ const handleCardClick = (item) => {
50
+ setSelectedItem(item);
51
+ onCardClick?.(item);
52
+ };
53
+
54
+ if (loading && workItems.length === 0) {
55
+ return (
56
+ <div className="flex items-center justify-center py-12">
57
+ <div className="text-center">
58
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
59
+ <p className="text-slate-600">Loading work items...</p>
60
+ </div>
61
+ </div>
62
+ );
63
+ }
64
+
65
+ if (filteredItems.length === 0 && workItems.length > 0) {
66
+ return (
67
+ <div className="flex items-center justify-center py-12">
68
+ <div className="text-center">
69
+ <div className="text-6xl mb-4">πŸ”</div>
70
+ <h3 className="text-xl font-semibold text-slate-900 mb-2">
71
+ No items match your filters
72
+ </h3>
73
+ <p className="text-slate-600">
74
+ Try adjusting your filters or search query
75
+ </p>
76
+ </div>
77
+ </div>
78
+ );
79
+ }
80
+
81
+ if (workItems.length === 0) {
82
+ return (
83
+ <div className="flex items-center justify-center py-12">
84
+ <div className="text-center">
85
+ {onStartProject ? (
86
+ // Case A: project files missing, ceremony not running
87
+ <>
88
+ <div className="text-6xl mb-4">πŸ“‹</div>
89
+ <h3 className="text-xl font-semibold text-slate-900 mb-2">No Work Items</h3>
90
+ <p className="text-sm text-slate-500 mb-4">Run the Sponsor Call ceremony to set up your project.</p>
91
+ <button
92
+ onClick={onStartProject}
93
+ className="px-5 py-2.5 text-sm font-medium bg-slate-900 text-white rounded-lg hover:bg-slate-700 transition-colors"
94
+ >
95
+ πŸš€ Start Project
96
+ </button>
97
+ </>
98
+ ) : projectFilesReady ? (
99
+ // Case B: files exist, ready for sprint planning (or sprint planning is running)
100
+ <>
101
+ <div className="text-5xl mb-4">πŸƒ</div>
102
+ <h3 className="text-lg font-semibold text-slate-900 mb-3">Ready for Sprint Planning</h3>
103
+ <p className="text-sm text-slate-500 mb-2 max-w-sm">
104
+ Your project is set up and ready for sprint planning.
105
+ </p>
106
+ <p className="text-sm text-slate-700 font-medium mb-5 max-w-sm">
107
+ Review <code className="bg-slate-100 px-1 rounded text-xs">doc.md</code> before starting β€” this file is the foundation of every Epic and Story that will be planned.
108
+ </p>
109
+ <div className="flex items-center justify-center gap-3 mb-6">
110
+ <button
111
+ onClick={onEditProjectDoc}
112
+ className="flex items-center gap-2 px-4 py-2 text-sm font-medium border border-slate-200 rounded-lg hover:bg-slate-50 transition-colors text-slate-700"
113
+ >
114
+ πŸ“„ Documentation
115
+ </button>
116
+ </div>
117
+ {onStartSprintPlanning ? (
118
+ <button
119
+ onClick={onStartSprintPlanning}
120
+ className="px-5 py-2.5 text-sm font-medium bg-slate-900 text-white rounded-lg hover:bg-slate-700 transition-colors"
121
+ >
122
+ πŸš€ Start Sprint Planning
123
+ </button>
124
+ ) : onOpenSprintPlanningSelection ? (
125
+ <button
126
+ onClick={onOpenSprintPlanningSelection}
127
+ className="flex items-center gap-2 px-5 py-2.5 text-sm font-medium bg-amber-500 text-white rounded-lg hover:bg-amber-600 transition-colors animate-pulse"
128
+ >
129
+ <span>⚠</span>
130
+ Sprint planning needs your input
131
+ </button>
132
+ ) : (
133
+ <p className="text-xs text-slate-400">Sprint planning is running…</p>
134
+ )}
135
+ </>
136
+ ) : sponsorCallRunning ? (
137
+ // Case C: no project files yet, sponsor call ceremony is running
138
+ <>
139
+ <div className="text-5xl mb-4">πŸ“‹</div>
140
+ <h3 className="text-lg font-semibold text-slate-900 mb-3">Sponsor Call is running…</h3>
141
+ <p className="text-sm text-slate-500 max-w-sm">
142
+ The Sponsor Call ceremony is generating your project definition. Work items will appear here once it completes.
143
+ </p>
144
+ </>
145
+ ) : null}
146
+ </div>
147
+ </div>
148
+ );
149
+ }
150
+
151
+ // Render based on grouping mode
152
+ if (groupedData.mode === 'sections') {
153
+ // Epic or Type grouping - render sections
154
+ return (
155
+ <motion.div
156
+ key={groupBy}
157
+ initial={{ opacity: 0, x: -20 }}
158
+ animate={{ opacity: 1, x: 0 }}
159
+ exit={{ opacity: 0, x: 20 }}
160
+ transition={{ duration: 0.3 }}
161
+ >
162
+ {groupedData.groups.length === 0 ? (
163
+ <div className="text-center py-12">
164
+ <p className="text-slate-600">No groups to display</p>
165
+ </div>
166
+ ) : (
167
+ groupedData.groups.map((group, index) => (
168
+ <motion.div
169
+ key={group.id}
170
+ initial={{ opacity: 0, y: 20 }}
171
+ animate={{ opacity: 1, y: 0 }}
172
+ transition={{ duration: 0.3, delay: index * 0.1 }}
173
+ >
174
+ <EpicSection
175
+ group={group}
176
+ columnVisibility={columnVisibility}
177
+ onCardClick={handleCardClick}
178
+ />
179
+ </motion.div>
180
+ ))
181
+ )}
182
+ </motion.div>
183
+ );
184
+ }
185
+
186
+ // Default: Status grouping - render columns
187
+ return (
188
+ <motion.div
189
+ key={groupBy}
190
+ initial={{ opacity: 0, x: -20 }}
191
+ animate={{ opacity: 1, x: 0 }}
192
+ exit={{ opacity: 0, x: 20 }}
193
+ transition={{ duration: 0.3 }}
194
+ className="flex gap-4 overflow-x-auto pb-4"
195
+ >
196
+ <AnimatePresence mode="sync">
197
+ {groupedData.groups
198
+ .filter((group) => columnVisibility[group.name])
199
+ .map((group) => (
200
+ <KanbanColumn
201
+ key={group.id}
202
+ columnName={group.name}
203
+ statuses={STATUS_COLUMN_MAPPING[group.name]}
204
+ workItems={group.items}
205
+ onCardClick={handleCardClick}
206
+ />
207
+ ))}
208
+ </AnimatePresence>
209
+ </motion.div>
210
+ );
211
+ }