@assistkick/create 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/create.js +0 -0
- package/package.json +9 -7
- package/templates/assistkick-product-system/.env.example +1 -0
- package/templates/assistkick-product-system/local.db +0 -0
- package/templates/assistkick-product-system/package.json +4 -2
- package/templates/assistkick-product-system/packages/backend/package.json +2 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +165 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +358 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +356 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +96 -1
- package/templates/assistkick-product-system/packages/backend/src/routes/graph.ts +1 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/kanban.ts +61 -6
- package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +200 -84
- package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +6 -3
- package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +53 -17
- package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +218 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +119 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +158 -0
- package/templates/assistkick-product-system/packages/backend/src/server.ts +60 -9
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +489 -0
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +416 -0
- package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.test.ts +189 -0
- package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.ts +182 -0
- package/templates/assistkick-product-system/packages/backend/src/services/init.ts +43 -77
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +16 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +73 -2
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +4 -4
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +87 -11
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +210 -69
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +210 -215
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +162 -0
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +148 -0
- package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +11 -5
- package/templates/assistkick-product-system/packages/backend/src/services/tts_service.test.ts +64 -0
- package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +134 -0
- package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.test.ts +256 -0
- package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +258 -0
- package/templates/assistkick-product-system/packages/backend/src/services/workflow_group_service.ts +106 -0
- package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.test.ts +275 -0
- package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.ts +245 -0
- package/templates/assistkick-product-system/packages/frontend/package-lock.json +3455 -0
- package/templates/assistkick-product-system/packages/frontend/package.json +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/App.tsx +8 -0
- package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +458 -18
- package/templates/assistkick-product-system/packages/frontend/src/api/client_files.test.ts +172 -0
- package/templates/assistkick-product-system/packages/frontend/src/api/client_video.test.ts +238 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +307 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/CoherenceView.tsx +82 -66
- package/templates/assistkick-product-system/packages/frontend/src/components/CompositionPlaceholder.tsx +97 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/DesignSystemView.tsx +20 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +57 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +313 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeContextMenu.tsx +61 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +73 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +404 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/GitRepoModal.tsx +187 -56
- package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +71 -73
- package/templates/assistkick-product-system/packages/frontend/src/components/GraphSettings.tsx +8 -8
- package/templates/assistkick-product-system/packages/frontend/src/components/GraphView.tsx +1 -1
- package/templates/assistkick-product-system/packages/frontend/src/components/InviteUserDialog.tsx +15 -11
- package/templates/assistkick-product-system/packages/frontend/src/components/IterationCommentModal.tsx +80 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +263 -167
- package/templates/assistkick-product-system/packages/frontend/src/components/LoginPage.tsx +14 -14
- package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +54 -33
- package/templates/assistkick-product-system/packages/frontend/src/components/QaIssueSheet.tsx +32 -49
- package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +43 -48
- package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +121 -52
- package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +20 -14
- package/templates/assistkick-product-system/packages/frontend/src/components/UsersView.tsx +52 -52
- package/templates/assistkick-product-system/packages/frontend/src/components/VideoGallery.tsx +313 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/VideographyView.tsx +250 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/WorkflowsView.tsx +474 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/AccentBorderList.tsx +53 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/Button.tsx +87 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonGroup.tsx +29 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonShowcase.tsx +221 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/CardGlass.tsx +141 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/CompletionRing.tsx +30 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/ContentCard.tsx +34 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/IconButton.tsx +74 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCard.tsx +103 -87
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +9 -188
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/Kbd.tsx +11 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/KindBadge.tsx +21 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/NavBarSidekick.tsx +81 -37
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/SidePanelShowcase.tsx +370 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/SideSheet.tsx +64 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/StatusDot.tsx +18 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCardPositionNode.tsx +36 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCycleCountNode.tsx +60 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/EndNode.tsx +42 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GenerateTTSNode.tsx +52 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +189 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +123 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RebuildBundleNode.tsx +20 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RenderVideoNode.tsx +72 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RunAgentNode.tsx +51 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/SetCardMetadataNode.tsx +53 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/StartNode.tsx +18 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/TransitionCardNode.tsx +59 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +341 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +643 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/autoLayout.ts +103 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/edgeColors.ts +35 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +246 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.test.ts +119 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +136 -0
- package/templates/assistkick-product-system/packages/frontend/src/constants/graph.ts +13 -11
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useAutoSave.ts +75 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useToast.tsx +16 -3
- package/templates/assistkick-product-system/packages/frontend/src/pages/accept_invitation_page.tsx +30 -27
- package/templates/assistkick-product-system/packages/frontend/src/pages/forgot_password_page.tsx +18 -15
- package/templates/assistkick-product-system/packages/frontend/src/pages/register_page.tsx +21 -18
- package/templates/assistkick-product-system/packages/frontend/src/pages/reset_password_page.tsx +28 -25
- package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +1 -1
- package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +2 -2
- package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +13 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +2 -2
- package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +13 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +6 -3
- package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +4 -4
- package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +275 -3535
- package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.test.ts +167 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.ts +101 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.test.ts +42 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.ts +17 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.test.ts +145 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.ts +42 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.test.ts +4 -10
- package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.ts +19 -1
- package/templates/assistkick-product-system/packages/frontend/vite.config.ts +5 -0
- package/templates/assistkick-product-system/packages/shared/db/local.db +0 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0004_tidy_matthew_murdock.sql +9 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0005_mysterious_falcon.sql +692 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0006_next_venom.sql +9 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0007_deep_barracuda.sql +39 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0008_puzzling_hannibal_king.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0009_amused_beast.sql +8 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0010_spotty_moira_mactaggert.sql +9 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0011_goofy_snowbird.sql +3 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0011_supreme_doctor_octopus.sql +3 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0013_reflective_prowler.sql +15 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0014_nifty_punisher.sql +15 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0004_snapshot.json +921 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0005_snapshot.json +1042 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0006_snapshot.json +1101 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0007_snapshot.json +1336 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0008_snapshot.json +1275 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0009_snapshot.json +1327 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0010_snapshot.json +1393 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0011_snapshot.json +1436 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0013_snapshot.json +1538 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0014_snapshot.json +1545 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +77 -0
- package/templates/assistkick-product-system/packages/shared/db/schema.ts +114 -0
- package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +32 -7
- package/templates/assistkick-product-system/packages/shared/lib/constants.ts +9 -0
- package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +12 -4
- package/templates/assistkick-product-system/packages/shared/lib/graph.ts +5 -0
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +1999 -0
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +1437 -0
- package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +211 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +43 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +13 -2
- package/templates/assistkick-product-system/packages/shared/tools/get_kanban.ts +1 -1
- package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.test.ts +226 -0
- package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.ts +251 -0
- package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -2
- package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.test.ts +10 -0
- package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.ts +6 -0
- package/templates/assistkick-product-system/packages/video/Root.tsx +85 -0
- package/templates/assistkick-product-system/packages/video/components/email_scene.tsx +231 -0
- package/templates/assistkick-product-system/packages/video/components/outro_scene.tsx +153 -0
- package/templates/assistkick-product-system/packages/video/components/part_divider.tsx +90 -0
- package/templates/assistkick-product-system/packages/video/components/scene.tsx +226 -0
- package/templates/assistkick-product-system/packages/video/components/theme.ts +22 -0
- package/templates/assistkick-product-system/packages/video/components/title_scene.tsx +169 -0
- package/templates/assistkick-product-system/packages/video/components/video_split_layout.tsx +84 -0
- package/templates/assistkick-product-system/packages/video/compositions/.gitkeep +0 -0
- package/templates/assistkick-product-system/packages/video/index.ts +4 -0
- package/templates/assistkick-product-system/packages/video/package.json +28 -0
- package/templates/assistkick-product-system/packages/video/remotion.config.ts +11 -0
- package/templates/assistkick-product-system/packages/video/scripts/process_script.test.ts +326 -0
- package/templates/assistkick-product-system/packages/video/scripts/process_script.ts +630 -0
- package/templates/assistkick-product-system/packages/video/style.css +1 -0
- package/templates/assistkick-product-system/packages/video/tsconfig.json +18 -0
- package/templates/assistkick-product-system/tests/graph_legend.test.ts +2 -1
- package/templates/assistkick-product-system/tests/video_render_service.test.ts +181 -0
- package/templates/assistkick-product-system/tests/web_terminal.test.ts +219 -455
- package/templates/assistkick-product-system/tests/workflow_integration.test.ts +341 -0
- package/templates/skills/assistkick-developer/SKILL.md +3 -0
- package/templates/skills/assistkick-developer/references/react_development_guidelines.md +225 -0
- package/templates/skills/product-system/graph.json +1890 -0
- package/templates/skills/product-system/kanban.json +304 -0
- package/templates/skills/product-system/nodes/comp_001.md +56 -0
- package/templates/skills/product-system/nodes/comp_002.md +57 -0
- package/templates/skills/product-system/nodes/data_001.md +51 -0
- package/templates/skills/product-system/nodes/data_002.md +40 -0
- package/templates/skills/product-system/nodes/data_004.md +38 -0
- package/templates/skills/product-system/nodes/dec_001.md +34 -0
- package/templates/skills/product-system/nodes/dec_016.md +32 -0
- package/templates/skills/product-system/nodes/feat_008.md +30 -0
- package/templates/skills/video-composition-agent/SKILL.md +232 -0
- package/templates/skills/video-script-writer/SKILL.md +136 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
2
|
import { apiClient } from '../api/client';
|
|
3
|
-
import { COLUMNS,
|
|
3
|
+
import { COLUMNS, WORKFLOW_STATUS_LABELS } from '../constants/graph';
|
|
4
4
|
import { useToast } from '../hooks/useToast';
|
|
5
5
|
import { KanbanCard } from './ds/KanbanCard';
|
|
6
|
+
import { IterationCommentModal } from './IterationCommentModal';
|
|
7
|
+
import { WorkflowMonitorModal } from './workflow/WorkflowMonitorModal';
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
interface KanbanViewProps {
|
|
@@ -17,6 +19,10 @@ interface CardData {
|
|
|
17
19
|
name: string;
|
|
18
20
|
completeness: number;
|
|
19
21
|
kind: string;
|
|
22
|
+
nodeType: string;
|
|
23
|
+
featureType: string | null;
|
|
24
|
+
isEpicChild: boolean;
|
|
25
|
+
epicParentId: string | null;
|
|
20
26
|
rejectionCount: number;
|
|
21
27
|
notes: any[];
|
|
22
28
|
reviews: any[];
|
|
@@ -25,62 +31,48 @@ interface CardData {
|
|
|
25
31
|
movedAt: string | null;
|
|
26
32
|
}
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const fill = (lastTurnUsage.input_tokens || 0)
|
|
49
|
-
+ (lastTurnUsage.cache_creation_input_tokens || 0)
|
|
50
|
-
+ (lastTurnUsage.cache_read_input_tokens || 0);
|
|
51
|
-
const denom = contextWindow || 200_000;
|
|
52
|
-
const pct = Math.round((fill / denom) * 100);
|
|
53
|
-
return `${pct}%`;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const formatCost = (costUsd: number | null): string => {
|
|
57
|
-
if (costUsd == null) return '';
|
|
58
|
-
return `$${costUsd.toFixed(4)}`;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const formatModel = (model: string | null): string => {
|
|
62
|
-
if (!model) return '';
|
|
63
|
-
// Shorten long model names: "claude-sonnet-4-20250514" → "sonnet-4"
|
|
64
|
-
const match = model.match(/(opus|sonnet|haiku)-(\d[\d.]*)/);
|
|
65
|
-
return match ? `${match[1]}-${match[2]}` : model;
|
|
66
|
-
};
|
|
34
|
+
interface WorkflowStatus {
|
|
35
|
+
executionId?: string;
|
|
36
|
+
status: string;
|
|
37
|
+
featureId: string;
|
|
38
|
+
currentNodeId?: string | null;
|
|
39
|
+
currentNodeType?: string | null;
|
|
40
|
+
currentAgentName?: string | null;
|
|
41
|
+
error?: string | null;
|
|
42
|
+
completedNodes?: string[];
|
|
43
|
+
failedNodes?: string[];
|
|
44
|
+
pendingNodes?: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface WorkflowListItem {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
featureType: string | null;
|
|
51
|
+
triggerColumn: string | null;
|
|
52
|
+
isDefault: number;
|
|
53
|
+
}
|
|
67
54
|
|
|
68
55
|
export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }: KanbanViewProps) {
|
|
69
56
|
const [kanbanData, setKanbanData] = useState<any>(null);
|
|
70
57
|
const [error, setError] = useState<string | null>(null);
|
|
71
58
|
const [draggedCard, setDraggedCard] = useState<string | null>(null);
|
|
72
59
|
const [dragOverColumn, setDragOverColumn] = useState<string | null>(null);
|
|
73
|
-
const [
|
|
60
|
+
const [workflowStatuses, setWorkflowStatuses] = useState<Record<string, WorkflowStatus>>({});
|
|
74
61
|
const [playAllRunning, setPlayAllRunning] = useState(false);
|
|
75
62
|
const [playAllCurrentFeature, setPlayAllCurrentFeature] = useState<string | null>(null);
|
|
63
|
+
const [playAllEpicId, setPlayAllEpicId] = useState<string | null>(null);
|
|
76
64
|
const [copiedId, setCopiedId] = useState<string | null>(null);
|
|
77
|
-
const [
|
|
65
|
+
const [availableWorkflows, setAvailableWorkflows] = useState<WorkflowListItem[]>([]);
|
|
66
|
+
const [dropdownOpen, setDropdownOpen] = useState<string | null>(null);
|
|
67
|
+
const [monitorTarget, setMonitorTarget] = useState<{ featureId: string; featureName: string } | null>(null);
|
|
68
|
+
const [backwardMove, setBackwardMove] = useState<{ featureId: string; featureName: string; fromColumn: string; toColumn: string } | null>(null);
|
|
78
69
|
const { showToast } = useToast();
|
|
79
70
|
|
|
80
71
|
const pollersRef = useRef<Map<string, ReturnType<typeof setInterval>>>(new Map());
|
|
81
72
|
const orchestratorPollerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
82
73
|
const kanbanDataRef = useRef(kanbanData);
|
|
83
74
|
kanbanDataRef.current = kanbanData;
|
|
75
|
+
const backwardMoveTargetRef = useRef<string | null>(null);
|
|
84
76
|
|
|
85
77
|
const fetchKanban = useCallback(async () => {
|
|
86
78
|
try {
|
|
@@ -94,10 +86,16 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
|
|
|
94
86
|
}
|
|
95
87
|
}, [projectId]);
|
|
96
88
|
|
|
89
|
+
// Load available workflows for the dropdown
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
apiClient.fetchWorkflows(projectId ?? undefined).then(data => {
|
|
92
|
+
setAvailableWorkflows(data.workflows || []);
|
|
93
|
+
}).catch(() => { /* ignore */ });
|
|
94
|
+
}, [projectId]);
|
|
95
|
+
|
|
97
96
|
useEffect(() => {
|
|
98
97
|
fetchKanban();
|
|
99
98
|
return () => {
|
|
100
|
-
// Cleanup all pollers
|
|
101
99
|
for (const intervalId of pollersRef.current.values()) {
|
|
102
100
|
clearInterval(intervalId);
|
|
103
101
|
}
|
|
@@ -111,18 +109,33 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
|
|
|
111
109
|
|
|
112
110
|
const getCardsForColumn = useCallback((columnId: string): CardData[] => {
|
|
113
111
|
if (!kanbanData || !graphData) return [];
|
|
114
|
-
const
|
|
112
|
+
const kanbanNodes = new Map<string, any>();
|
|
115
113
|
graphData.nodes
|
|
116
|
-
.filter((n: any) => n.type === 'feature')
|
|
117
|
-
.forEach((n: any) =>
|
|
114
|
+
.filter((n: any) => n.type === 'feature' || n.type === 'epic')
|
|
115
|
+
.forEach((n: any) => kanbanNodes.set(n.id, n));
|
|
116
|
+
|
|
117
|
+
// Build epic → children map from backend-provided epic_parent_id
|
|
118
|
+
const epicChildrenMap = new Map<string, Set<string>>();
|
|
119
|
+
for (const [id, entry] of Object.entries(kanbanData) as [string, any][]) {
|
|
120
|
+
if (entry.epic_parent_id) {
|
|
121
|
+
if (!epicChildrenMap.has(entry.epic_parent_id)) {
|
|
122
|
+
epicChildrenMap.set(entry.epic_parent_id, new Set());
|
|
123
|
+
}
|
|
124
|
+
epicChildrenMap.get(entry.epic_parent_id)!.add(id);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
118
127
|
|
|
119
128
|
const cards: CardData[] = Object.entries(kanbanData)
|
|
120
|
-
.filter(([id, entry]: [string, any]) => entry.column === columnId &&
|
|
129
|
+
.filter(([id, entry]: [string, any]) => entry.column === columnId && kanbanNodes.has(id))
|
|
121
130
|
.map(([id, entry]: [string, any]) => ({
|
|
122
131
|
id,
|
|
123
|
-
name:
|
|
124
|
-
completeness:
|
|
125
|
-
kind:
|
|
132
|
+
name: kanbanNodes.get(id).name,
|
|
133
|
+
completeness: kanbanNodes.get(id).completeness,
|
|
134
|
+
kind: kanbanNodes.get(id).kind || 'new',
|
|
135
|
+
nodeType: kanbanNodes.get(id).type || 'feature',
|
|
136
|
+
featureType: kanbanNodes.get(id).feature_type || null,
|
|
137
|
+
isEpicChild: false,
|
|
138
|
+
epicParentId: entry.epic_parent_id || null,
|
|
126
139
|
rejectionCount: entry.rejection_count || 0,
|
|
127
140
|
notes: entry.notes || [],
|
|
128
141
|
reviews: entry.reviews || [],
|
|
@@ -139,22 +152,45 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
|
|
|
139
152
|
cards.sort((a, b) => (a.movedAt || '').localeCompare(b.movedAt || ''));
|
|
140
153
|
}
|
|
141
154
|
|
|
142
|
-
|
|
155
|
+
// Sort-order-independent grouping: epics first, then their children, then ungrouped
|
|
156
|
+
const epicCards = cards.filter(c => c.nodeType === 'epic');
|
|
157
|
+
const childIds = new Set<string>();
|
|
158
|
+
const grouped: CardData[] = [];
|
|
159
|
+
|
|
160
|
+
for (const epic of epicCards) {
|
|
161
|
+
grouped.push(epic);
|
|
162
|
+
const children = epicChildrenMap.get(epic.id);
|
|
163
|
+
if (children) {
|
|
164
|
+
const childCards = cards.filter(c => children.has(c.id));
|
|
165
|
+
for (const child of childCards) {
|
|
166
|
+
child.isEpicChild = true;
|
|
167
|
+
grouped.push(child);
|
|
168
|
+
childIds.add(child.id);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Add ungrouped cards (not an epic and not an epic child)
|
|
174
|
+
for (const card of cards) {
|
|
175
|
+
if (card.nodeType !== 'epic' && !childIds.has(card.id)) {
|
|
176
|
+
grouped.push(card);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return grouped;
|
|
143
181
|
}, [kanbanData, graphData]);
|
|
144
182
|
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
if (rejectionCount >= 1) return `Retry ${rejectionCount + 1}`;
|
|
148
|
-
return label;
|
|
183
|
+
const getStatusBadgeText = (status: string) => {
|
|
184
|
+
return (WORKFLOW_STATUS_LABELS as any)[status] || status;
|
|
149
185
|
};
|
|
150
186
|
|
|
151
|
-
const
|
|
187
|
+
const startWorkflowPolling = useCallback((featureId: string) => {
|
|
152
188
|
if (pollersRef.current.has(featureId)) return;
|
|
153
189
|
const intervalId = setInterval(async () => {
|
|
154
190
|
try {
|
|
155
|
-
const status = await apiClient.
|
|
156
|
-
|
|
157
|
-
if (['idle', 'completed', '
|
|
191
|
+
const status: WorkflowStatus = await apiClient.getWorkflowStatus(featureId);
|
|
192
|
+
setWorkflowStatuses(prev => ({ ...prev, [featureId]: status }));
|
|
193
|
+
if (['idle', 'completed', 'failed'].includes(status.status)) {
|
|
158
194
|
clearInterval(pollersRef.current.get(featureId)!);
|
|
159
195
|
pollersRef.current.delete(featureId);
|
|
160
196
|
await fetchKanban();
|
|
@@ -167,35 +203,22 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
|
|
|
167
203
|
pollersRef.current.set(featureId, intervalId);
|
|
168
204
|
}, [fetchKanban]);
|
|
169
205
|
|
|
170
|
-
// Fetch initial
|
|
206
|
+
// Fetch initial workflow statuses for all cards
|
|
171
207
|
useEffect(() => {
|
|
172
208
|
if (!kanbanData || !graphData) return;
|
|
173
209
|
const featureIds = Object.keys(kanbanData);
|
|
174
210
|
featureIds.forEach(async (id) => {
|
|
175
211
|
try {
|
|
176
|
-
const status = await apiClient.
|
|
212
|
+
const status: WorkflowStatus = await apiClient.getWorkflowStatus(id);
|
|
177
213
|
if (status.status !== 'idle') {
|
|
178
|
-
|
|
179
|
-
if (!['completed', 'failed'
|
|
180
|
-
|
|
214
|
+
setWorkflowStatuses(prev => ({ ...prev, [id]: status }));
|
|
215
|
+
if (!['completed', 'failed'].includes(status.status)) {
|
|
216
|
+
startWorkflowPolling(id);
|
|
181
217
|
}
|
|
182
218
|
}
|
|
183
219
|
} catch { /* ignore */ }
|
|
184
220
|
});
|
|
185
|
-
}, [kanbanData, graphData,
|
|
186
|
-
|
|
187
|
-
const checkDependencies = useCallback((featureId: string) => {
|
|
188
|
-
if (!graphData || !kanbanData) return { blocked: false, blockedBy: [] as string[] };
|
|
189
|
-
const deps = graphData.edges
|
|
190
|
-
.filter((e: any) => e.from === featureId && e.relation === 'depends_on')
|
|
191
|
-
.map((e: any) => e.to)
|
|
192
|
-
.filter((depId: string) => graphData.nodes.some((n: any) => n.id === depId && n.type === 'feature'));
|
|
193
|
-
const blockedBy = deps.filter((depId: string) => {
|
|
194
|
-
const entry = kanbanData[depId];
|
|
195
|
-
return !entry || entry.column !== 'done';
|
|
196
|
-
});
|
|
197
|
-
return { blocked: blockedBy.length > 0, blockedBy };
|
|
198
|
-
}, [graphData, kanbanData]);
|
|
221
|
+
}, [kanbanData, graphData, startWorkflowPolling]);
|
|
199
222
|
|
|
200
223
|
// Drag handlers
|
|
201
224
|
const handleDragStart = (e: React.DragEvent, featureId: string) => {
|
|
@@ -229,6 +252,26 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
|
|
|
229
252
|
setDragOverColumn(null);
|
|
230
253
|
const featureId = e.dataTransfer.getData('text/plain');
|
|
231
254
|
if (!featureId || !targetColumn) return;
|
|
255
|
+
|
|
256
|
+
// Detect backward move — prompt for iteration comment
|
|
257
|
+
const currentEntry = kanbanData?.[featureId];
|
|
258
|
+
if (currentEntry) {
|
|
259
|
+
const colIds = COLUMNS.map(c => c.id);
|
|
260
|
+
const currentIdx = colIds.indexOf(currentEntry.column);
|
|
261
|
+
const targetIdx = colIds.indexOf(targetColumn);
|
|
262
|
+
if (targetIdx < currentIdx) {
|
|
263
|
+
// Find the card name from graphData
|
|
264
|
+
const node = graphData?.nodes?.find((n: any) => n.id === featureId);
|
|
265
|
+
const featureName = node?.name || featureId;
|
|
266
|
+
const fromLabel = COLUMNS.find(c => c.id === currentEntry.column)?.label || currentEntry.column;
|
|
267
|
+
const toLabel = COLUMNS.find(c => c.id === targetColumn)?.label || targetColumn;
|
|
268
|
+
setBackwardMove({ featureId, featureName, fromColumn: fromLabel, toColumn: toLabel });
|
|
269
|
+
// Store the target for the confirm handler
|
|
270
|
+
backwardMoveTargetRef.current = targetColumn;
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
232
275
|
try {
|
|
233
276
|
await apiClient.moveCard(featureId, targetColumn);
|
|
234
277
|
await fetchKanban();
|
|
@@ -237,34 +280,44 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
|
|
|
237
280
|
}
|
|
238
281
|
};
|
|
239
282
|
|
|
240
|
-
const
|
|
283
|
+
const handleBackwardMoveConfirm = async (comment: string) => {
|
|
284
|
+
if (!backwardMove) return;
|
|
285
|
+
const targetColumn = backwardMoveTargetRef.current;
|
|
286
|
+
backwardMoveTargetRef.current = null;
|
|
287
|
+
setBackwardMove(null);
|
|
288
|
+
if (!targetColumn) return;
|
|
241
289
|
try {
|
|
242
|
-
await apiClient.
|
|
243
|
-
startPipelinePolling(featureId);
|
|
290
|
+
await apiClient.moveCard(backwardMove.featureId, targetColumn, comment || undefined);
|
|
244
291
|
await fetchKanban();
|
|
245
|
-
} catch (err
|
|
246
|
-
console.error('Failed to
|
|
247
|
-
showToast(err?.message || 'Failed to start pipeline');
|
|
292
|
+
} catch (err) {
|
|
293
|
+
console.error('Failed to move card:', err);
|
|
248
294
|
}
|
|
249
295
|
};
|
|
250
296
|
|
|
251
|
-
const
|
|
297
|
+
const handleBackwardMoveCancel = () => {
|
|
298
|
+
backwardMoveTargetRef.current = null;
|
|
299
|
+
setBackwardMove(null);
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const handleStartWorkflow = async (featureId: string, workflowId?: string) => {
|
|
252
303
|
try {
|
|
253
|
-
await apiClient.
|
|
254
|
-
|
|
304
|
+
await apiClient.startWorkflow(featureId, workflowId, projectId ?? undefined);
|
|
305
|
+
startWorkflowPolling(featureId);
|
|
255
306
|
await fetchKanban();
|
|
256
307
|
} catch (err: any) {
|
|
257
|
-
console.error('Failed to
|
|
258
|
-
showToast(err?.message || 'Failed to
|
|
308
|
+
console.error('Failed to start workflow:', err);
|
|
309
|
+
showToast(err?.message || 'Failed to start workflow');
|
|
259
310
|
}
|
|
260
311
|
};
|
|
261
312
|
|
|
262
|
-
const
|
|
313
|
+
const handleResumeWorkflow = async (featureId: string) => {
|
|
263
314
|
try {
|
|
264
|
-
await apiClient.
|
|
315
|
+
await apiClient.resumeWorkflow(featureId);
|
|
316
|
+
startWorkflowPolling(featureId);
|
|
265
317
|
await fetchKanban();
|
|
266
|
-
} catch (err) {
|
|
267
|
-
console.error('Failed to
|
|
318
|
+
} catch (err: any) {
|
|
319
|
+
console.error('Failed to resume workflow:', err);
|
|
320
|
+
showToast(err?.message || 'Failed to resume workflow');
|
|
268
321
|
}
|
|
269
322
|
};
|
|
270
323
|
|
|
@@ -277,6 +330,18 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
|
|
|
277
330
|
} catch { /* ignore */ }
|
|
278
331
|
};
|
|
279
332
|
|
|
333
|
+
const handlePlayDropdown = (featureId: string) => {
|
|
334
|
+
setDropdownOpen(prev => prev === featureId ? null : featureId);
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// Close dropdown when clicking outside
|
|
338
|
+
useEffect(() => {
|
|
339
|
+
if (!dropdownOpen) return;
|
|
340
|
+
const handler = () => setDropdownOpen(null);
|
|
341
|
+
document.addEventListener('click', handler);
|
|
342
|
+
return () => document.removeEventListener('click', handler);
|
|
343
|
+
}, [dropdownOpen]);
|
|
344
|
+
|
|
280
345
|
// Orchestrator status polling
|
|
281
346
|
const startOrchestratorPolling = useCallback(() => {
|
|
282
347
|
if (orchestratorPollerRef.current) return;
|
|
@@ -285,14 +350,15 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
|
|
|
285
350
|
const status = await apiClient.getOrchestratorStatus();
|
|
286
351
|
setPlayAllRunning(status.active);
|
|
287
352
|
setPlayAllCurrentFeature(status.currentFeatureId);
|
|
353
|
+
setPlayAllEpicId(status.epicId || null);
|
|
288
354
|
if (status.active) {
|
|
289
355
|
await fetchKanban();
|
|
290
356
|
} else {
|
|
291
|
-
// Orchestrator stopped — clean up poller
|
|
292
357
|
if (orchestratorPollerRef.current) {
|
|
293
358
|
clearInterval(orchestratorPollerRef.current);
|
|
294
359
|
orchestratorPollerRef.current = null;
|
|
295
360
|
}
|
|
361
|
+
setPlayAllEpicId(null);
|
|
296
362
|
await fetchKanban();
|
|
297
363
|
}
|
|
298
364
|
} catch {
|
|
@@ -302,6 +368,7 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
|
|
|
302
368
|
}
|
|
303
369
|
setPlayAllRunning(false);
|
|
304
370
|
setPlayAllCurrentFeature(null);
|
|
371
|
+
setPlayAllEpicId(null);
|
|
305
372
|
}
|
|
306
373
|
}, 5000);
|
|
307
374
|
}, [fetchKanban]);
|
|
@@ -311,6 +378,7 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
|
|
|
311
378
|
apiClient.getOrchestratorStatus().then(status => {
|
|
312
379
|
setPlayAllRunning(status.active);
|
|
313
380
|
setPlayAllCurrentFeature(status.currentFeatureId);
|
|
381
|
+
setPlayAllEpicId(status.epicId || null);
|
|
314
382
|
if (status.active) {
|
|
315
383
|
startOrchestratorPolling();
|
|
316
384
|
}
|
|
@@ -325,10 +393,10 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
|
|
|
325
393
|
}, [startOrchestratorPolling]);
|
|
326
394
|
|
|
327
395
|
// Play All — thin wrapper around backend orchestrator
|
|
328
|
-
const startPlayAll = async () => {
|
|
396
|
+
const startPlayAll = async (epicId?: string) => {
|
|
329
397
|
if (playAllRunning) return;
|
|
330
398
|
try {
|
|
331
|
-
await apiClient.startPlayAll(projectId ?? undefined);
|
|
399
|
+
await apiClient.startPlayAll(projectId ?? undefined, epicId);
|
|
332
400
|
setPlayAllRunning(true);
|
|
333
401
|
setPlayAllCurrentFeature(null);
|
|
334
402
|
startOrchestratorPolling();
|
|
@@ -352,7 +420,6 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
|
|
|
352
420
|
<div className="flex h-full w-full gap-2 overflow-x-auto">
|
|
353
421
|
{COLUMNS.map(col => {
|
|
354
422
|
const cards = getCardsForColumn(col.id);
|
|
355
|
-
const sourceColumn = draggedCard ? kanbanData?.[draggedCard]?.column : null;
|
|
356
423
|
const isDragOver = dragOverColumn === col.id;
|
|
357
424
|
|
|
358
425
|
return (
|
|
@@ -376,7 +443,7 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
|
|
|
376
443
|
<button
|
|
377
444
|
className="flex h-6 w-6 items-center justify-center rounded-full border border-accent text-accent text-[9px] tracking-[-2px] pl-0.5 hover:bg-accent hover:text-surface transition-colors cursor-pointer"
|
|
378
445
|
title="Start automated development for all TODO features"
|
|
379
|
-
onClick={startPlayAll}
|
|
446
|
+
onClick={() => startPlayAll()}
|
|
380
447
|
>
|
|
381
448
|
{'\u25B6\u25B6'}
|
|
382
449
|
</button>
|
|
@@ -396,48 +463,12 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
|
|
|
396
463
|
>
|
|
397
464
|
{cards.map(card => {
|
|
398
465
|
const pct = Math.round((card.completeness || 0) * 100);
|
|
399
|
-
const
|
|
400
|
-
const
|
|
401
|
-
const
|
|
466
|
+
const wStatus = workflowStatuses[card.id];
|
|
467
|
+
const workflowStatusValue = wStatus?.status ?? 'idle';
|
|
468
|
+
const isActive = workflowStatusValue === 'running';
|
|
402
469
|
const showResumeBtn = ['in_progress', 'in_review'].includes(card.column)
|
|
403
470
|
&& !isActive
|
|
404
|
-
&&
|
|
405
|
-
|
|
406
|
-
// Build tool calls + meta from stage stats or flat toolCalls
|
|
407
|
-
let toolCalls: Record<string, number> | undefined;
|
|
408
|
-
let metaPills: string[] | undefined;
|
|
409
|
-
let stopReason: string | undefined;
|
|
410
|
-
|
|
411
|
-
const ss = pStatus?.stageStats;
|
|
412
|
-
const availableTabs = ss
|
|
413
|
-
? STAGE_TABS.filter(t => hasStageData(ss[t.key]))
|
|
414
|
-
: [];
|
|
415
|
-
|
|
416
|
-
if (availableTabs.length > 0) {
|
|
417
|
-
const currentTab = activeStageTab[card.id] || availableTabs[availableTabs.length - 1].key;
|
|
418
|
-
const stage = ss[currentTab];
|
|
419
|
-
const tc = stage?.toolCalls;
|
|
420
|
-
if (tc && tc.total > 0) {
|
|
421
|
-
toolCalls = {};
|
|
422
|
-
if (tc.read > 0) toolCalls['Read'] = tc.read;
|
|
423
|
-
if (tc.bash > 0) toolCalls['Tools'] = tc.bash;
|
|
424
|
-
if (tc.write > 0) toolCalls['Write'] = tc.write;
|
|
425
|
-
if (tc.edit > 0) toolCalls['Edit'] = tc.edit;
|
|
426
|
-
}
|
|
427
|
-
const pills: string[] = [];
|
|
428
|
-
if (stage?.lastTurnUsage) pills.push(`Ctx ${formatContextFill(stage.lastTurnUsage, stage.contextWindow)}`);
|
|
429
|
-
if (stage?.numTurns != null) pills.push(`${stage.numTurns}t`);
|
|
430
|
-
if (stage?.costUsd != null) pills.push(formatCost(stage.costUsd));
|
|
431
|
-
if (stage?.model) pills.push(formatModel(stage.model));
|
|
432
|
-
if (pills.length > 0) metaPills = pills;
|
|
433
|
-
if (stage?.stopReason) stopReason = stage.stopReason;
|
|
434
|
-
} else if (pStatus?.toolCalls?.total > 0) {
|
|
435
|
-
toolCalls = {};
|
|
436
|
-
if (pStatus.toolCalls.read > 0) toolCalls['Read'] = pStatus.toolCalls.read;
|
|
437
|
-
if (pStatus.toolCalls.bash > 0) toolCalls['Tools'] = pStatus.toolCalls.bash;
|
|
438
|
-
if (pStatus.toolCalls.write > 0) toolCalls['Write'] = pStatus.toolCalls.write;
|
|
439
|
-
if (pStatus.toolCalls.edit > 0) toolCalls['Edit'] = pStatus.toolCalls.edit;
|
|
440
|
-
}
|
|
471
|
+
&& workflowStatusValue === 'failed';
|
|
441
472
|
|
|
442
473
|
const issueLabel = card.notes.length > 0
|
|
443
474
|
? `${card.notes.length} issue${card.notes.length !== 1 ? 's' : ''} reported`
|
|
@@ -446,35 +477,81 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
|
|
|
446
477
|
: 'No issues';
|
|
447
478
|
|
|
448
479
|
return (
|
|
449
|
-
<
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
480
|
+
<div key={card.id} className={['relative', card.isEpicChild ? 'ml-3' : ''].join(' ')}>
|
|
481
|
+
<KanbanCard
|
|
482
|
+
id={card.id}
|
|
483
|
+
name={card.name}
|
|
484
|
+
pct={pct}
|
|
485
|
+
kind={card.kind}
|
|
486
|
+
isEpic={card.nodeType === 'epic'}
|
|
487
|
+
rejectionCount={card.rejectionCount}
|
|
488
|
+
blocked={card.devBlocked}
|
|
489
|
+
pipeline={workflowStatusValue}
|
|
490
|
+
pipelineLabel={wStatus && wStatus.status !== 'idle' ? getStatusBadgeText(wStatus.status) : undefined}
|
|
491
|
+
issueCount={card.notes.length}
|
|
492
|
+
issueLabel={issueLabel}
|
|
493
|
+
showPlay={(() => {
|
|
494
|
+
if (card.devBlocked) return false;
|
|
495
|
+
if (card.nodeType === 'epic' && playAllRunning && playAllEpicId === card.id) return false;
|
|
496
|
+
// Default column for non-video features
|
|
497
|
+
if (!card.featureType || card.featureType === 'code') return card.column === 'todo';
|
|
498
|
+
// For video features, show play when a workflow matches the column
|
|
499
|
+
const cardType = card.featureType;
|
|
500
|
+
return availableWorkflows.some(wf =>
|
|
501
|
+
(!wf.featureType || wf.featureType === cardType)
|
|
502
|
+
&& wf.triggerColumn === card.column
|
|
503
|
+
);
|
|
504
|
+
})()}
|
|
505
|
+
showPlayDropdown={card.nodeType !== 'epic' && availableWorkflows.length > 1}
|
|
506
|
+
showStop={card.nodeType === 'epic' && playAllRunning && playAllEpicId === card.id}
|
|
507
|
+
showResume={showResumeBtn}
|
|
508
|
+
copied={copiedId === card.id}
|
|
509
|
+
playAllActive={playAllCurrentFeature === card.id || (card.nodeType === 'epic' && playAllRunning && playAllEpicId === card.id)}
|
|
510
|
+
agentName={isActive ? wStatus?.currentAgentName : null}
|
|
511
|
+
errorMessage={workflowStatusValue === 'failed' ? wStatus?.error : null}
|
|
512
|
+
onClick={() => onCardClick({ id: card.id, name: card.name })}
|
|
513
|
+
onCopy={() => handleCopy(card)}
|
|
514
|
+
onPlay={() => card.nodeType === 'epic' ? startPlayAll(card.id) : handleStartWorkflow(card.id)}
|
|
515
|
+
onPlayDropdown={() => handlePlayDropdown(card.id)}
|
|
516
|
+
onStop={stopPlayAll}
|
|
517
|
+
onResume={() => handleResumeWorkflow(card.id)}
|
|
518
|
+
onIssuesClick={() => onIssuesClick(card.id, card.name, card.column, card.notes, card.reviews)}
|
|
519
|
+
onMonitorClick={() => setMonitorTarget({ featureId: card.id, featureName: card.name })}
|
|
520
|
+
draggable
|
|
521
|
+
onDragStart={(e) => handleDragStart(e, card.id)}
|
|
522
|
+
onDragEnd={handleDragEnd}
|
|
523
|
+
/>
|
|
524
|
+
{/* Workflow selection dropdown — filtered by feature type */}
|
|
525
|
+
{dropdownOpen === card.id && (() => {
|
|
526
|
+
const cardFeatureType = card.featureType || 'code';
|
|
527
|
+
const filteredWorkflows = availableWorkflows.filter(wf =>
|
|
528
|
+
(!wf.featureType || wf.featureType === cardFeatureType)
|
|
529
|
+
&& (!wf.triggerColumn || wf.triggerColumn === card.column)
|
|
530
|
+
);
|
|
531
|
+
return (
|
|
532
|
+
<div
|
|
533
|
+
className="absolute right-0 top-8 z-50 min-w-[180px] rounded-lg border border-edge bg-surface-raised shadow-xl shadow-black/20 py-1"
|
|
534
|
+
onClick={(e) => e.stopPropagation()}
|
|
535
|
+
>
|
|
536
|
+
{filteredWorkflows.map(wf => (
|
|
537
|
+
<button
|
|
538
|
+
key={wf.id}
|
|
539
|
+
className="flex w-full items-center gap-2 px-3 py-2 text-left text-[12px] text-content hover:bg-white/[0.08] transition-colors cursor-pointer"
|
|
540
|
+
onClick={() => {
|
|
541
|
+
setDropdownOpen(null);
|
|
542
|
+
handleStartWorkflow(card.id, wf.id);
|
|
543
|
+
}}
|
|
544
|
+
>
|
|
545
|
+
<span className="flex-1">{wf.name}</span>
|
|
546
|
+
{wf.isDefault === 1 && (
|
|
547
|
+
<span className="rounded-full bg-accent/15 px-1.5 py-0.5 text-[9px] font-bold text-accent">Default</span>
|
|
548
|
+
)}
|
|
549
|
+
</button>
|
|
550
|
+
))}
|
|
551
|
+
</div>
|
|
552
|
+
);
|
|
553
|
+
})()}
|
|
554
|
+
</div>
|
|
478
555
|
);
|
|
479
556
|
})}
|
|
480
557
|
</div>
|
|
@@ -482,6 +559,25 @@ export function KanbanView({ graphData, projectId, onCardClick, onIssuesClick }:
|
|
|
482
559
|
);
|
|
483
560
|
})}
|
|
484
561
|
|
|
562
|
+
{/* Workflow Monitor Modal */}
|
|
563
|
+
{monitorTarget && (
|
|
564
|
+
<WorkflowMonitorModal
|
|
565
|
+
featureId={monitorTarget.featureId}
|
|
566
|
+
featureName={monitorTarget.featureName}
|
|
567
|
+
isOpen={true}
|
|
568
|
+
onClose={() => setMonitorTarget(null)}
|
|
569
|
+
/>
|
|
570
|
+
)}
|
|
571
|
+
|
|
572
|
+
{/* Iteration Comment Modal — backward card moves */}
|
|
573
|
+
<IterationCommentModal
|
|
574
|
+
isOpen={!!backwardMove}
|
|
575
|
+
featureName={backwardMove?.featureName || ''}
|
|
576
|
+
fromColumn={backwardMove?.fromColumn || ''}
|
|
577
|
+
toColumn={backwardMove?.toColumn || ''}
|
|
578
|
+
onConfirm={handleBackwardMoveConfirm}
|
|
579
|
+
onCancel={handleBackwardMoveCancel}
|
|
580
|
+
/>
|
|
485
581
|
</div>
|
|
486
582
|
);
|
|
487
583
|
}
|