@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.
- package/cli/agent-loader.js +21 -0
- package/cli/agents/agent-selector.md +152 -0
- package/cli/agents/architecture-recommender.md +418 -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/database-deep-dive.md +470 -0
- package/cli/agents/database-recommender.md +634 -0
- package/cli/agents/doc-distributor.md +176 -0
- package/cli/agents/doc-writer-epic.md +42 -0
- package/cli/agents/doc-writer-story.md +43 -0
- package/cli/agents/documentation-updater.md +203 -0
- package/cli/agents/duplicate-detector.md +110 -0
- package/cli/agents/epic-story-decomposer.md +559 -0
- package/cli/agents/feature-context-generator.md +91 -0
- package/cli/agents/gap-checker-epic.md +52 -0
- package/cli/agents/impact-checker-story.md +51 -0
- package/cli/agents/migration-guide-generator.md +305 -0
- package/cli/agents/mission-scope-generator.md +143 -0
- package/cli/agents/mission-scope-validator.md +146 -0
- package/cli/agents/project-context-extractor.md +122 -0
- package/cli/agents/project-documentation-creator.json +226 -0
- package/cli/agents/project-documentation-creator.md +595 -0
- package/cli/agents/question-prefiller.md +269 -0
- package/cli/agents/refiner-epic.md +39 -0
- package/cli/agents/refiner-story.md +42 -0
- package/cli/agents/scaffolding-generator.md +99 -0
- package/cli/agents/seed-validator.md +71 -0
- package/cli/agents/story-doc-enricher.md +133 -0
- package/cli/agents/story-scope-reviewer.md +147 -0
- package/cli/agents/story-splitter.md +83 -0
- package/cli/agents/suggestion-business-analyst.md +88 -0
- package/cli/agents/suggestion-deployment-architect.md +263 -0
- package/cli/agents/suggestion-product-manager.md +129 -0
- package/cli/agents/suggestion-security-specialist.md +156 -0
- package/cli/agents/suggestion-technical-architect.md +269 -0
- package/cli/agents/suggestion-ux-researcher.md +93 -0
- package/cli/agents/task-subtask-decomposer.md +188 -0
- package/cli/agents/validator-documentation.json +183 -0
- package/cli/agents/validator-documentation.md +455 -0
- package/cli/agents/validator-selector.md +211 -0
- package/cli/ansi-colors.js +21 -0
- package/cli/api-reference-tool.js +368 -0
- package/cli/build-docs.js +29 -8
- package/cli/ceremony-history.js +369 -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/command-logger.js +49 -12
- package/cli/components/static-output.js +63 -0
- package/cli/console-output-manager.js +94 -0
- package/cli/dependency-checker.js +72 -0
- package/cli/docs-sync.js +306 -0
- package/cli/epic-story-validator.js +659 -0
- package/cli/evaluation-prompts.js +1008 -0
- package/cli/execution-context.js +195 -0
- package/cli/generate-summary-table.js +340 -0
- package/cli/init-model-config.js +704 -0
- package/cli/init.js +1737 -278
- package/cli/kanban-server-manager.js +227 -0
- package/cli/llm-claude.js +150 -1
- package/cli/llm-gemini.js +109 -0
- package/cli/llm-local.js +493 -0
- package/cli/llm-mock.js +233 -0
- package/cli/llm-openai.js +454 -0
- package/cli/llm-provider.js +379 -3
- package/cli/llm-token-limits.js +211 -0
- package/cli/llm-verifier.js +662 -0
- package/cli/llm-xiaomi.js +143 -0
- package/cli/message-constants.js +49 -0
- package/cli/message-manager.js +334 -0
- package/cli/message-types.js +96 -0
- package/cli/messaging-api.js +291 -0
- 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 +192 -0
- package/cli/model-query-engine.js +468 -0
- package/cli/model-recommendation-analyzer.js +495 -0
- package/cli/model-selector.js +270 -0
- package/cli/output-buffer.js +107 -0
- package/cli/process-manager.js +73 -2
- package/cli/prompt-logger.js +57 -0
- package/cli/repl-ink.js +4625 -1094
- package/cli/repl-old.js +3 -4
- package/cli/seed-processor.js +962 -0
- package/cli/sprint-planning-processor.js +4162 -0
- package/cli/template-processor.js +2149 -105
- package/cli/templates/project.md +25 -8
- package/cli/templates/vitepress-config.mts.template +5 -4
- package/cli/token-tracker.js +547 -0
- package/cli/tools/generate-story-validators.js +317 -0
- package/cli/tools/generate-validators.js +669 -0
- package/cli/update-checker.js +19 -17
- package/cli/update-notifier.js +4 -4
- package/cli/validation-router.js +667 -0
- package/cli/verification-tracker.js +563 -0
- package/cli/worktree-runner.js +654 -0
- package/kanban/README.md +386 -0
- package/kanban/client/README.md +205 -0
- package/kanban/client/components.json +20 -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 +16 -0
- package/kanban/client/dist/vite.svg +1 -0
- package/kanban/client/index.html +15 -0
- package/kanban/client/package-lock.json +9442 -0
- package/kanban/client/package.json +44 -0
- package/kanban/client/postcss.config.js +6 -0
- package/kanban/client/public/vite.svg +1 -0
- package/kanban/client/src/App.jsx +651 -0
- package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +420 -0
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +629 -0
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +1133 -0
- package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
- package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +686 -0
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +838 -0
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +136 -0
- package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
- package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
- package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
- package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +329 -0
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +249 -0
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +646 -0
- package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
- package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
- package/kanban/client/src/components/kanban/GroupingSelector.jsx +63 -0
- package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
- package/kanban/client/src/components/kanban/KanbanCard.jsx +147 -0
- package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +784 -0
- 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/layout/LoadingScreen.jsx +82 -0
- package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
- package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
- package/kanban/client/src/components/settings/AgentsTab.jsx +381 -0
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +142 -0
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +105 -0
- package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +95 -0
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +269 -0
- package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
- package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
- package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
- package/kanban/client/src/components/stats/CostModal.jsx +384 -0
- package/kanban/client/src/components/ui/badge.jsx +27 -0
- package/kanban/client/src/components/ui/dialog.jsx +121 -0
- package/kanban/client/src/components/ui/tabs.jsx +85 -0
- package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
- package/kanban/client/src/hooks/useGrouping.js +177 -0
- package/kanban/client/src/hooks/useWebSocket.js +120 -0
- package/kanban/client/src/lib/__tests__/api.test.js +196 -0
- package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
- package/kanban/client/src/lib/api.js +515 -0
- package/kanban/client/src/lib/status-grouping.js +154 -0
- package/kanban/client/src/lib/utils.js +11 -0
- package/kanban/client/src/main.jsx +10 -0
- package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
- package/kanban/client/src/store/ceremonyStore.js +172 -0
- package/kanban/client/src/store/filterStore.js +201 -0
- package/kanban/client/src/store/kanbanStore.js +123 -0
- package/kanban/client/src/store/processStore.js +65 -0
- package/kanban/client/src/store/sprintPlanningStore.js +33 -0
- package/kanban/client/src/styles/globals.css +59 -0
- package/kanban/client/tailwind.config.js +77 -0
- package/kanban/client/vite.config.js +28 -0
- package/kanban/client/vitest.config.js +28 -0
- package/kanban/dev-start.sh +47 -0
- package/kanban/package.json +12 -0
- package/kanban/server/index.js +537 -0
- package/kanban/server/routes/ceremony.js +454 -0
- package/kanban/server/routes/costs.js +163 -0
- package/kanban/server/routes/openai-oauth.js +366 -0
- package/kanban/server/routes/processes.js +50 -0
- package/kanban/server/routes/settings.js +736 -0
- package/kanban/server/routes/websocket.js +281 -0
- package/kanban/server/routes/work-items.js +487 -0
- package/kanban/server/services/CeremonyService.js +1441 -0
- package/kanban/server/services/FileSystemScanner.js +95 -0
- package/kanban/server/services/FileWatcher.js +144 -0
- package/kanban/server/services/HierarchyBuilder.js +196 -0
- package/kanban/server/services/ProcessRegistry.js +122 -0
- package/kanban/server/services/TaskRunnerService.js +261 -0
- package/kanban/server/services/WorkItemReader.js +123 -0
- package/kanban/server/services/WorkItemRefineService.js +510 -0
- package/kanban/server/start.js +49 -0
- package/kanban/server/utils/kanban-logger.js +132 -0
- package/kanban/server/utils/markdown.js +91 -0
- package/kanban/server/utils/status-grouping.js +107 -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 +92 -0
- package/kanban/server/workers/sprint-planning-worker.js +212 -0
- package/package.json +19 -7
- package/cli/agents/documentation.md +0 -302
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Dialog,
|
|
4
|
+
DialogContent,
|
|
5
|
+
DialogHeader,
|
|
6
|
+
DialogTitle,
|
|
7
|
+
DialogDescription,
|
|
8
|
+
} from '../ui/dialog';
|
|
9
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../ui/tabs';
|
|
10
|
+
import { Badge } from '../ui/badge';
|
|
11
|
+
import { SeedButton } from './SeedButton';
|
|
12
|
+
import { RunButton } from './RunButton';
|
|
13
|
+
import {
|
|
14
|
+
FileText,
|
|
15
|
+
BookOpen,
|
|
16
|
+
Users,
|
|
17
|
+
Link2,
|
|
18
|
+
Calendar,
|
|
19
|
+
Package,
|
|
20
|
+
ChevronLeft,
|
|
21
|
+
ChevronRight,
|
|
22
|
+
Pencil,
|
|
23
|
+
X,
|
|
24
|
+
Save,
|
|
25
|
+
ChevronUp,
|
|
26
|
+
Wand2,
|
|
27
|
+
ListChecks,
|
|
28
|
+
Lock,
|
|
29
|
+
} from 'lucide-react';
|
|
30
|
+
import {
|
|
31
|
+
getWorkItem,
|
|
32
|
+
getWorkItemDoc,
|
|
33
|
+
getWorkItemDocRaw,
|
|
34
|
+
updateWorkItemDoc,
|
|
35
|
+
getProjectDoc,
|
|
36
|
+
} from '../../lib/api';
|
|
37
|
+
import { getStatusMetadata } from '../../lib/status-grouping';
|
|
38
|
+
import { cn } from '../../lib/utils';
|
|
39
|
+
import { RefineWorkItemPopup } from './RefineWorkItemPopup';
|
|
40
|
+
import { useKanbanStore } from '../../store/kanbanStore';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Clickable item box — same visual style as the children list.
|
|
44
|
+
*/
|
|
45
|
+
function ItemBox({ item, fallbackName, fallbackId, onItemClick }) {
|
|
46
|
+
const typeMeta = item ? TYPE_METADATA[item.type] : null;
|
|
47
|
+
const statusMeta = item ? getStatusMetadata(item.status) : null;
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<button
|
|
51
|
+
onClick={() => item && onItemClick?.(item)}
|
|
52
|
+
disabled={!item}
|
|
53
|
+
className="w-full text-left p-4 border border-slate-200 rounded-lg hover:border-blue-300 hover:bg-blue-50 transition-colors disabled:opacity-50 disabled:cursor-default"
|
|
54
|
+
>
|
|
55
|
+
<div className="flex items-start justify-between">
|
|
56
|
+
<div className="flex-1 min-w-0">
|
|
57
|
+
<p className="font-medium text-slate-900 mb-1 truncate">{item?.name ?? fallbackName}</p>
|
|
58
|
+
{item?.description && (
|
|
59
|
+
<p className="text-xs text-slate-500 mb-1 line-clamp-2">{item.description}</p>
|
|
60
|
+
)}
|
|
61
|
+
{(statusMeta || typeMeta) && (
|
|
62
|
+
<div className="flex items-center gap-2 mt-1">
|
|
63
|
+
{statusMeta && (
|
|
64
|
+
<Badge variant="secondary" className={cn(
|
|
65
|
+
'text-xs',
|
|
66
|
+
statusMeta.color === 'green' && 'bg-green-100 text-green-700',
|
|
67
|
+
statusMeta.color === 'blue' && 'bg-blue-100 text-blue-700',
|
|
68
|
+
statusMeta.color === 'yellow' && 'bg-yellow-100 text-yellow-700',
|
|
69
|
+
statusMeta.color === 'purple' && 'bg-purple-100 text-purple-700',
|
|
70
|
+
statusMeta.color === 'red' && 'bg-red-100 text-red-700',
|
|
71
|
+
)}>
|
|
72
|
+
{statusMeta.icon} {statusMeta.label}
|
|
73
|
+
</Badge>
|
|
74
|
+
)}
|
|
75
|
+
{typeMeta && (
|
|
76
|
+
<Badge variant="outline" className="text-xs">
|
|
77
|
+
{typeMeta.icon} {typeMeta.label}
|
|
78
|
+
</Badge>
|
|
79
|
+
)}
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
{item && <ChevronRight className="w-4 h-4 text-slate-400 mt-1 flex-shrink-0 ml-2" />}
|
|
84
|
+
</div>
|
|
85
|
+
</button>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Inline viewer for a parent's (or project root's) doc or context file.
|
|
91
|
+
* item = { id, name } for work items, or { id: 'project', name: 'Project' } for root.
|
|
92
|
+
*/
|
|
93
|
+
function ParentFileLink({ item }) {
|
|
94
|
+
const [expanded, setExpanded] = useState(false);
|
|
95
|
+
const [html, setHtml] = useState(null);
|
|
96
|
+
|
|
97
|
+
const load = async () => {
|
|
98
|
+
if (html !== null) { setExpanded(!expanded); return; }
|
|
99
|
+
const content = item.id === 'project'
|
|
100
|
+
? await getProjectDoc()
|
|
101
|
+
: await getWorkItemDoc(item.id).catch(() => '');
|
|
102
|
+
setHtml(content || '<p class="text-slate-400 italic">No content</p>');
|
|
103
|
+
setExpanded(true);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const isProject = item.id === 'project';
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div className="w-full">
|
|
110
|
+
<button
|
|
111
|
+
onClick={load}
|
|
112
|
+
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded font-medium ${
|
|
113
|
+
isProject
|
|
114
|
+
? 'bg-indigo-50 hover:bg-indigo-100 text-indigo-700'
|
|
115
|
+
: 'bg-slate-100 hover:bg-slate-200 text-slate-600'
|
|
116
|
+
}`}
|
|
117
|
+
>
|
|
118
|
+
{expanded ? <ChevronUp className="w-3 h-3" /> : <ChevronRight className="w-3 h-3" />}
|
|
119
|
+
{item.name} — doc.md
|
|
120
|
+
</button>
|
|
121
|
+
{expanded && html && (
|
|
122
|
+
<div className="mt-2 ml-4 p-3 border-l-2 border-slate-200 prose prose-sm prose-slate max-w-none">
|
|
123
|
+
<div dangerouslySetInnerHTML={{ __html: html }} />
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Work item type metadata
|
|
132
|
+
*/
|
|
133
|
+
const TYPE_METADATA = {
|
|
134
|
+
epic: { color: 'indigo', icon: '🏛️', label: 'Epic' },
|
|
135
|
+
story: { color: 'blue', icon: '📖', label: 'Story' },
|
|
136
|
+
task: { color: 'emerald', icon: '⚙️', label: 'Task' },
|
|
137
|
+
subtask: { color: 'gray', icon: '📝', label: 'Subtask' },
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Card Detail Modal Component
|
|
142
|
+
* Displays full work item details with tabbed sections
|
|
143
|
+
*/
|
|
144
|
+
export function CardDetailModal({ workItem, open, onOpenChange, onNavigate, onItemClick, allItems, refineProgress, refineResult, refineError, onClearRefine }) {
|
|
145
|
+
const [activeTab, setActiveTab] = useState('documentation');
|
|
146
|
+
const [fullDetails, setFullDetails] = useState(null);
|
|
147
|
+
const [documentation, setDocumentation] = useState(null);
|
|
148
|
+
const [loading, setLoading] = useState(false);
|
|
149
|
+
const [error, setError] = useState(null);
|
|
150
|
+
|
|
151
|
+
// Edit mode state
|
|
152
|
+
const [editingDoc, setEditingDoc] = useState(false);
|
|
153
|
+
const [docDraft, setDocDraft] = useState('');
|
|
154
|
+
const [saving, setSaving] = useState(false);
|
|
155
|
+
|
|
156
|
+
// Parent chain for navigation
|
|
157
|
+
const [parentChain, setParentChain] = useState([]);
|
|
158
|
+
|
|
159
|
+
// Read-only during active ceremony
|
|
160
|
+
const ceremonyActive = useKanbanStore((s) => s.ceremonyActive);
|
|
161
|
+
|
|
162
|
+
// Refine popup state
|
|
163
|
+
const [refineOpen, setRefineOpen] = useState(false);
|
|
164
|
+
|
|
165
|
+
// Reload full details after a successful refine apply
|
|
166
|
+
const handleRefineAccepted = () => {
|
|
167
|
+
setRefineOpen(false);
|
|
168
|
+
onClearRefine?.();
|
|
169
|
+
loadFullDetails();
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// Load full details when modal opens
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
if (open && workItem) {
|
|
175
|
+
loadFullDetails();
|
|
176
|
+
}
|
|
177
|
+
}, [open, workItem?.id]);
|
|
178
|
+
|
|
179
|
+
// Fall back to 'details' tab if no doc.md available
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
if (!loading && documentation === null) setActiveTab('details');
|
|
182
|
+
}, [loading, documentation]);
|
|
183
|
+
|
|
184
|
+
// Keyboard navigation
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
if (!open) return;
|
|
187
|
+
|
|
188
|
+
const handleKeyPress = (e) => {
|
|
189
|
+
if (e.key === 'ArrowLeft') {
|
|
190
|
+
onNavigate?.('prev');
|
|
191
|
+
} else if (e.key === 'ArrowRight') {
|
|
192
|
+
onNavigate?.('next');
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
document.addEventListener('keydown', handleKeyPress);
|
|
197
|
+
return () => document.removeEventListener('keydown', handleKeyPress);
|
|
198
|
+
}, [open, onNavigate]);
|
|
199
|
+
|
|
200
|
+
const loadFullDetails = async () => {
|
|
201
|
+
if (!workItem) return;
|
|
202
|
+
|
|
203
|
+
setLoading(true);
|
|
204
|
+
setError(null);
|
|
205
|
+
setEditingDoc(false);
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const details = await getWorkItem(workItem.id);
|
|
209
|
+
setFullDetails(details);
|
|
210
|
+
|
|
211
|
+
// Build parent chain: project root → … → grandparent → parent
|
|
212
|
+
const chain = [{ id: 'project', name: 'Project' }];
|
|
213
|
+
if (allItems) {
|
|
214
|
+
const ancestors = [];
|
|
215
|
+
let parentId = details.parentId;
|
|
216
|
+
while (parentId) {
|
|
217
|
+
const parent = allItems.find((i) => i.id === parentId);
|
|
218
|
+
if (!parent) break;
|
|
219
|
+
ancestors.unshift(parent);
|
|
220
|
+
parentId = parent.parentId;
|
|
221
|
+
}
|
|
222
|
+
chain.push(...ancestors);
|
|
223
|
+
}
|
|
224
|
+
setParentChain(chain);
|
|
225
|
+
|
|
226
|
+
const doc = await getWorkItemDoc(workItem.id).catch(() => null);
|
|
227
|
+
setDocumentation(doc || null);
|
|
228
|
+
} catch (err) {
|
|
229
|
+
setError(err.message);
|
|
230
|
+
console.error('Failed to load work item details:', err);
|
|
231
|
+
} finally {
|
|
232
|
+
setLoading(false);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const startEditDoc = async () => {
|
|
237
|
+
const raw = await getWorkItemDocRaw(workItem.id);
|
|
238
|
+
setDocDraft(raw);
|
|
239
|
+
setEditingDoc(true);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const saveDoc = async () => {
|
|
243
|
+
setSaving(true);
|
|
244
|
+
try {
|
|
245
|
+
await updateWorkItemDoc(workItem.id, docDraft);
|
|
246
|
+
const html = await getWorkItemDoc(workItem.id);
|
|
247
|
+
setDocumentation(html);
|
|
248
|
+
setEditingDoc(false);
|
|
249
|
+
} finally {
|
|
250
|
+
setSaving(false);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
if (!workItem) return null;
|
|
255
|
+
|
|
256
|
+
const statusMeta = getStatusMetadata(workItem.status);
|
|
257
|
+
const typeMeta = TYPE_METADATA[workItem.type] || TYPE_METADATA.task;
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
261
|
+
<DialogContent onClose={() => onOpenChange(false)}>
|
|
262
|
+
{/* Header */}
|
|
263
|
+
<DialogHeader>
|
|
264
|
+
<div className="flex items-start justify-between pr-8">
|
|
265
|
+
<div className="flex-1">
|
|
266
|
+
<DialogTitle className="mb-2">{workItem.name}</DialogTitle>
|
|
267
|
+
<DialogDescription className="flex items-center gap-2 flex-wrap">
|
|
268
|
+
{/* Status Badge */}
|
|
269
|
+
<Badge
|
|
270
|
+
variant="secondary"
|
|
271
|
+
className={cn(
|
|
272
|
+
statusMeta?.color === 'blue' && 'bg-blue-100 text-blue-700',
|
|
273
|
+
statusMeta?.color === 'yellow' && 'bg-yellow-100 text-yellow-700',
|
|
274
|
+
statusMeta?.color === 'green' && 'bg-green-100 text-green-700',
|
|
275
|
+
statusMeta?.color === 'purple' && 'bg-purple-100 text-purple-700',
|
|
276
|
+
statusMeta?.color === 'red' && 'bg-red-100 text-red-700'
|
|
277
|
+
)}
|
|
278
|
+
>
|
|
279
|
+
{statusMeta?.icon} {statusMeta?.label}
|
|
280
|
+
</Badge>
|
|
281
|
+
|
|
282
|
+
{/* Type Badge */}
|
|
283
|
+
<Badge variant="outline">{typeMeta.icon} {typeMeta.label}</Badge>
|
|
284
|
+
|
|
285
|
+
{/* ID */}
|
|
286
|
+
<span className="text-slate-400 text-xs">{workItem.id}</span>
|
|
287
|
+
</DialogDescription>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
</DialogHeader>
|
|
291
|
+
|
|
292
|
+
{/* Tabs */}
|
|
293
|
+
<div className="flex-1 overflow-hidden flex flex-col">
|
|
294
|
+
<div className="px-6 pb-2 border-b border-slate-100 flex items-center justify-between gap-4">
|
|
295
|
+
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
|
296
|
+
<TabsList>
|
|
297
|
+
{documentation && (
|
|
298
|
+
<TabsTrigger value="documentation">
|
|
299
|
+
<BookOpen className="w-4 h-4 mr-2" />
|
|
300
|
+
Documentation
|
|
301
|
+
</TabsTrigger>
|
|
302
|
+
)}
|
|
303
|
+
<TabsTrigger value="details">
|
|
304
|
+
<FileText className="w-4 h-4 mr-2" />
|
|
305
|
+
Details
|
|
306
|
+
</TabsTrigger>
|
|
307
|
+
{fullDetails?.children && fullDetails.children.length > 0 && (
|
|
308
|
+
<TabsTrigger value="children">
|
|
309
|
+
<Users className="w-4 h-4 mr-2" />
|
|
310
|
+
Children ({fullDetails.children.length})
|
|
311
|
+
</TabsTrigger>
|
|
312
|
+
)}
|
|
313
|
+
{fullDetails?.functions && fullDetails.functions.length > 0 && (
|
|
314
|
+
<TabsTrigger value="code">
|
|
315
|
+
Code ({fullDetails.functions.length})
|
|
316
|
+
</TabsTrigger>
|
|
317
|
+
)}
|
|
318
|
+
</TabsList>
|
|
319
|
+
</Tabs>
|
|
320
|
+
|
|
321
|
+
{/* Validation score + Refine button — right side, same row as tabs */}
|
|
322
|
+
<div className="flex items-center gap-2 flex-shrink-0">
|
|
323
|
+
{(() => {
|
|
324
|
+
const vr = fullDetails?.metadata?.validationResult;
|
|
325
|
+
if (!vr) return null;
|
|
326
|
+
const score = vr.averageScore ?? null;
|
|
327
|
+
const critCount = (vr.criticalIssues || []).length;
|
|
328
|
+
const majCount = (vr.majorIssues || []).length;
|
|
329
|
+
const minCount = (vr.minorIssues || []).length;
|
|
330
|
+
const totalIssues = critCount + majCount + minCount;
|
|
331
|
+
return (
|
|
332
|
+
<div className="flex items-center gap-1.5 text-xs text-slate-500">
|
|
333
|
+
<span className={`font-bold px-2 py-0.5 rounded-full ${
|
|
334
|
+
score >= 95 ? 'bg-green-100 text-green-700'
|
|
335
|
+
: score >= 80 ? 'bg-amber-100 text-amber-700'
|
|
336
|
+
: 'bg-red-100 text-red-700'
|
|
337
|
+
}`}>{score}/100</span>
|
|
338
|
+
{totalIssues > 0 && (
|
|
339
|
+
<span>
|
|
340
|
+
{critCount > 0 && <span className="text-red-600 font-medium">{critCount} critical</span>}
|
|
341
|
+
{critCount > 0 && majCount > 0 && <span className="mx-0.5">·</span>}
|
|
342
|
+
{majCount > 0 && <span className="text-orange-600 font-medium">{majCount} major</span>}
|
|
343
|
+
{(critCount > 0 || majCount > 0) && minCount > 0 && <span className="mx-0.5">·</span>}
|
|
344
|
+
{minCount > 0 && <span className="text-amber-600">{minCount} minor</span>}
|
|
345
|
+
</span>
|
|
346
|
+
)}
|
|
347
|
+
</div>
|
|
348
|
+
);
|
|
349
|
+
})()}
|
|
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>
|
|
368
|
+
)}
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
{/* Tab Content */}
|
|
373
|
+
<div className="flex-1 overflow-y-auto px-6 py-4">
|
|
374
|
+
{loading ? (
|
|
375
|
+
<div className="flex items-center justify-center py-12">
|
|
376
|
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
|
377
|
+
</div>
|
|
378
|
+
) : error ? (
|
|
379
|
+
<div className="text-center py-12">
|
|
380
|
+
<p className="text-red-600">Failed to load details: {error}</p>
|
|
381
|
+
</div>
|
|
382
|
+
) : (
|
|
383
|
+
<Tabs value={activeTab}>
|
|
384
|
+
{/* Documentation Tab */}
|
|
385
|
+
{documentation && (
|
|
386
|
+
<TabsContent value="documentation">
|
|
387
|
+
{/* Parent chain links */}
|
|
388
|
+
{parentChain.length > 0 && (
|
|
389
|
+
<div className="mb-4 flex flex-wrap items-center gap-2 text-xs text-slate-500">
|
|
390
|
+
{parentChain.map((ancestor) => (
|
|
391
|
+
<ParentFileLink key={ancestor.id} item={ancestor} fileType="doc" />
|
|
392
|
+
))}
|
|
393
|
+
</div>
|
|
394
|
+
)}
|
|
395
|
+
{/* Edit toolbar */}
|
|
396
|
+
<div className="flex justify-end mb-2">
|
|
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 ? (
|
|
402
|
+
<div className="flex gap-2">
|
|
403
|
+
<button
|
|
404
|
+
onClick={() => setEditingDoc(false)}
|
|
405
|
+
className="flex items-center gap-1 px-2 py-1 text-xs text-slate-600 hover:text-slate-900 border border-slate-200 rounded"
|
|
406
|
+
>
|
|
407
|
+
<X className="w-3 h-3" /> Cancel
|
|
408
|
+
</button>
|
|
409
|
+
<button
|
|
410
|
+
onClick={saveDoc}
|
|
411
|
+
disabled={saving}
|
|
412
|
+
className="flex items-center gap-1 px-2 py-1 text-xs text-white bg-indigo-600 hover:bg-indigo-700 rounded disabled:opacity-50"
|
|
413
|
+
>
|
|
414
|
+
<Save className="w-3 h-3" /> {saving ? 'Saving…' : 'Save'}
|
|
415
|
+
</button>
|
|
416
|
+
</div>
|
|
417
|
+
) : (
|
|
418
|
+
<button
|
|
419
|
+
onClick={startEditDoc}
|
|
420
|
+
className="flex items-center gap-1 px-2 py-1 text-xs text-slate-600 hover:text-slate-900 border border-slate-200 rounded"
|
|
421
|
+
>
|
|
422
|
+
<Pencil className="w-3 h-3" /> Edit
|
|
423
|
+
</button>
|
|
424
|
+
)}
|
|
425
|
+
</div>
|
|
426
|
+
{editingDoc ? (
|
|
427
|
+
<textarea
|
|
428
|
+
className="w-full h-96 p-3 text-sm font-mono border border-slate-300 rounded focus:outline-none focus:ring-2 focus:ring-indigo-400 resize-y"
|
|
429
|
+
value={docDraft}
|
|
430
|
+
onChange={(e) => setDocDraft(e.target.value)}
|
|
431
|
+
/>
|
|
432
|
+
) : (
|
|
433
|
+
<div className="prose prose-slate max-w-none">
|
|
434
|
+
<div dangerouslySetInnerHTML={{ __html: documentation }} />
|
|
435
|
+
</div>
|
|
436
|
+
)}
|
|
437
|
+
</TabsContent>
|
|
438
|
+
)}
|
|
439
|
+
|
|
440
|
+
{/* Details Tab */}
|
|
441
|
+
<TabsContent value="details">
|
|
442
|
+
<div className="space-y-6">
|
|
443
|
+
{/* Created */}
|
|
444
|
+
{fullDetails?.created && (
|
|
445
|
+
<div className="flex items-center gap-2 text-sm text-slate-500">
|
|
446
|
+
<Calendar className="w-4 h-4" />
|
|
447
|
+
<span>Created {new Date(fullDetails.created).toLocaleDateString()}</span>
|
|
448
|
+
</div>
|
|
449
|
+
)}
|
|
450
|
+
|
|
451
|
+
{/* Parent */}
|
|
452
|
+
{fullDetails?.parentName && (() => {
|
|
453
|
+
const parent = allItems?.find((i) => i.id === fullDetails.parentId);
|
|
454
|
+
return (
|
|
455
|
+
<div>
|
|
456
|
+
<div className="flex items-center gap-2 text-sm font-semibold text-slate-700 mb-2">
|
|
457
|
+
<Package className="w-4 h-4" />
|
|
458
|
+
<span>Parent</span>
|
|
459
|
+
</div>
|
|
460
|
+
<ItemBox item={parent} fallbackName={fullDetails.parentName} fallbackId={fullDetails.parentId} onItemClick={onItemClick} />
|
|
461
|
+
</div>
|
|
462
|
+
);
|
|
463
|
+
})()}
|
|
464
|
+
|
|
465
|
+
{/* Epic */}
|
|
466
|
+
{fullDetails?.epicName && workItem.type !== 'epic' && (() => {
|
|
467
|
+
const epic = allItems?.find((i) => i.id === fullDetails.epicId);
|
|
468
|
+
return (
|
|
469
|
+
<div>
|
|
470
|
+
<div className="flex items-center gap-2 text-sm font-semibold text-slate-700 mb-2">
|
|
471
|
+
<Package className="w-4 h-4" />
|
|
472
|
+
<span>Epic</span>
|
|
473
|
+
</div>
|
|
474
|
+
<ItemBox item={epic} fallbackName={fullDetails.epicName} fallbackId={fullDetails.epicId} onItemClick={onItemClick} />
|
|
475
|
+
</div>
|
|
476
|
+
);
|
|
477
|
+
})()}
|
|
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
|
+
|
|
512
|
+
{/* Dependencies */}
|
|
513
|
+
{fullDetails?.dependencies && fullDetails.dependencies.length > 0 && (
|
|
514
|
+
<div>
|
|
515
|
+
<div className="flex items-center gap-2 text-sm font-semibold text-slate-700 mb-2">
|
|
516
|
+
<Link2 className="w-4 h-4" />
|
|
517
|
+
<span>Depends On ({fullDetails.dependencies.length})</span>
|
|
518
|
+
</div>
|
|
519
|
+
<div className="space-y-2">
|
|
520
|
+
{fullDetails.dependencies.map((depId) => {
|
|
521
|
+
const depItem = allItems?.find((i) => i.id === depId);
|
|
522
|
+
return <ItemBox key={depId} item={depItem} fallbackName={depId} fallbackId={depId} onItemClick={onItemClick} />;
|
|
523
|
+
})}
|
|
524
|
+
</div>
|
|
525
|
+
</div>
|
|
526
|
+
)}
|
|
527
|
+
</div>
|
|
528
|
+
</TabsContent>
|
|
529
|
+
|
|
530
|
+
{/* Children Tab */}
|
|
531
|
+
{fullDetails?.children && fullDetails.children.length > 0 && (
|
|
532
|
+
<TabsContent value="children">
|
|
533
|
+
<div className="space-y-2">
|
|
534
|
+
{fullDetails.children.map((child) => {
|
|
535
|
+
const childStatusMeta = getStatusMetadata(child.status);
|
|
536
|
+
const childTypeMeta = TYPE_METADATA[child.type];
|
|
537
|
+
|
|
538
|
+
const fullChild = allItems?.find((i) => i.id === child.id);
|
|
539
|
+
|
|
540
|
+
return (
|
|
541
|
+
<button
|
|
542
|
+
key={child.id}
|
|
543
|
+
onClick={() => fullChild && onItemClick?.(fullChild)}
|
|
544
|
+
className="w-full text-left p-4 border border-slate-200 rounded-lg hover:border-blue-300 hover:bg-blue-50 transition-colors cursor-pointer"
|
|
545
|
+
>
|
|
546
|
+
<div className="flex items-start justify-between">
|
|
547
|
+
<div>
|
|
548
|
+
<h4 className="font-medium text-slate-900 mb-1">
|
|
549
|
+
{child.name}
|
|
550
|
+
</h4>
|
|
551
|
+
<div className="flex items-center gap-2">
|
|
552
|
+
<Badge
|
|
553
|
+
variant="secondary"
|
|
554
|
+
className={cn(
|
|
555
|
+
'text-xs',
|
|
556
|
+
childStatusMeta?.color === 'green' &&
|
|
557
|
+
'bg-green-100 text-green-700',
|
|
558
|
+
childStatusMeta?.color === 'blue' &&
|
|
559
|
+
'bg-blue-100 text-blue-700',
|
|
560
|
+
childStatusMeta?.color === 'yellow' &&
|
|
561
|
+
'bg-yellow-100 text-yellow-700'
|
|
562
|
+
)}
|
|
563
|
+
>
|
|
564
|
+
{childStatusMeta?.icon} {childStatusMeta?.label}
|
|
565
|
+
</Badge>
|
|
566
|
+
<Badge variant="outline" className="text-xs">
|
|
567
|
+
{childTypeMeta?.icon} {childTypeMeta?.label}
|
|
568
|
+
</Badge>
|
|
569
|
+
</div>
|
|
570
|
+
</div>
|
|
571
|
+
<ChevronRight className="w-4 h-4 text-slate-400 mt-1 flex-shrink-0" />
|
|
572
|
+
</div>
|
|
573
|
+
</button>
|
|
574
|
+
);
|
|
575
|
+
})}
|
|
576
|
+
</div>
|
|
577
|
+
</TabsContent>
|
|
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
|
+
)}
|
|
606
|
+
</Tabs>
|
|
607
|
+
)}
|
|
608
|
+
</div>
|
|
609
|
+
</div>
|
|
610
|
+
|
|
611
|
+
{/* Footer with Navigation */}
|
|
612
|
+
{onNavigate && (
|
|
613
|
+
<div className="px-6 py-4 border-t border-slate-100 flex items-center justify-between">
|
|
614
|
+
<button
|
|
615
|
+
onClick={() => onNavigate('prev')}
|
|
616
|
+
className="flex items-center gap-2 text-sm text-slate-600 hover:text-slate-900 transition-colors"
|
|
617
|
+
>
|
|
618
|
+
<ChevronLeft className="w-4 h-4" />
|
|
619
|
+
Previous
|
|
620
|
+
</button>
|
|
621
|
+
<span className="text-xs text-slate-400">Use ← → to navigate</span>
|
|
622
|
+
<button
|
|
623
|
+
onClick={() => onNavigate('next')}
|
|
624
|
+
className="flex items-center gap-2 text-sm text-slate-600 hover:text-slate-900 transition-colors"
|
|
625
|
+
>
|
|
626
|
+
Next
|
|
627
|
+
<ChevronRight className="w-4 h-4" />
|
|
628
|
+
</button>
|
|
629
|
+
</div>
|
|
630
|
+
)}
|
|
631
|
+
</DialogContent>
|
|
632
|
+
|
|
633
|
+
{/* Refine popup — rendered outside DialogContent so it stacks above the modal */}
|
|
634
|
+
{refineOpen && fullDetails && (
|
|
635
|
+
<RefineWorkItemPopup
|
|
636
|
+
item={fullDetails}
|
|
637
|
+
refineProgress={refineProgress?.itemId === fullDetails.id ? refineProgress : null}
|
|
638
|
+
refineResult={refineResult?.itemId === fullDetails.id ? refineResult : null}
|
|
639
|
+
refineError={refineError?.itemId === fullDetails.id ? refineError : null}
|
|
640
|
+
onClose={() => { setRefineOpen(false); onClearRefine?.(); }}
|
|
641
|
+
onAccepted={handleRefineAccepted}
|
|
642
|
+
/>
|
|
643
|
+
)}
|
|
644
|
+
</Dialog>
|
|
645
|
+
);
|
|
646
|
+
}
|