@agile-vibe-coding/avc 0.2.3 → 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.
- package/cli/agents/agent-selector.md +23 -0
- package/cli/agents/code-implementer.md +117 -0
- package/cli/agents/code-validator.md +80 -0
- package/cli/agents/context-reviewer-epic.md +101 -0
- package/cli/agents/context-reviewer-story.md +92 -0
- package/cli/agents/context-writer-epic.md +145 -0
- package/cli/agents/context-writer-story.md +111 -0
- package/cli/agents/doc-writer-epic.md +42 -0
- package/cli/agents/doc-writer-story.md +43 -0
- package/cli/agents/duplicate-detector.md +110 -0
- package/cli/agents/epic-story-decomposer.md +318 -39
- package/cli/agents/mission-scope-generator.md +68 -4
- package/cli/agents/mission-scope-validator.md +40 -6
- package/cli/agents/project-context-extractor.md +21 -6
- package/cli/agents/scaffolding-generator.md +99 -0
- package/cli/agents/seed-validator.md +71 -0
- package/cli/agents/story-scope-reviewer.md +147 -0
- package/cli/agents/story-splitter.md +83 -0
- package/cli/agents/validator-documentation.json +31 -0
- package/cli/agents/validator-documentation.md +3 -1
- package/cli/api-reference-tool.js +368 -0
- package/cli/checks/catalog.json +76 -0
- package/cli/checks/code/quality.json +26 -0
- package/cli/checks/code/testing.json +14 -0
- package/cli/checks/code/traceability.json +26 -0
- package/cli/checks/cross-refs/epic.json +171 -0
- package/cli/checks/cross-refs/story.json +149 -0
- package/cli/checks/epic/api.json +114 -0
- package/cli/checks/epic/backend.json +126 -0
- package/cli/checks/epic/cloud.json +126 -0
- package/cli/checks/epic/data.json +102 -0
- package/cli/checks/epic/database.json +114 -0
- package/cli/checks/epic/developer.json +182 -0
- package/cli/checks/epic/devops.json +174 -0
- package/cli/checks/epic/frontend.json +162 -0
- package/cli/checks/epic/mobile.json +102 -0
- package/cli/checks/epic/qa.json +90 -0
- package/cli/checks/epic/security.json +184 -0
- package/cli/checks/epic/solution-architect.json +192 -0
- package/cli/checks/epic/test-architect.json +90 -0
- package/cli/checks/epic/ui.json +102 -0
- package/cli/checks/epic/ux.json +90 -0
- package/cli/checks/fixes/epic-fix-template.md +10 -0
- package/cli/checks/fixes/story-fix-template.md +10 -0
- package/cli/checks/story/api.json +186 -0
- package/cli/checks/story/backend.json +102 -0
- package/cli/checks/story/cloud.json +102 -0
- package/cli/checks/story/data.json +210 -0
- package/cli/checks/story/database.json +102 -0
- package/cli/checks/story/developer.json +168 -0
- package/cli/checks/story/devops.json +102 -0
- package/cli/checks/story/frontend.json +174 -0
- package/cli/checks/story/mobile.json +102 -0
- package/cli/checks/story/qa.json +210 -0
- package/cli/checks/story/security.json +198 -0
- package/cli/checks/story/solution-architect.json +230 -0
- package/cli/checks/story/test-architect.json +210 -0
- package/cli/checks/story/ui.json +102 -0
- package/cli/checks/story/ux.json +102 -0
- package/cli/coding-order.js +401 -0
- package/cli/dependency-checker.js +72 -0
- package/cli/epic-story-validator.js +284 -799
- package/cli/index.js +0 -0
- package/cli/init-model-config.js +17 -10
- package/cli/init.js +514 -92
- package/cli/kanban-server-manager.js +1 -2
- package/cli/llm-claude.js +98 -31
- package/cli/llm-gemini.js +29 -5
- package/cli/llm-local.js +493 -0
- package/cli/llm-openai.js +262 -41
- package/cli/llm-provider.js +147 -8
- package/cli/llm-token-limits.js +113 -4
- package/cli/llm-verifier.js +209 -1
- package/cli/llm-xiaomi.js +143 -0
- package/cli/message-constants.js +3 -12
- package/cli/messaging-api.js +6 -12
- package/cli/micro-check-fixer.js +335 -0
- package/cli/micro-check-runner.js +449 -0
- package/cli/micro-check-scorer.js +148 -0
- package/cli/micro-check-validator.js +538 -0
- package/cli/model-pricing.js +23 -0
- package/cli/model-selector.js +3 -2
- package/cli/prompt-logger.js +57 -0
- package/cli/repl-ink.js +106 -346
- package/cli/repl-old.js +1 -2
- package/cli/seed-processor.js +194 -24
- package/cli/sprint-planning-processor.js +2638 -289
- package/cli/template-processor.js +50 -3
- package/cli/token-tracker.js +50 -23
- package/cli/tools/generate-story-validators.js +1 -1
- package/cli/validation-router.js +70 -8
- package/cli/worktree-runner.js +654 -0
- package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
- package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
- package/kanban/client/dist/index.html +2 -2
- package/kanban/client/src/App.jsx +43 -14
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +7 -3
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +23 -10
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +320 -133
- package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +80 -13
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +156 -22
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +11 -11
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +3 -21
- package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +214 -10
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +23 -2
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +97 -10
- package/kanban/client/src/components/kanban/GroupingSelector.jsx +7 -1
- package/kanban/client/src/components/kanban/KanbanCard.jsx +23 -14
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +9 -14
- package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
- package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
- package/kanban/client/src/components/settings/AgentsTab.jsx +103 -75
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +31 -2
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +9 -2
- package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +3 -2
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +72 -7
- package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
- package/kanban/client/src/components/settings/SettingsModal.jsx +4 -4
- package/kanban/client/src/components/stats/CostModal.jsx +34 -3
- package/kanban/client/src/hooks/useGrouping.js +59 -0
- package/kanban/client/src/lib/api.js +118 -4
- package/kanban/client/src/lib/status-grouping.js +10 -0
- package/kanban/client/src/store/kanbanStore.js +8 -0
- package/kanban/server/index.js +23 -2
- package/kanban/server/routes/ceremony.js +153 -4
- package/kanban/server/routes/costs.js +9 -3
- package/kanban/server/routes/openai-oauth.js +366 -0
- package/kanban/server/routes/settings.js +447 -14
- package/kanban/server/routes/websocket.js +7 -2
- package/kanban/server/routes/work-items.js +141 -1
- package/kanban/server/services/CeremonyService.js +275 -24
- package/kanban/server/services/TaskRunnerService.js +261 -0
- package/kanban/server/workers/run-task-worker.js +121 -0
- package/kanban/server/workers/seed-worker.js +94 -0
- package/kanban/server/workers/sponsor-call-worker.js +14 -6
- package/kanban/server/workers/sprint-planning-worker.js +94 -12
- package/package.json +2 -3
- package/cli/agents/solver-epic-api.json +0 -15
- package/cli/agents/solver-epic-api.md +0 -39
- package/cli/agents/solver-epic-backend.json +0 -15
- package/cli/agents/solver-epic-backend.md +0 -39
- package/cli/agents/solver-epic-cloud.json +0 -15
- package/cli/agents/solver-epic-cloud.md +0 -39
- package/cli/agents/solver-epic-data.json +0 -15
- package/cli/agents/solver-epic-data.md +0 -39
- package/cli/agents/solver-epic-database.json +0 -15
- package/cli/agents/solver-epic-database.md +0 -39
- package/cli/agents/solver-epic-developer.json +0 -15
- package/cli/agents/solver-epic-developer.md +0 -39
- package/cli/agents/solver-epic-devops.json +0 -15
- package/cli/agents/solver-epic-devops.md +0 -39
- package/cli/agents/solver-epic-frontend.json +0 -15
- package/cli/agents/solver-epic-frontend.md +0 -39
- package/cli/agents/solver-epic-mobile.json +0 -15
- package/cli/agents/solver-epic-mobile.md +0 -39
- package/cli/agents/solver-epic-qa.json +0 -15
- package/cli/agents/solver-epic-qa.md +0 -39
- package/cli/agents/solver-epic-security.json +0 -15
- package/cli/agents/solver-epic-security.md +0 -39
- package/cli/agents/solver-epic-solution-architect.json +0 -15
- package/cli/agents/solver-epic-solution-architect.md +0 -39
- package/cli/agents/solver-epic-test-architect.json +0 -15
- package/cli/agents/solver-epic-test-architect.md +0 -39
- package/cli/agents/solver-epic-ui.json +0 -15
- package/cli/agents/solver-epic-ui.md +0 -39
- package/cli/agents/solver-epic-ux.json +0 -15
- package/cli/agents/solver-epic-ux.md +0 -39
- package/cli/agents/solver-story-api.json +0 -15
- package/cli/agents/solver-story-api.md +0 -39
- package/cli/agents/solver-story-backend.json +0 -15
- package/cli/agents/solver-story-backend.md +0 -39
- package/cli/agents/solver-story-cloud.json +0 -15
- package/cli/agents/solver-story-cloud.md +0 -39
- package/cli/agents/solver-story-data.json +0 -15
- package/cli/agents/solver-story-data.md +0 -39
- package/cli/agents/solver-story-database.json +0 -15
- package/cli/agents/solver-story-database.md +0 -39
- package/cli/agents/solver-story-developer.json +0 -15
- package/cli/agents/solver-story-developer.md +0 -39
- package/cli/agents/solver-story-devops.json +0 -15
- package/cli/agents/solver-story-devops.md +0 -39
- package/cli/agents/solver-story-frontend.json +0 -15
- package/cli/agents/solver-story-frontend.md +0 -39
- package/cli/agents/solver-story-mobile.json +0 -15
- package/cli/agents/solver-story-mobile.md +0 -39
- package/cli/agents/solver-story-qa.json +0 -15
- package/cli/agents/solver-story-qa.md +0 -39
- package/cli/agents/solver-story-security.json +0 -15
- package/cli/agents/solver-story-security.md +0 -39
- package/cli/agents/solver-story-solution-architect.json +0 -15
- package/cli/agents/solver-story-solution-architect.md +0 -39
- package/cli/agents/solver-story-test-architect.json +0 -15
- package/cli/agents/solver-story-test-architect.md +0 -39
- package/cli/agents/solver-story-ui.json +0 -15
- package/cli/agents/solver-story-ui.md +0 -39
- package/cli/agents/solver-story-ux.json +0 -15
- package/cli/agents/solver-story-ux.md +0 -39
- package/cli/agents/validator-epic-api.json +0 -93
- package/cli/agents/validator-epic-api.md +0 -137
- package/cli/agents/validator-epic-backend.json +0 -93
- package/cli/agents/validator-epic-backend.md +0 -130
- package/cli/agents/validator-epic-cloud.json +0 -93
- package/cli/agents/validator-epic-cloud.md +0 -137
- package/cli/agents/validator-epic-data.json +0 -93
- package/cli/agents/validator-epic-data.md +0 -130
- package/cli/agents/validator-epic-database.json +0 -93
- package/cli/agents/validator-epic-database.md +0 -137
- package/cli/agents/validator-epic-developer.json +0 -74
- package/cli/agents/validator-epic-developer.md +0 -153
- package/cli/agents/validator-epic-devops.json +0 -74
- package/cli/agents/validator-epic-devops.md +0 -153
- package/cli/agents/validator-epic-frontend.json +0 -74
- package/cli/agents/validator-epic-frontend.md +0 -153
- package/cli/agents/validator-epic-mobile.json +0 -93
- package/cli/agents/validator-epic-mobile.md +0 -130
- package/cli/agents/validator-epic-qa.json +0 -93
- package/cli/agents/validator-epic-qa.md +0 -130
- package/cli/agents/validator-epic-security.json +0 -74
- package/cli/agents/validator-epic-security.md +0 -154
- package/cli/agents/validator-epic-solution-architect.json +0 -74
- package/cli/agents/validator-epic-solution-architect.md +0 -156
- package/cli/agents/validator-epic-test-architect.json +0 -93
- package/cli/agents/validator-epic-test-architect.md +0 -130
- package/cli/agents/validator-epic-ui.json +0 -93
- package/cli/agents/validator-epic-ui.md +0 -130
- package/cli/agents/validator-epic-ux.json +0 -93
- package/cli/agents/validator-epic-ux.md +0 -130
- package/cli/agents/validator-story-api.json +0 -104
- package/cli/agents/validator-story-api.md +0 -152
- package/cli/agents/validator-story-backend.json +0 -104
- package/cli/agents/validator-story-backend.md +0 -152
- package/cli/agents/validator-story-cloud.json +0 -104
- package/cli/agents/validator-story-cloud.md +0 -152
- package/cli/agents/validator-story-data.json +0 -104
- package/cli/agents/validator-story-data.md +0 -152
- package/cli/agents/validator-story-database.json +0 -104
- package/cli/agents/validator-story-database.md +0 -152
- package/cli/agents/validator-story-developer.json +0 -104
- package/cli/agents/validator-story-developer.md +0 -152
- package/cli/agents/validator-story-devops.json +0 -104
- package/cli/agents/validator-story-devops.md +0 -152
- package/cli/agents/validator-story-frontend.json +0 -104
- package/cli/agents/validator-story-frontend.md +0 -152
- package/cli/agents/validator-story-mobile.json +0 -104
- package/cli/agents/validator-story-mobile.md +0 -152
- package/cli/agents/validator-story-qa.json +0 -104
- package/cli/agents/validator-story-qa.md +0 -152
- package/cli/agents/validator-story-security.json +0 -104
- package/cli/agents/validator-story-security.md +0 -152
- package/cli/agents/validator-story-solution-architect.json +0 -104
- package/cli/agents/validator-story-solution-architect.md +0 -152
- package/cli/agents/validator-story-test-architect.json +0 -104
- package/cli/agents/validator-story-test-architect.md +0 -152
- package/cli/agents/validator-story-ui.json +0 -104
- package/cli/agents/validator-story-ui.md +0 -152
- package/cli/agents/validator-story-ux.json +0 -104
- package/cli/agents/validator-story-ux.md +0 -152
- package/kanban/client/dist/assets/index-CiD8PS2e.js +0 -306
- package/kanban/client/dist/assets/index-nLh0m82Q.css +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react';
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { ArrowDownToLine } from 'lucide-react';
|
|
3
3
|
import { useCeremonyStore } from '../../../store/ceremonyStore';
|
|
4
4
|
import { resetCeremony } from '../../../lib/api';
|
|
@@ -32,6 +32,17 @@ function buildStageGroups(progressLog) {
|
|
|
32
32
|
export function RunningStep({ transitioning, onPause, onResume, onCancel, onBackground }) {
|
|
33
33
|
const { progressLog, ceremonyStatus, ceremonyError, isPaused, setWizardStep, setCeremonyStatus, setCeremonyError } = useCeremonyStore();
|
|
34
34
|
const logBottomRef = useRef(null);
|
|
35
|
+
const [showForceStop, setShowForceStop] = useState(false);
|
|
36
|
+
|
|
37
|
+
// Show "Force Stop" button after 5s of cancelling
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (transitioning === 'cancelling') {
|
|
40
|
+
setShowForceStop(false);
|
|
41
|
+
const timer = setTimeout(() => setShowForceStop(true), 5000);
|
|
42
|
+
return () => clearTimeout(timer);
|
|
43
|
+
}
|
|
44
|
+
setShowForceStop(false);
|
|
45
|
+
}, [transitioning]);
|
|
35
46
|
|
|
36
47
|
const handleForceReset = async () => {
|
|
37
48
|
try { await resetCeremony(); } catch (_) {}
|
|
@@ -197,7 +208,17 @@ export function RunningStep({ transitioning, onPause, onResume, onCancel, onBack
|
|
|
197
208
|
{transitioning === 'pausing' ? (
|
|
198
209
|
<span className="flex items-center gap-1.5 text-xs text-slate-400"><span className="inline-block w-3 h-3 border border-slate-400 border-t-slate-200 rounded-full animate-spin" />Pausing…</span>
|
|
199
210
|
) : transitioning === 'cancelling' ? (
|
|
200
|
-
<
|
|
211
|
+
<div className="flex items-center gap-3">
|
|
212
|
+
<span className="flex items-center gap-1.5 text-xs text-red-400"><span className="inline-block w-3 h-3 border border-red-400 border-t-red-200 rounded-full animate-spin" />Cancelling…</span>
|
|
213
|
+
{showForceStop && (
|
|
214
|
+
<button
|
|
215
|
+
onClick={handleForceReset}
|
|
216
|
+
className="px-3 py-1.5 text-xs rounded-lg bg-red-600 text-white hover:bg-red-700 transition-colors"
|
|
217
|
+
>
|
|
218
|
+
Force Stop
|
|
219
|
+
</button>
|
|
220
|
+
)}
|
|
221
|
+
</div>
|
|
201
222
|
) : !isPaused ? (
|
|
202
223
|
<button
|
|
203
224
|
onClick={onPause}
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
} from '../ui/dialog';
|
|
9
9
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../ui/tabs';
|
|
10
10
|
import { Badge } from '../ui/badge';
|
|
11
|
+
import { SeedButton } from './SeedButton';
|
|
12
|
+
import { RunButton } from './RunButton';
|
|
11
13
|
import {
|
|
12
14
|
FileText,
|
|
13
15
|
BookOpen,
|
|
@@ -22,6 +24,8 @@ import {
|
|
|
22
24
|
Save,
|
|
23
25
|
ChevronUp,
|
|
24
26
|
Wand2,
|
|
27
|
+
ListChecks,
|
|
28
|
+
Lock,
|
|
25
29
|
} from 'lucide-react';
|
|
26
30
|
import {
|
|
27
31
|
getWorkItem,
|
|
@@ -33,6 +37,7 @@ import {
|
|
|
33
37
|
import { getStatusMetadata } from '../../lib/status-grouping';
|
|
34
38
|
import { cn } from '../../lib/utils';
|
|
35
39
|
import { RefineWorkItemPopup } from './RefineWorkItemPopup';
|
|
40
|
+
import { useKanbanStore } from '../../store/kanbanStore';
|
|
36
41
|
|
|
37
42
|
/**
|
|
38
43
|
* Clickable item box — same visual style as the children list.
|
|
@@ -151,6 +156,9 @@ export function CardDetailModal({ workItem, open, onOpenChange, onNavigate, onIt
|
|
|
151
156
|
// Parent chain for navigation
|
|
152
157
|
const [parentChain, setParentChain] = useState([]);
|
|
153
158
|
|
|
159
|
+
// Read-only during active ceremony
|
|
160
|
+
const ceremonyActive = useKanbanStore((s) => s.ceremonyActive);
|
|
161
|
+
|
|
154
162
|
// Refine popup state
|
|
155
163
|
const [refineOpen, setRefineOpen] = useState(false);
|
|
156
164
|
|
|
@@ -302,6 +310,11 @@ export function CardDetailModal({ workItem, open, onOpenChange, onNavigate, onIt
|
|
|
302
310
|
Children ({fullDetails.children.length})
|
|
303
311
|
</TabsTrigger>
|
|
304
312
|
)}
|
|
313
|
+
{fullDetails?.functions && fullDetails.functions.length > 0 && (
|
|
314
|
+
<TabsTrigger value="code">
|
|
315
|
+
Code ({fullDetails.functions.length})
|
|
316
|
+
</TabsTrigger>
|
|
317
|
+
)}
|
|
305
318
|
</TabsList>
|
|
306
319
|
</Tabs>
|
|
307
320
|
|
|
@@ -334,14 +347,24 @@ export function CardDetailModal({ workItem, open, onOpenChange, onNavigate, onIt
|
|
|
334
347
|
</div>
|
|
335
348
|
);
|
|
336
349
|
})()}
|
|
337
|
-
{fullDetails && (
|
|
338
|
-
<
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
350
|
+
{fullDetails && !ceremonyActive && (
|
|
351
|
+
<div className="flex items-center gap-2">
|
|
352
|
+
{/* Seed button — stories without children */}
|
|
353
|
+
{workItem.type === 'story' && (!fullDetails.children || fullDetails.children.length === 0) && (
|
|
354
|
+
<SeedButton storyId={workItem.id} onStarted={loadFullDetails} />
|
|
355
|
+
)}
|
|
356
|
+
{/* Run button — tasks that are planned, ready, or failed */}
|
|
357
|
+
{workItem.type === 'task' && (workItem.status === 'planned' || workItem.status === 'ready' || workItem.status === 'failed') && (
|
|
358
|
+
<RunButton taskId={workItem.id} onStarted={loadFullDetails} />
|
|
359
|
+
)}
|
|
360
|
+
<button
|
|
361
|
+
onClick={() => setRefineOpen(true)}
|
|
362
|
+
className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-violet-700 bg-violet-50 border border-violet-200 rounded-lg hover:bg-violet-100 transition-colors"
|
|
363
|
+
>
|
|
364
|
+
<Wand2 className="w-3.5 h-3.5" />
|
|
365
|
+
Refine with AI
|
|
366
|
+
</button>
|
|
367
|
+
</div>
|
|
345
368
|
)}
|
|
346
369
|
</div>
|
|
347
370
|
</div>
|
|
@@ -371,7 +394,11 @@ export function CardDetailModal({ workItem, open, onOpenChange, onNavigate, onIt
|
|
|
371
394
|
)}
|
|
372
395
|
{/* Edit toolbar */}
|
|
373
396
|
<div className="flex justify-end mb-2">
|
|
374
|
-
{
|
|
397
|
+
{ceremonyActive ? (
|
|
398
|
+
<span className="flex items-center gap-1 px-2 py-1 text-xs text-slate-400">
|
|
399
|
+
<Lock className="w-3 h-3" /> Read-only during ceremony
|
|
400
|
+
</span>
|
|
401
|
+
) : editingDoc ? (
|
|
375
402
|
<div className="flex gap-2">
|
|
376
403
|
<button
|
|
377
404
|
onClick={() => setEditingDoc(false)}
|
|
@@ -449,12 +476,45 @@ export function CardDetailModal({ workItem, open, onOpenChange, onNavigate, onIt
|
|
|
449
476
|
);
|
|
450
477
|
})()}
|
|
451
478
|
|
|
479
|
+
{/* Acceptance Criteria (stories use .acceptance, epics use .features) */}
|
|
480
|
+
{(() => {
|
|
481
|
+
const items = workItem.type === 'epic'
|
|
482
|
+
? fullDetails?.features
|
|
483
|
+
: fullDetails?.acceptance;
|
|
484
|
+
if (!items || items.length === 0) return null;
|
|
485
|
+
return (
|
|
486
|
+
<div>
|
|
487
|
+
<div className="flex items-center gap-2 text-sm font-semibold text-slate-700 mb-2">
|
|
488
|
+
<ListChecks className="w-4 h-4" />
|
|
489
|
+
<span>Acceptance Criteria</span>
|
|
490
|
+
<span className="ml-auto flex items-center gap-1 text-xs font-normal text-slate-400">
|
|
491
|
+
<Lock className="w-3 h-3" />
|
|
492
|
+
updated by tests
|
|
493
|
+
</span>
|
|
494
|
+
</div>
|
|
495
|
+
<ul className="space-y-1.5">
|
|
496
|
+
{items.map((ac, idx) => (
|
|
497
|
+
<li key={idx} className="flex items-start gap-2.5 text-sm text-slate-700">
|
|
498
|
+
<input
|
|
499
|
+
type="checkbox"
|
|
500
|
+
readOnly
|
|
501
|
+
disabled
|
|
502
|
+
className="mt-0.5 h-4 w-4 flex-shrink-0 rounded border-slate-300 text-indigo-600 cursor-not-allowed opacity-60"
|
|
503
|
+
/>
|
|
504
|
+
<span className="leading-snug">{ac}</span>
|
|
505
|
+
</li>
|
|
506
|
+
))}
|
|
507
|
+
</ul>
|
|
508
|
+
</div>
|
|
509
|
+
);
|
|
510
|
+
})()}
|
|
511
|
+
|
|
452
512
|
{/* Dependencies */}
|
|
453
513
|
{fullDetails?.dependencies && fullDetails.dependencies.length > 0 && (
|
|
454
514
|
<div>
|
|
455
515
|
<div className="flex items-center gap-2 text-sm font-semibold text-slate-700 mb-2">
|
|
456
516
|
<Link2 className="w-4 h-4" />
|
|
457
|
-
<span>
|
|
517
|
+
<span>Depends On ({fullDetails.dependencies.length})</span>
|
|
458
518
|
</div>
|
|
459
519
|
<div className="space-y-2">
|
|
460
520
|
{fullDetails.dependencies.map((depId) => {
|
|
@@ -516,6 +576,33 @@ export function CardDetailModal({ workItem, open, onOpenChange, onNavigate, onIt
|
|
|
516
576
|
</div>
|
|
517
577
|
</TabsContent>
|
|
518
578
|
)}
|
|
579
|
+
|
|
580
|
+
{/* Code Tab — Function Registry */}
|
|
581
|
+
{fullDetails?.functions && fullDetails.functions.length > 0 && (
|
|
582
|
+
<TabsContent value="code">
|
|
583
|
+
<div className="space-y-2">
|
|
584
|
+
{fullDetails.functions.map((fn, idx) => (
|
|
585
|
+
<div key={fn.name || idx} className="flex items-start gap-3 p-2 rounded-lg border border-slate-200 hover:bg-slate-50">
|
|
586
|
+
<div className="flex-1 min-w-0">
|
|
587
|
+
<div className="flex items-center gap-2">
|
|
588
|
+
<code className="text-sm font-mono text-blue-700 truncate">{fn.name}</code>
|
|
589
|
+
{fn.pure && (
|
|
590
|
+
<span className="px-1.5 py-0.5 text-[10px] font-medium rounded bg-green-100 text-green-700 border border-green-200">pure</span>
|
|
591
|
+
)}
|
|
592
|
+
{fn.type === 'exported' && (
|
|
593
|
+
<span className="px-1.5 py-0.5 text-[10px] font-medium rounded bg-blue-100 text-blue-700 border border-blue-200">export</span>
|
|
594
|
+
)}
|
|
595
|
+
</div>
|
|
596
|
+
<div className="text-xs text-slate-500 mt-0.5 truncate">{fn.file}</div>
|
|
597
|
+
{fn.satisfies && <div className="text-xs text-slate-400 mt-0.5">{fn.satisfies}</div>}
|
|
598
|
+
{fn.task && <div className="text-xs text-slate-400 mt-0.5">Task: {fn.task}</div>}
|
|
599
|
+
</div>
|
|
600
|
+
{fn.lines && <span className="text-xs text-slate-400 whitespace-nowrap">{fn.lines}L</span>}
|
|
601
|
+
</div>
|
|
602
|
+
))}
|
|
603
|
+
</div>
|
|
604
|
+
</TabsContent>
|
|
605
|
+
)}
|
|
519
606
|
</Tabs>
|
|
520
607
|
)}
|
|
521
608
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LayoutGrid, Package, Box } from 'lucide-react';
|
|
1
|
+
import { LayoutGrid, Package, Box, ListOrdered } from 'lucide-react';
|
|
2
2
|
import { useFilterStore } from '../../store/filterStore';
|
|
3
3
|
import { cn } from '../../lib/utils';
|
|
4
4
|
|
|
@@ -28,6 +28,12 @@ export function GroupingSelector() {
|
|
|
28
28
|
icon: Box,
|
|
29
29
|
description: 'Separate boards by type',
|
|
30
30
|
},
|
|
31
|
+
{
|
|
32
|
+
value: 'phase',
|
|
33
|
+
label: 'Phase',
|
|
34
|
+
icon: ListOrdered,
|
|
35
|
+
description: 'Implementation order by dependencies',
|
|
36
|
+
},
|
|
31
37
|
];
|
|
32
38
|
|
|
33
39
|
return (
|
|
@@ -59,21 +59,30 @@ export function KanbanCard({ workItem, onClick }) {
|
|
|
59
59
|
{statusMeta?.icon} {statusMeta?.label}
|
|
60
60
|
</div>
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
typeMeta.color === 'blue' &&
|
|
69
|
-
'border-blue-300 bg-blue-50 text-blue-700',
|
|
70
|
-
typeMeta.color === 'emerald' &&
|
|
71
|
-
'border-emerald-300 bg-emerald-50 text-emerald-700',
|
|
72
|
-
typeMeta.color === 'gray' &&
|
|
73
|
-
'border-gray-300 bg-gray-50 text-gray-700'
|
|
62
|
+
<div className="flex items-center gap-1.5">
|
|
63
|
+
{/* Phase Badge */}
|
|
64
|
+
{workItem.metadata?.codingPhase && (
|
|
65
|
+
<div className="px-1.5 py-0.5 rounded text-[10px] font-bold bg-amber-100 text-amber-800 border border-amber-300" title={`Implementation Phase ${workItem.metadata.codingPhase}`}>
|
|
66
|
+
P{workItem.metadata.codingPhase}
|
|
67
|
+
</div>
|
|
74
68
|
)}
|
|
75
|
-
|
|
76
|
-
{
|
|
69
|
+
|
|
70
|
+
{/* Type Badge */}
|
|
71
|
+
<div
|
|
72
|
+
className={cn(
|
|
73
|
+
'px-2 py-1 rounded border text-xs font-medium',
|
|
74
|
+
typeMeta.color === 'indigo' &&
|
|
75
|
+
'border-indigo-300 bg-indigo-50 text-indigo-700',
|
|
76
|
+
typeMeta.color === 'blue' &&
|
|
77
|
+
'border-blue-300 bg-blue-50 text-blue-700',
|
|
78
|
+
typeMeta.color === 'emerald' &&
|
|
79
|
+
'border-emerald-300 bg-emerald-50 text-emerald-700',
|
|
80
|
+
typeMeta.color === 'gray' &&
|
|
81
|
+
'border-gray-300 bg-gray-50 text-gray-700'
|
|
82
|
+
)}
|
|
83
|
+
>
|
|
84
|
+
{typeMeta.label}
|
|
85
|
+
</div>
|
|
77
86
|
</div>
|
|
78
87
|
</div>
|
|
79
88
|
</div>
|
|
@@ -249,21 +249,16 @@ export function RefineWorkItemPopup({
|
|
|
249
249
|
Promise.all([getModels(), getSettings()])
|
|
250
250
|
.then(([data, settings]) => {
|
|
251
251
|
setModels(data);
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
);
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
: data.length > 0
|
|
258
|
-
? data[0].modelId
|
|
259
|
-
: '';
|
|
252
|
+
const ready = data.filter((m) => settings.apiKeys?.[normalizeProvider(m.provider)]?.isSet);
|
|
253
|
+
const isPro = (id) => /pro|opus|sonnet/i.test(id);
|
|
254
|
+
const isLite = (id) => /flash|lite|haiku|mini/i.test(id);
|
|
255
|
+
const generator = ready.find((m) => isPro(m.modelId)) || ready[0];
|
|
256
|
+
const genId = generator ? generator.modelId : (data.length > 0 ? data[0].modelId : '');
|
|
260
257
|
setSelectedModelId(genId);
|
|
261
|
-
const
|
|
262
|
-
(m) =>
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
);
|
|
266
|
-
setSelectedValidatorModelId(otherReady ? otherReady.modelId : genId);
|
|
258
|
+
const validator = ready.find((m) => isLite(m.modelId) && m.modelId !== genId)
|
|
259
|
+
|| ready.find((m) => m.modelId !== genId)
|
|
260
|
+
|| generator;
|
|
261
|
+
setSelectedValidatorModelId(validator ? validator.modelId : genId);
|
|
267
262
|
})
|
|
268
263
|
.catch(() => setConfigError('Failed to load models.'));
|
|
269
264
|
}, []);
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { Play, Loader2, AlertTriangle, X, ChevronDown, ChevronUp } from 'lucide-react';
|
|
3
|
+
import { getDependencyStatus, startRunTask } from '../../lib/api';
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Run Button — runs a task in a git worktree (implement + test + commit).
|
|
8
|
+
* Shows dependency check, and streaming progress log.
|
|
9
|
+
*/
|
|
10
|
+
export function RunButton({ taskId, onStarted }) {
|
|
11
|
+
const [state, setState] = useState('idle'); // idle | checking | blocked | running | complete | error
|
|
12
|
+
const [blockers, setBlockers] = useState([]);
|
|
13
|
+
const [error, setError] = useState(null);
|
|
14
|
+
const [progress, setProgress] = useState([]);
|
|
15
|
+
const [showLog, setShowLog] = useState(false);
|
|
16
|
+
const [processId, setProcessId] = useState(null);
|
|
17
|
+
const logRef = useRef(null);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight;
|
|
21
|
+
}, [progress]);
|
|
22
|
+
|
|
23
|
+
// Listen for WebSocket run-task events dispatched by App.jsx
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!processId) return;
|
|
26
|
+
|
|
27
|
+
const handler = (event) => {
|
|
28
|
+
const msg = event.detail;
|
|
29
|
+
if (!msg || msg.processId !== processId) return;
|
|
30
|
+
|
|
31
|
+
if (msg.type === 'run-task:progress') {
|
|
32
|
+
setProgress(prev => [...prev, msg.message]);
|
|
33
|
+
} else if (msg.type === 'run-task:complete') {
|
|
34
|
+
setState('complete');
|
|
35
|
+
setProgress(prev => [...prev, 'Task complete — code committed.']);
|
|
36
|
+
onStarted?.();
|
|
37
|
+
} else if (msg.type === 'run-task:error') {
|
|
38
|
+
setState('error');
|
|
39
|
+
setError(msg.error || 'Run failed');
|
|
40
|
+
setProgress(prev => [...prev, `Error: ${msg.error}`]);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
window.addEventListener('avc-ws-message', handler);
|
|
45
|
+
return () => window.removeEventListener('avc-ws-message', handler);
|
|
46
|
+
}, [processId, onStarted]);
|
|
47
|
+
|
|
48
|
+
const handleClick = async () => {
|
|
49
|
+
if (state === 'checking' || state === 'running') return;
|
|
50
|
+
|
|
51
|
+
setState('checking');
|
|
52
|
+
setError(null);
|
|
53
|
+
setBlockers([]);
|
|
54
|
+
setProgress([]);
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const depStatus = await getDependencyStatus(taskId);
|
|
58
|
+
if (!depStatus.ready) {
|
|
59
|
+
setState('blocked');
|
|
60
|
+
setBlockers(depStatus.blockers || []);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
setState('running');
|
|
65
|
+
setShowLog(true);
|
|
66
|
+
setProgress(['Starting task implementation...']);
|
|
67
|
+
const result = await startRunTask(taskId);
|
|
68
|
+
setProcessId(result.processId);
|
|
69
|
+
setProgress(prev => [...prev, `Process started: ${result.processId}`]);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
setError(err.message);
|
|
72
|
+
setState('error');
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const dismiss = () => {
|
|
77
|
+
setState('idle');
|
|
78
|
+
setBlockers([]);
|
|
79
|
+
setError(null);
|
|
80
|
+
setProgress([]);
|
|
81
|
+
setShowLog(false);
|
|
82
|
+
setProcessId(null);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
if (state === 'blocked') {
|
|
86
|
+
return (
|
|
87
|
+
<div className="space-y-2">
|
|
88
|
+
<div className="flex items-center gap-2 text-amber-700 text-sm bg-amber-50 border border-amber-200 rounded-lg px-3 py-2">
|
|
89
|
+
<AlertTriangle className="w-4 h-4 flex-shrink-0" />
|
|
90
|
+
<div>
|
|
91
|
+
<div className="font-medium">Dependencies not met</div>
|
|
92
|
+
<ul className="mt-1 text-xs space-y-0.5">
|
|
93
|
+
{blockers.map((b) => (<li key={b.id}>{b.id}: {b.name} ({b.status})</li>))}
|
|
94
|
+
</ul>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
<button onClick={dismiss} className="text-xs text-slate-500 hover:text-slate-700">Dismiss</button>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if ((state === 'running' || state === 'complete' || state === 'error') && showLog) {
|
|
103
|
+
return (
|
|
104
|
+
<div className="space-y-2">
|
|
105
|
+
<div className="flex items-center justify-between">
|
|
106
|
+
<div className="flex items-center gap-2 text-sm font-medium">
|
|
107
|
+
{state === 'running' && <Loader2 className="w-4 h-4 animate-spin text-blue-600" />}
|
|
108
|
+
{state === 'complete' && <Play className="w-4 h-4 text-green-600" />}
|
|
109
|
+
{state === 'error' && <AlertTriangle className="w-4 h-4 text-red-600" />}
|
|
110
|
+
<span className={cn(
|
|
111
|
+
state === 'running' && 'text-blue-700',
|
|
112
|
+
state === 'complete' && 'text-green-700',
|
|
113
|
+
state === 'error' && 'text-red-700',
|
|
114
|
+
)}>
|
|
115
|
+
{state === 'running' ? 'Implementing...' : state === 'complete' ? 'Complete' : 'Failed'}
|
|
116
|
+
</span>
|
|
117
|
+
</div>
|
|
118
|
+
<div className="flex items-center gap-1">
|
|
119
|
+
<button onClick={() => setShowLog(!showLog)} className="p-1 text-slate-400 hover:text-slate-600">
|
|
120
|
+
{showLog ? <ChevronUp className="w-3.5 h-3.5" /> : <ChevronDown className="w-3.5 h-3.5" />}
|
|
121
|
+
</button>
|
|
122
|
+
{state !== 'running' && (
|
|
123
|
+
<button onClick={dismiss} className="p-1 text-slate-400 hover:text-slate-600">
|
|
124
|
+
<X className="w-3.5 h-3.5" />
|
|
125
|
+
</button>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
{showLog && (
|
|
130
|
+
<div ref={logRef} className="max-h-32 overflow-y-auto bg-slate-900 text-slate-300 text-xs font-mono rounded-md p-2 space-y-0.5">
|
|
131
|
+
{progress.map((msg, i) => (
|
|
132
|
+
<div key={i} className={cn(
|
|
133
|
+
msg.startsWith('Error') && 'text-red-400',
|
|
134
|
+
msg.includes('complete') && 'text-green-400',
|
|
135
|
+
)}>{msg}</div>
|
|
136
|
+
))}
|
|
137
|
+
{state === 'running' && <div className="text-slate-500 animate-pulse">...</div>}
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<div className="space-y-1">
|
|
146
|
+
<button
|
|
147
|
+
onClick={handleClick}
|
|
148
|
+
disabled={state === 'checking'}
|
|
149
|
+
className={cn(
|
|
150
|
+
'flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm font-medium transition-colors',
|
|
151
|
+
state === 'checking'
|
|
152
|
+
? 'bg-blue-100 text-blue-700 cursor-wait'
|
|
153
|
+
: 'bg-blue-600 text-white hover:bg-blue-700'
|
|
154
|
+
)}
|
|
155
|
+
>
|
|
156
|
+
{state === 'checking' ? <Loader2 className="w-4 h-4 animate-spin" /> : <Play className="w-4 h-4" />}
|
|
157
|
+
{state === 'checking' ? 'Checking...' : 'Run'}
|
|
158
|
+
</button>
|
|
159
|
+
{error && <p className="text-xs text-red-600">{error}</p>}
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react';
|
|
2
|
+
import { Sprout, Loader2, AlertTriangle, X, ChevronDown, ChevronUp, Settings } from 'lucide-react';
|
|
3
|
+
import { getDependencyStatus, startSeedCeremony, getSettings } from '../../lib/api';
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Seed Button — decomposes a story into tasks/subtasks.
|
|
8
|
+
* Shows dependency check, model selector, and streaming progress log.
|
|
9
|
+
*/
|
|
10
|
+
export function SeedButton({ storyId, onStarted }) {
|
|
11
|
+
const [state, setState] = useState('idle'); // idle | checking | blocked | configuring | seeding | complete | error
|
|
12
|
+
const [blockers, setBlockers] = useState([]);
|
|
13
|
+
const [error, setError] = useState(null);
|
|
14
|
+
const [progress, setProgress] = useState([]);
|
|
15
|
+
const [showLog, setShowLog] = useState(false);
|
|
16
|
+
const [processId, setProcessId] = useState(null);
|
|
17
|
+
const logRef = useRef(null);
|
|
18
|
+
|
|
19
|
+
// Auto-scroll progress log
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight;
|
|
22
|
+
}, [progress]);
|
|
23
|
+
|
|
24
|
+
// Listen for WebSocket seed events dispatched by App.jsx
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!processId) return;
|
|
27
|
+
|
|
28
|
+
const handler = (event) => {
|
|
29
|
+
const msg = event.detail;
|
|
30
|
+
if (!msg || msg.processId !== processId) return;
|
|
31
|
+
|
|
32
|
+
if (msg.type === 'seed:progress') {
|
|
33
|
+
setProgress(prev => [...prev, msg.message]);
|
|
34
|
+
} else if (msg.type === 'seed:complete') {
|
|
35
|
+
setState('complete');
|
|
36
|
+
setProgress(prev => [...prev, 'Seed complete — tasks created.']);
|
|
37
|
+
onStarted?.();
|
|
38
|
+
} else if (msg.type === 'seed:error') {
|
|
39
|
+
setState('error');
|
|
40
|
+
setError(msg.error || 'Seed failed');
|
|
41
|
+
setProgress(prev => [...prev, `Error: ${msg.error}`]);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
window.addEventListener('avc-ws-message', handler);
|
|
46
|
+
return () => window.removeEventListener('avc-ws-message', handler);
|
|
47
|
+
}, [processId, onStarted]);
|
|
48
|
+
|
|
49
|
+
const handleClick = async () => {
|
|
50
|
+
if (state === 'checking' || state === 'seeding') return;
|
|
51
|
+
|
|
52
|
+
setState('checking');
|
|
53
|
+
setError(null);
|
|
54
|
+
setBlockers([]);
|
|
55
|
+
setProgress([]);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const depStatus = await getDependencyStatus(storyId);
|
|
59
|
+
if (!depStatus.ready) {
|
|
60
|
+
setState('blocked');
|
|
61
|
+
setBlockers(depStatus.blockers || []);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Go straight to seeding (model config comes from avc.json server-side)
|
|
66
|
+
setState('seeding');
|
|
67
|
+
setShowLog(true);
|
|
68
|
+
setProgress(['Starting seed ceremony...']);
|
|
69
|
+
const result = await startSeedCeremony(storyId);
|
|
70
|
+
setProcessId(result.processId);
|
|
71
|
+
setProgress(prev => [...prev, `Process started: ${result.processId}`]);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
setError(err.message);
|
|
74
|
+
setState('error');
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const dismiss = () => {
|
|
79
|
+
setState('idle');
|
|
80
|
+
setBlockers([]);
|
|
81
|
+
setError(null);
|
|
82
|
+
setProgress([]);
|
|
83
|
+
setShowLog(false);
|
|
84
|
+
setProcessId(null);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Blocked state — show blockers
|
|
88
|
+
if (state === 'blocked') {
|
|
89
|
+
return (
|
|
90
|
+
<div className="space-y-2">
|
|
91
|
+
<div className="flex items-center gap-2 text-amber-700 text-sm bg-amber-50 border border-amber-200 rounded-lg px-3 py-2">
|
|
92
|
+
<AlertTriangle className="w-4 h-4 flex-shrink-0" />
|
|
93
|
+
<div>
|
|
94
|
+
<div className="font-medium">Dependencies not met</div>
|
|
95
|
+
<ul className="mt-1 text-xs space-y-0.5">
|
|
96
|
+
{blockers.map((b) => (
|
|
97
|
+
<li key={b.id}>{b.id}: {b.name} ({b.status})</li>
|
|
98
|
+
))}
|
|
99
|
+
</ul>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
<button onClick={dismiss} className="text-xs text-slate-500 hover:text-slate-700">Dismiss</button>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Seeding/complete/error state — show progress log
|
|
108
|
+
if ((state === 'seeding' || state === 'complete' || state === 'error') && showLog) {
|
|
109
|
+
return (
|
|
110
|
+
<div className="space-y-2">
|
|
111
|
+
<div className="flex items-center justify-between">
|
|
112
|
+
<div className="flex items-center gap-2 text-sm font-medium">
|
|
113
|
+
{state === 'seeding' && <Loader2 className="w-4 h-4 animate-spin text-green-600" />}
|
|
114
|
+
{state === 'complete' && <Sprout className="w-4 h-4 text-green-600" />}
|
|
115
|
+
{state === 'error' && <AlertTriangle className="w-4 h-4 text-red-600" />}
|
|
116
|
+
<span className={cn(
|
|
117
|
+
state === 'seeding' && 'text-green-700',
|
|
118
|
+
state === 'complete' && 'text-green-700',
|
|
119
|
+
state === 'error' && 'text-red-700',
|
|
120
|
+
)}>
|
|
121
|
+
{state === 'seeding' ? 'Seeding...' : state === 'complete' ? 'Seed Complete' : 'Seed Failed'}
|
|
122
|
+
</span>
|
|
123
|
+
</div>
|
|
124
|
+
<div className="flex items-center gap-1">
|
|
125
|
+
<button onClick={() => setShowLog(!showLog)} className="p-1 text-slate-400 hover:text-slate-600">
|
|
126
|
+
{showLog ? <ChevronUp className="w-3.5 h-3.5" /> : <ChevronDown className="w-3.5 h-3.5" />}
|
|
127
|
+
</button>
|
|
128
|
+
{state !== 'seeding' && (
|
|
129
|
+
<button onClick={dismiss} className="p-1 text-slate-400 hover:text-slate-600">
|
|
130
|
+
<X className="w-3.5 h-3.5" />
|
|
131
|
+
</button>
|
|
132
|
+
)}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
{showLog && (
|
|
136
|
+
<div
|
|
137
|
+
ref={logRef}
|
|
138
|
+
className="max-h-32 overflow-y-auto bg-slate-900 text-slate-300 text-xs font-mono rounded-md p-2 space-y-0.5"
|
|
139
|
+
>
|
|
140
|
+
{progress.map((msg, i) => (
|
|
141
|
+
<div key={i} className={cn(
|
|
142
|
+
msg.startsWith('Error') && 'text-red-400',
|
|
143
|
+
msg.includes('complete') && 'text-green-400',
|
|
144
|
+
)}>{msg}</div>
|
|
145
|
+
))}
|
|
146
|
+
{state === 'seeding' && <div className="text-slate-500 animate-pulse">...</div>}
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Default idle state — seed button
|
|
154
|
+
return (
|
|
155
|
+
<div className="space-y-1">
|
|
156
|
+
<button
|
|
157
|
+
onClick={handleClick}
|
|
158
|
+
disabled={state === 'checking'}
|
|
159
|
+
className={cn(
|
|
160
|
+
'flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm font-medium transition-colors',
|
|
161
|
+
state === 'checking'
|
|
162
|
+
? 'bg-green-100 text-green-700 cursor-wait'
|
|
163
|
+
: 'bg-green-600 text-white hover:bg-green-700'
|
|
164
|
+
)}
|
|
165
|
+
>
|
|
166
|
+
{state === 'checking' ? (
|
|
167
|
+
<Loader2 className="w-4 h-4 animate-spin" />
|
|
168
|
+
) : (
|
|
169
|
+
<Sprout className="w-4 h-4" />
|
|
170
|
+
)}
|
|
171
|
+
{state === 'checking' ? 'Checking...' : 'Seed'}
|
|
172
|
+
</button>
|
|
173
|
+
{error && <p className="text-xs text-red-600">{error}</p>}
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
}
|