tlc-claude-code 1.2.26 → 1.2.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dashboard/dist/components/ActivityFeed.d.ts +17 -0
  2. package/dashboard/dist/components/ActivityFeed.js +42 -0
  3. package/dashboard/dist/components/ActivityFeed.test.d.ts +1 -0
  4. package/dashboard/dist/components/ActivityFeed.test.js +162 -0
  5. package/dashboard/dist/components/BranchSelector.d.ts +16 -0
  6. package/dashboard/dist/components/BranchSelector.js +49 -0
  7. package/dashboard/dist/components/BranchSelector.test.d.ts +1 -0
  8. package/dashboard/dist/components/BranchSelector.test.js +166 -0
  9. package/dashboard/dist/components/CommandPalette.d.ts +17 -0
  10. package/dashboard/dist/components/CommandPalette.js +118 -0
  11. package/dashboard/dist/components/CommandPalette.test.d.ts +1 -0
  12. package/dashboard/dist/components/CommandPalette.test.js +181 -0
  13. package/dashboard/dist/components/ConnectionStatus.d.ts +16 -0
  14. package/dashboard/dist/components/ConnectionStatus.js +27 -0
  15. package/dashboard/dist/components/ConnectionStatus.test.d.ts +1 -0
  16. package/dashboard/dist/components/ConnectionStatus.test.js +121 -0
  17. package/dashboard/dist/components/DeviceFrame.d.ts +19 -0
  18. package/dashboard/dist/components/DeviceFrame.js +52 -0
  19. package/dashboard/dist/components/DeviceFrame.test.d.ts +1 -0
  20. package/dashboard/dist/components/DeviceFrame.test.js +118 -0
  21. package/dashboard/dist/components/EnvironmentBadge.d.ts +11 -0
  22. package/dashboard/dist/components/EnvironmentBadge.js +16 -0
  23. package/dashboard/dist/components/EnvironmentBadge.test.d.ts +1 -0
  24. package/dashboard/dist/components/EnvironmentBadge.test.js +102 -0
  25. package/dashboard/dist/components/FocusIndicator.d.ts +19 -0
  26. package/dashboard/dist/components/FocusIndicator.js +47 -0
  27. package/dashboard/dist/components/FocusIndicator.test.d.ts +1 -0
  28. package/dashboard/dist/components/FocusIndicator.test.js +117 -0
  29. package/dashboard/dist/components/KeyboardHelp.d.ts +15 -0
  30. package/dashboard/dist/components/KeyboardHelp.js +61 -0
  31. package/dashboard/dist/components/KeyboardHelp.test.d.ts +1 -0
  32. package/dashboard/dist/components/KeyboardHelp.test.js +131 -0
  33. package/dashboard/dist/components/LogSearch.d.ts +13 -0
  34. package/dashboard/dist/components/LogSearch.js +43 -0
  35. package/dashboard/dist/components/LogSearch.test.d.ts +1 -0
  36. package/dashboard/dist/components/LogSearch.test.js +100 -0
  37. package/dashboard/dist/components/LogStream.d.ts +21 -0
  38. package/dashboard/dist/components/LogStream.js +123 -0
  39. package/dashboard/dist/components/LogStream.test.d.ts +1 -0
  40. package/dashboard/dist/components/LogStream.test.js +159 -0
  41. package/dashboard/dist/components/PlanView.d.ts +7 -0
  42. package/dashboard/dist/components/PlanView.js +74 -2
  43. package/dashboard/dist/components/PlanView.test.js +70 -1
  44. package/dashboard/dist/components/PreviewPanel.d.ts +18 -0
  45. package/dashboard/dist/components/PreviewPanel.js +73 -0
  46. package/dashboard/dist/components/PreviewPanel.test.d.ts +1 -0
  47. package/dashboard/dist/components/PreviewPanel.test.js +124 -0
  48. package/dashboard/dist/components/ProjectCard.d.ts +18 -0
  49. package/dashboard/dist/components/ProjectCard.js +19 -0
  50. package/dashboard/dist/components/ProjectCard.test.d.ts +1 -0
  51. package/dashboard/dist/components/ProjectCard.test.js +53 -0
  52. package/dashboard/dist/components/ProjectDetail.d.ts +44 -0
  53. package/dashboard/dist/components/ProjectDetail.js +65 -0
  54. package/dashboard/dist/components/ProjectDetail.test.d.ts +1 -0
  55. package/dashboard/dist/components/ProjectDetail.test.js +196 -0
  56. package/dashboard/dist/components/ProjectList.d.ts +11 -0
  57. package/dashboard/dist/components/ProjectList.js +62 -0
  58. package/dashboard/dist/components/ProjectList.test.d.ts +1 -0
  59. package/dashboard/dist/components/ProjectList.test.js +93 -0
  60. package/dashboard/dist/components/SettingsPanel.d.ts +32 -0
  61. package/dashboard/dist/components/SettingsPanel.js +154 -0
  62. package/dashboard/dist/components/SettingsPanel.test.d.ts +1 -0
  63. package/dashboard/dist/components/SettingsPanel.test.js +196 -0
  64. package/dashboard/dist/components/StatusBar.d.ts +16 -0
  65. package/dashboard/dist/components/StatusBar.js +47 -0
  66. package/dashboard/dist/components/StatusBar.test.d.ts +1 -0
  67. package/dashboard/dist/components/StatusBar.test.js +123 -0
  68. package/dashboard/dist/components/TaskBoard.d.ts +22 -0
  69. package/dashboard/dist/components/TaskBoard.js +102 -0
  70. package/dashboard/dist/components/TaskBoard.test.d.ts +1 -0
  71. package/dashboard/dist/components/TaskBoard.test.js +113 -0
  72. package/dashboard/dist/components/TaskCard.d.ts +17 -0
  73. package/dashboard/dist/components/TaskCard.js +29 -0
  74. package/dashboard/dist/components/TaskCard.test.d.ts +1 -0
  75. package/dashboard/dist/components/TaskCard.test.js +109 -0
  76. package/dashboard/dist/components/TaskDetail.d.ts +36 -0
  77. package/dashboard/dist/components/TaskDetail.js +41 -0
  78. package/dashboard/dist/components/TaskDetail.test.d.ts +1 -0
  79. package/dashboard/dist/components/TaskDetail.test.js +164 -0
  80. package/dashboard/dist/components/TaskFilter.d.ts +12 -0
  81. package/dashboard/dist/components/TaskFilter.js +138 -0
  82. package/dashboard/dist/components/TaskFilter.test.d.ts +1 -0
  83. package/dashboard/dist/components/TaskFilter.test.js +109 -0
  84. package/dashboard/dist/components/TeamPanel.d.ts +15 -0
  85. package/dashboard/dist/components/TeamPanel.js +24 -0
  86. package/dashboard/dist/components/TeamPanel.test.d.ts +1 -0
  87. package/dashboard/dist/components/TeamPanel.test.js +109 -0
  88. package/dashboard/dist/components/TeamPresence.d.ts +14 -0
  89. package/dashboard/dist/components/TeamPresence.js +31 -0
  90. package/dashboard/dist/components/TeamPresence.test.d.ts +1 -0
  91. package/dashboard/dist/components/TeamPresence.test.js +144 -0
  92. package/dashboard/dist/components/layout/Header.d.ts +9 -0
  93. package/dashboard/dist/components/layout/Header.js +11 -0
  94. package/dashboard/dist/components/layout/Header.test.d.ts +1 -0
  95. package/dashboard/dist/components/layout/Header.test.js +35 -0
  96. package/dashboard/dist/components/layout/Shell.d.ts +10 -0
  97. package/dashboard/dist/components/layout/Shell.js +5 -0
  98. package/dashboard/dist/components/layout/Shell.test.d.ts +1 -0
  99. package/dashboard/dist/components/layout/Shell.test.js +34 -0
  100. package/dashboard/dist/components/layout/Sidebar.d.ts +14 -0
  101. package/dashboard/dist/components/layout/Sidebar.js +8 -0
  102. package/dashboard/dist/components/layout/Sidebar.test.d.ts +1 -0
  103. package/dashboard/dist/components/layout/Sidebar.test.js +40 -0
  104. package/dashboard/dist/components/ui/Badge.d.ts +9 -0
  105. package/dashboard/dist/components/ui/Badge.js +13 -0
  106. package/dashboard/dist/components/ui/Badge.test.d.ts +1 -0
  107. package/dashboard/dist/components/ui/Badge.test.js +69 -0
  108. package/dashboard/dist/components/ui/Button.d.ts +12 -0
  109. package/dashboard/dist/components/ui/Button.js +14 -0
  110. package/dashboard/dist/components/ui/Button.test.d.ts +1 -0
  111. package/dashboard/dist/components/ui/Button.test.js +81 -0
  112. package/dashboard/dist/components/ui/Card.d.ts +21 -0
  113. package/dashboard/dist/components/ui/Card.js +20 -0
  114. package/dashboard/dist/components/ui/Card.test.d.ts +1 -0
  115. package/dashboard/dist/components/ui/Card.test.js +82 -0
  116. package/dashboard/dist/components/ui/Input.d.ts +13 -0
  117. package/dashboard/dist/components/ui/Input.js +8 -0
  118. package/dashboard/dist/components/ui/Input.test.d.ts +1 -0
  119. package/dashboard/dist/components/ui/Input.test.js +68 -0
  120. package/dashboard/dist/styles/tokens.d.ts +150 -0
  121. package/dashboard/dist/styles/tokens.js +184 -0
  122. package/dashboard/dist/styles/tokens.test.d.ts +1 -0
  123. package/dashboard/dist/styles/tokens.test.js +95 -0
  124. package/dashboard/dist/test/setup.d.ts +1 -0
  125. package/dashboard/dist/test/setup.js +1 -0
  126. package/dashboard/package.json +3 -0
  127. package/package.json +1 -1
  128. package/server/dashboard/index.html +157 -2
  129. package/server/index.js +38 -21
  130. package/server/lib/adapters/base-adapter.js +114 -0
  131. package/server/lib/adapters/base-adapter.test.js +90 -0
  132. package/server/lib/adapters/claude-adapter.js +141 -0
  133. package/server/lib/adapters/claude-adapter.test.js +180 -0
  134. package/server/lib/adapters/deepseek-adapter.js +153 -0
  135. package/server/lib/adapters/deepseek-adapter.test.js +193 -0
  136. package/server/lib/adapters/openai-adapter.js +190 -0
  137. package/server/lib/adapters/openai-adapter.test.js +231 -0
  138. package/server/lib/budget-tracker.js +169 -0
  139. package/server/lib/budget-tracker.test.js +165 -0
  140. package/server/lib/claude-injector.js +85 -0
  141. package/server/lib/claude-injector.test.js +161 -0
  142. package/server/lib/consensus-engine.js +135 -0
  143. package/server/lib/consensus-engine.test.js +152 -0
  144. package/server/lib/context-builder.js +112 -0
  145. package/server/lib/context-builder.test.js +120 -0
  146. package/server/lib/file-collector.js +322 -0
  147. package/server/lib/file-collector.test.js +307 -0
  148. package/server/lib/memory-classifier.js +175 -0
  149. package/server/lib/memory-classifier.test.js +169 -0
  150. package/server/lib/memory-committer.js +138 -0
  151. package/server/lib/memory-committer.test.js +136 -0
  152. package/server/lib/memory-hooks.js +127 -0
  153. package/server/lib/memory-hooks.test.js +136 -0
  154. package/server/lib/memory-init.js +104 -0
  155. package/server/lib/memory-init.test.js +119 -0
  156. package/server/lib/memory-observer.js +149 -0
  157. package/server/lib/memory-observer.test.js +158 -0
  158. package/server/lib/memory-reader.js +243 -0
  159. package/server/lib/memory-reader.test.js +216 -0
  160. package/server/lib/memory-storage.js +120 -0
  161. package/server/lib/memory-storage.test.js +136 -0
  162. package/server/lib/memory-writer.js +176 -0
  163. package/server/lib/memory-writer.test.js +231 -0
  164. package/server/lib/overdrive-command.js +30 -6
  165. package/server/lib/overdrive-command.test.js +8 -1
  166. package/server/lib/pattern-detector.js +216 -0
  167. package/server/lib/pattern-detector.test.js +241 -0
  168. package/server/lib/relevance-scorer.js +175 -0
  169. package/server/lib/relevance-scorer.test.js +107 -0
  170. package/server/lib/review-command.js +238 -0
  171. package/server/lib/review-command.test.js +245 -0
  172. package/server/lib/review-orchestrator.js +273 -0
  173. package/server/lib/review-orchestrator.test.js +300 -0
  174. package/server/lib/review-reporter.js +288 -0
  175. package/server/lib/review-reporter.test.js +240 -0
  176. package/server/lib/session-summary.js +90 -0
  177. package/server/lib/session-summary.test.js +156 -0
@@ -0,0 +1,123 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, it, expect } from 'vitest';
3
+ import { render } from 'ink-testing-library';
4
+ import { StatusBar } from './StatusBar.js';
5
+ describe('StatusBar', () => {
6
+ describe('Branch Display', () => {
7
+ it('shows current branch', () => {
8
+ const { lastFrame } = render(_jsx(StatusBar, { branch: "main" }));
9
+ expect(lastFrame()).toContain('main');
10
+ });
11
+ it('shows feature branch', () => {
12
+ const { lastFrame } = render(_jsx(StatusBar, { branch: "feature/auth" }));
13
+ expect(lastFrame()).toContain('feature/auth');
14
+ });
15
+ it('shows branch icon', () => {
16
+ const { lastFrame } = render(_jsx(StatusBar, { branch: "main" }));
17
+ expect(lastFrame()).toMatch(/⎇|branch|git/i);
18
+ });
19
+ });
20
+ describe('Environment Display', () => {
21
+ it('shows local environment', () => {
22
+ const { lastFrame } = render(_jsx(StatusBar, { environment: "local" }));
23
+ expect(lastFrame()).toContain('local');
24
+ });
25
+ it('shows VPS environment', () => {
26
+ const { lastFrame } = render(_jsx(StatusBar, { environment: "vps" }));
27
+ expect(lastFrame()).toContain('vps');
28
+ });
29
+ it('shows staging environment', () => {
30
+ const { lastFrame } = render(_jsx(StatusBar, { environment: "staging" }));
31
+ expect(lastFrame()).toContain('staging');
32
+ });
33
+ it('shows production with warning', () => {
34
+ const { lastFrame } = render(_jsx(StatusBar, { environment: "production" }));
35
+ expect(lastFrame()).toMatch(/prod|⚠/i);
36
+ });
37
+ });
38
+ describe('Connection Status', () => {
39
+ it('shows connected status', () => {
40
+ const { lastFrame } = render(_jsx(StatusBar, { connectionState: "connected" }));
41
+ expect(lastFrame()).toMatch(/●|connected/i);
42
+ });
43
+ it('shows disconnected status', () => {
44
+ const { lastFrame } = render(_jsx(StatusBar, { connectionState: "disconnected" }));
45
+ expect(lastFrame()).toMatch(/○|disconnected/i);
46
+ });
47
+ it('shows connecting status', () => {
48
+ const { lastFrame } = render(_jsx(StatusBar, { connectionState: "connecting" }));
49
+ expect(lastFrame()).toMatch(/◐|connecting/i);
50
+ });
51
+ });
52
+ describe('Keyboard Help Hint', () => {
53
+ it('shows help hint', () => {
54
+ const { lastFrame } = render(_jsx(StatusBar, {}));
55
+ expect(lastFrame()).toMatch(/\?|help/i);
56
+ });
57
+ });
58
+ describe('Compact Single-Line', () => {
59
+ it('fits on single line', () => {
60
+ const { lastFrame } = render(_jsx(StatusBar, { branch: "main", environment: "local", connectionState: "connected" }));
61
+ const output = lastFrame() || '';
62
+ const lines = output.split('\n').filter((l) => l.trim());
63
+ expect(lines.length).toBe(1);
64
+ });
65
+ it('uses separators between sections', () => {
66
+ const { lastFrame } = render(_jsx(StatusBar, { branch: "main", environment: "vps", connectionState: "connected" }));
67
+ expect(lastFrame()).toMatch(/│|•|\|/);
68
+ });
69
+ });
70
+ describe('Project Info', () => {
71
+ it('shows project name', () => {
72
+ const { lastFrame } = render(_jsx(StatusBar, { projectName: "my-app" }));
73
+ expect(lastFrame()).toContain('my-app');
74
+ });
75
+ it('shows version', () => {
76
+ const { lastFrame } = render(_jsx(StatusBar, { projectName: "app", version: "1.2.3" }));
77
+ expect(lastFrame()).toContain('1.2.3');
78
+ });
79
+ });
80
+ describe('Phase Info', () => {
81
+ it('shows current phase', () => {
82
+ const { lastFrame } = render(_jsx(StatusBar, { currentPhase: 3, totalPhases: 5 }));
83
+ expect(lastFrame()).toMatch(/3.*5|phase.*3/i);
84
+ });
85
+ });
86
+ describe('Test Status', () => {
87
+ it('shows passing tests', () => {
88
+ const { lastFrame } = render(_jsx(StatusBar, { testsPassing: 45, testsTotal: 50 }));
89
+ expect(lastFrame()).toMatch(/45.*50|tests/i);
90
+ });
91
+ it('shows failing tests indicator', () => {
92
+ const { lastFrame } = render(_jsx(StatusBar, { testsPassing: 40, testsTotal: 50, testsFailing: 10 }));
93
+ expect(lastFrame()).toMatch(/10|fail/i);
94
+ });
95
+ });
96
+ describe('Empty/Default State', () => {
97
+ it('renders with no props', () => {
98
+ const { lastFrame } = render(_jsx(StatusBar, {}));
99
+ expect(lastFrame()).toMatch(/\?|help/i);
100
+ });
101
+ it('handles missing branch gracefully', () => {
102
+ const { lastFrame } = render(_jsx(StatusBar, { environment: "local" }));
103
+ expect(lastFrame()).toContain('local');
104
+ });
105
+ it('handles missing environment gracefully', () => {
106
+ const { lastFrame } = render(_jsx(StatusBar, { branch: "main" }));
107
+ expect(lastFrame()).toContain('main');
108
+ });
109
+ });
110
+ describe('Colors', () => {
111
+ it('uses appropriate colors for connection states', () => {
112
+ const { lastFrame } = render(_jsx(StatusBar, { connectionState: "connected" }));
113
+ // Visual verification - renders without error
114
+ expect(lastFrame()).toBeDefined();
115
+ });
116
+ });
117
+ describe('Width', () => {
118
+ it('respects width prop', () => {
119
+ const { lastFrame } = render(_jsx(StatusBar, { width: 80, branch: "main" }));
120
+ expect(lastFrame()).toBeDefined();
121
+ });
122
+ });
123
+ });
@@ -0,0 +1,22 @@
1
+ import { TaskStatus, TaskPriority } from './TaskCard.js';
2
+ export interface Task {
3
+ id: string;
4
+ title: string;
5
+ status: TaskStatus;
6
+ priority?: TaskPriority;
7
+ assignee?: string;
8
+ description?: string;
9
+ tests?: {
10
+ passing: number;
11
+ failing: number;
12
+ };
13
+ }
14
+ export interface TaskBoardProps {
15
+ tasks: Task[];
16
+ initialColumn?: number;
17
+ initialRow?: number;
18
+ compact?: boolean;
19
+ onSelect?: (task: Task) => void;
20
+ onMove?: (task: Task, newStatus: TaskStatus) => void;
21
+ }
22
+ export declare function TaskBoard({ tasks, initialColumn, initialRow, compact, onSelect, onMove, }: TaskBoardProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,102 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useMemo } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import { TaskCard } from './TaskCard.js';
5
+ const columns = [
6
+ { key: 'pending', label: 'Pending' },
7
+ { key: 'in_progress', label: 'In Progress' },
8
+ { key: 'completed', label: 'Completed' },
9
+ ];
10
+ const priorityOrder = {
11
+ high: 0,
12
+ medium: 1,
13
+ low: 2,
14
+ };
15
+ export function TaskBoard({ tasks, initialColumn = 0, initialRow = 0, compact = false, onSelect, onMove, }) {
16
+ const [activeColumn, setActiveColumn] = useState(initialColumn);
17
+ const [activeRow, setActiveRow] = useState(initialRow);
18
+ const [moveMode, setMoveMode] = useState(false);
19
+ // Group and sort tasks by status
20
+ const tasksByColumn = useMemo(() => {
21
+ const grouped = {
22
+ pending: [],
23
+ in_progress: [],
24
+ completed: [],
25
+ };
26
+ for (const task of tasks) {
27
+ grouped[task.status].push(task);
28
+ }
29
+ // Sort by priority within each column
30
+ for (const status of Object.keys(grouped)) {
31
+ grouped[status].sort((a, b) => {
32
+ const aPriority = a.priority ? priorityOrder[a.priority] : 999;
33
+ const bPriority = b.priority ? priorityOrder[b.priority] : 999;
34
+ return aPriority - bPriority;
35
+ });
36
+ }
37
+ return grouped;
38
+ }, [tasks]);
39
+ // Get current column tasks
40
+ const currentColumnTasks = tasksByColumn[columns[activeColumn].key];
41
+ const currentTask = currentColumnTasks[activeRow];
42
+ // Handle keyboard input
43
+ useInput((input, key) => {
44
+ if (tasks.length === 0)
45
+ return;
46
+ // Move mode: select target column
47
+ if (moveMode) {
48
+ if (input === '1') {
49
+ handleMove('pending');
50
+ }
51
+ else if (input === '2') {
52
+ handleMove('in_progress');
53
+ }
54
+ else if (input === '3') {
55
+ handleMove('completed');
56
+ }
57
+ else if (key.escape) {
58
+ setMoveMode(false);
59
+ }
60
+ return;
61
+ }
62
+ // Horizontal navigation (columns)
63
+ if (key.rightArrow || input === 'l') {
64
+ setActiveColumn((prev) => Math.min(prev + 1, columns.length - 1));
65
+ setActiveRow(0); // Reset row when changing column
66
+ }
67
+ else if (key.leftArrow || input === 'h') {
68
+ setActiveColumn((prev) => Math.max(prev - 1, 0));
69
+ setActiveRow(0);
70
+ }
71
+ // Vertical navigation (tasks within column)
72
+ else if (key.downArrow || input === 'j') {
73
+ setActiveRow((prev) => Math.min(prev + 1, currentColumnTasks.length - 1));
74
+ }
75
+ else if (key.upArrow || input === 'k') {
76
+ setActiveRow((prev) => Math.max(prev - 1, 0));
77
+ }
78
+ // Select task
79
+ else if (key.return && currentTask && onSelect) {
80
+ onSelect(currentTask);
81
+ }
82
+ // Enter move mode
83
+ else if (input === 'm' && currentTask) {
84
+ setMoveMode(true);
85
+ }
86
+ });
87
+ const handleMove = (newStatus) => {
88
+ if (currentTask && onMove) {
89
+ onMove(currentTask, newStatus);
90
+ }
91
+ setMoveMode(false);
92
+ };
93
+ // Empty state
94
+ if (tasks.length === 0) {
95
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { dimColor: true, children: "No tasks found" }), _jsx(Text, { dimColor: true, children: "Run /tlc:plan to create tasks" })] }));
96
+ }
97
+ return (_jsxs(Box, { flexDirection: "column", children: [moveMode && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: "yellow", children: ["Move \"", currentTask?.title, "\" to: [1] Pending [2] In Progress [3] Completed"] }) })), _jsx(Box, { children: columns.map((column, colIndex) => {
98
+ const columnTasks = tasksByColumn[column.key];
99
+ const isActiveColumn = colIndex === activeColumn;
100
+ return (_jsxs(Box, { flexDirection: "column", width: "33%", marginRight: 1, borderStyle: isActiveColumn ? 'double' : 'single', borderColor: isActiveColumn ? 'cyan' : 'gray', children: [_jsxs(Box, { marginBottom: 1, paddingX: 1, children: [_jsx(Text, { bold: true, color: isActiveColumn ? 'cyan' : 'white', children: column.label }), _jsxs(Text, { dimColor: true, children: [" (", columnTasks.length, ")"] })] }), _jsx(Box, { flexDirection: "column", paddingX: 1, children: columnTasks.length === 0 ? (_jsx(Text, { dimColor: true, children: "- empty -" })) : (columnTasks.map((task, rowIndex) => (_jsx(Box, { marginBottom: compact ? 0 : 1, children: _jsx(TaskCard, { ...task, isSelected: isActiveColumn && rowIndex === activeRow, compact: compact }) }, task.id)))) })] }, column.key));
101
+ }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "h/l \u2190/\u2192 columns \u2022 j/k \u2191/\u2193 tasks \u2022 Enter select \u2022 m move" }) })] }));
102
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,113 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, it, expect, vi } from 'vitest';
3
+ import { render } from 'ink-testing-library';
4
+ import { TaskBoard } from './TaskBoard.js';
5
+ const sampleTasks = [
6
+ { id: 't1', title: 'Create schema', status: 'completed', priority: 'high' },
7
+ { id: 't2', title: 'Add validation', status: 'in_progress', assignee: 'alice' },
8
+ { id: 't3', title: 'Write tests', status: 'pending', priority: 'medium' },
9
+ { id: 't4', title: 'Add docs', status: 'pending', priority: 'low' },
10
+ { id: 't5', title: 'Review code', status: 'in_progress', assignee: 'bob' },
11
+ ];
12
+ describe('TaskBoard', () => {
13
+ describe('Columns', () => {
14
+ it('shows three columns', () => {
15
+ const { lastFrame } = render(_jsx(TaskBoard, { tasks: sampleTasks }));
16
+ expect(lastFrame()).toContain('Pending');
17
+ expect(lastFrame()).toContain('In Progress');
18
+ expect(lastFrame()).toContain('Completed');
19
+ });
20
+ it('shows task count in column headers', () => {
21
+ const { lastFrame } = render(_jsx(TaskBoard, { tasks: sampleTasks }));
22
+ // 2 pending, 2 in progress, 1 completed
23
+ expect(lastFrame()).toContain('2');
24
+ expect(lastFrame()).toContain('1');
25
+ });
26
+ it('shows tasks in correct columns', () => {
27
+ const { lastFrame } = render(_jsx(TaskBoard, { tasks: sampleTasks }));
28
+ expect(lastFrame()).toContain('Create schema');
29
+ expect(lastFrame()).toContain('Add validation');
30
+ expect(lastFrame()).toContain('Write tests');
31
+ });
32
+ });
33
+ describe('Empty State', () => {
34
+ it('shows empty message when no tasks', () => {
35
+ const { lastFrame } = render(_jsx(TaskBoard, { tasks: [] }));
36
+ expect(lastFrame()).toContain('No tasks');
37
+ });
38
+ it('shows empty column indicator', () => {
39
+ const oneTask = [
40
+ { id: 't1', title: 'Test', status: 'pending' },
41
+ ];
42
+ const { lastFrame } = render(_jsx(TaskBoard, { tasks: oneTask }));
43
+ // In Progress and Completed should be empty
44
+ expect(lastFrame()).toMatch(/empty|none|-/i);
45
+ });
46
+ });
47
+ describe('Selection', () => {
48
+ it('first task is selected by default', () => {
49
+ const { lastFrame } = render(_jsx(TaskBoard, { tasks: sampleTasks }));
50
+ expect(lastFrame()).toContain('▶');
51
+ });
52
+ it('accepts initialColumn prop', () => {
53
+ const { lastFrame } = render(_jsx(TaskBoard, { tasks: sampleTasks, initialColumn: 1 }));
54
+ // Column 1 is "In Progress"
55
+ expect(lastFrame()).toContain('▶');
56
+ });
57
+ it('accepts initialRow prop', () => {
58
+ const { lastFrame } = render(_jsx(TaskBoard, { tasks: sampleTasks, initialRow: 1 }));
59
+ expect(lastFrame()).toContain('▶');
60
+ });
61
+ });
62
+ describe('Navigation Hints', () => {
63
+ it('shows horizontal navigation hint', () => {
64
+ const { lastFrame } = render(_jsx(TaskBoard, { tasks: sampleTasks }));
65
+ expect(lastFrame()).toMatch(/h\/l|←\/→/);
66
+ });
67
+ it('shows vertical navigation hint', () => {
68
+ const { lastFrame } = render(_jsx(TaskBoard, { tasks: sampleTasks }));
69
+ expect(lastFrame()).toMatch(/j\/k|↑\/↓/);
70
+ });
71
+ it('shows move task hint', () => {
72
+ const { lastFrame } = render(_jsx(TaskBoard, { tasks: sampleTasks }));
73
+ expect(lastFrame()).toMatch(/m|move/i);
74
+ });
75
+ });
76
+ describe('Callbacks', () => {
77
+ it('calls onSelect when task selected', () => {
78
+ const onSelect = vi.fn();
79
+ render(_jsx(TaskBoard, { tasks: sampleTasks, onSelect: onSelect }));
80
+ // Selection happens on Enter - verified by callback presence
81
+ expect(onSelect).not.toHaveBeenCalled();
82
+ });
83
+ it('calls onMove when task moved', () => {
84
+ const onMove = vi.fn();
85
+ render(_jsx(TaskBoard, { tasks: sampleTasks, onMove: onMove }));
86
+ // Move happens on 'm' key - verified by callback presence
87
+ expect(onMove).not.toHaveBeenCalled();
88
+ });
89
+ });
90
+ describe('Compact Mode', () => {
91
+ it('supports compact display', () => {
92
+ const { lastFrame } = render(_jsx(TaskBoard, { tasks: sampleTasks, compact: true }));
93
+ expect(lastFrame()).toContain('Pending');
94
+ });
95
+ });
96
+ describe('Column Highlighting', () => {
97
+ it('highlights active column', () => {
98
+ const { lastFrame } = render(_jsx(TaskBoard, { tasks: sampleTasks }));
99
+ // Active column should have visual distinction
100
+ expect(lastFrame()).toContain('Pending');
101
+ });
102
+ });
103
+ describe('Priority Sorting', () => {
104
+ it('sorts tasks by priority within column', () => {
105
+ const { lastFrame } = render(_jsx(TaskBoard, { tasks: sampleTasks }));
106
+ const output = lastFrame() || '';
107
+ // High priority 'Write tests' should appear before low priority 'Add docs'
108
+ const writeIndex = output.indexOf('Write tests');
109
+ const addIndex = output.indexOf('Add docs');
110
+ expect(writeIndex).toBeLessThan(addIndex);
111
+ });
112
+ });
113
+ });
@@ -0,0 +1,17 @@
1
+ export type TaskStatus = 'pending' | 'in_progress' | 'completed';
2
+ export type TaskPriority = 'high' | 'medium' | 'low';
3
+ export interface TaskCardProps {
4
+ id: string;
5
+ title: string;
6
+ status: TaskStatus;
7
+ priority?: TaskPriority;
8
+ assignee?: string;
9
+ description?: string;
10
+ tests?: {
11
+ passing: number;
12
+ failing: number;
13
+ };
14
+ isSelected?: boolean;
15
+ compact?: boolean;
16
+ }
17
+ export declare function TaskCard({ title, status, priority, assignee, description, tests, isSelected, compact, }: TaskCardProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { Badge } from './ui/Badge.js';
4
+ const priorityColors = {
5
+ high: 'red',
6
+ medium: 'yellow',
7
+ low: 'green',
8
+ };
9
+ const priorityLabels = {
10
+ high: '! high',
11
+ medium: 'medium',
12
+ low: 'low',
13
+ };
14
+ const statusIndicators = {
15
+ pending: { symbol: '○', color: 'gray' },
16
+ in_progress: { symbol: '◐', color: 'yellow' },
17
+ completed: { symbol: '✓', color: 'green' },
18
+ };
19
+ const statusLabels = {
20
+ pending: 'pending',
21
+ in_progress: 'in progress',
22
+ completed: 'completed',
23
+ };
24
+ export function TaskCard({ title, status, priority, assignee, description, tests, isSelected = false, compact = false, }) {
25
+ const statusInfo = statusIndicators[status];
26
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? 'cyan' : undefined, children: isSelected ? '▶ ' : ' ' }), _jsxs(Text, { color: statusInfo.color, children: [statusInfo.symbol, " "] }), _jsx(Text, { bold: isSelected, color: isSelected ? 'cyan' : 'white', children: title }), priority && (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: priorityColors[priority], children: ["[", priorityLabels[priority], "]"] }) })), assignee && (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { dimColor: true, children: ["@", assignee] }) }))] }), description && !compact && (_jsx(Box, { marginLeft: 4, children: _jsx(Text, { dimColor: true, wrap: "truncate-end", children: description }) })), description && compact && (_jsx(Box, { marginLeft: 4, children: _jsx(Text, { dimColor: true, children: description.length > 40
27
+ ? description.substring(0, 40) + '...'
28
+ : description }) })), _jsxs(Box, { marginLeft: 4, children: [_jsx(Text, { dimColor: true, children: statusLabels[status] }), tests && (_jsx(Box, { marginLeft: 1, children: tests.failing > 0 ? (_jsxs(Badge, { variant: "error", size: "sm", children: [tests.failing, " fail"] })) : (_jsxs(Badge, { variant: "success", size: "sm", children: [tests.passing, " \u2713"] })) }))] })] }));
29
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,109 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, it, expect } from 'vitest';
3
+ import { render } from 'ink-testing-library';
4
+ import { TaskCard } from './TaskCard.js';
5
+ const sampleTask = {
6
+ id: 't1',
7
+ title: 'Create user schema',
8
+ status: 'pending',
9
+ priority: 'high',
10
+ };
11
+ describe('TaskCard', () => {
12
+ describe('Basic Display', () => {
13
+ it('renders task title', () => {
14
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask }));
15
+ expect(lastFrame()).toContain('Create user schema');
16
+ });
17
+ it('renders task status', () => {
18
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask }));
19
+ expect(lastFrame()).toContain('pending');
20
+ });
21
+ it('renders in progress status', () => {
22
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask, status: "in_progress" }));
23
+ expect(lastFrame()).toMatch(/in.progress|working/i);
24
+ });
25
+ it('renders completed status', () => {
26
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask, status: "completed" }));
27
+ expect(lastFrame()).toMatch(/completed|done|✓/i);
28
+ });
29
+ });
30
+ describe('Priority', () => {
31
+ it('shows high priority indicator', () => {
32
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask, priority: "high" }));
33
+ expect(lastFrame()).toMatch(/high|🔴|!/i);
34
+ });
35
+ it('shows medium priority indicator', () => {
36
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask, priority: "medium" }));
37
+ expect(lastFrame()).toMatch(/medium|🟡/i);
38
+ });
39
+ it('shows low priority indicator', () => {
40
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask, priority: "low" }));
41
+ expect(lastFrame()).toMatch(/low|🟢/i);
42
+ });
43
+ it('handles no priority', () => {
44
+ const { lastFrame } = render(_jsx(TaskCard, { id: "t1", title: "Test", status: "pending" }));
45
+ expect(lastFrame()).toContain('Test');
46
+ });
47
+ });
48
+ describe('Assignee', () => {
49
+ it('shows assignee when present', () => {
50
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask, assignee: "alice" }));
51
+ expect(lastFrame()).toContain('alice');
52
+ });
53
+ it('shows @ prefix for assignee', () => {
54
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask, assignee: "bob" }));
55
+ expect(lastFrame()).toContain('@bob');
56
+ });
57
+ it('handles no assignee', () => {
58
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask }));
59
+ expect(lastFrame()).not.toContain('@');
60
+ });
61
+ });
62
+ describe('Test Status', () => {
63
+ it('shows passing tests badge', () => {
64
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask, tests: { passing: 10, failing: 0 } }));
65
+ expect(lastFrame()).toMatch(/10.*(pass|✓)/i);
66
+ });
67
+ it('shows failing tests badge', () => {
68
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask, tests: { passing: 8, failing: 2 } }));
69
+ expect(lastFrame()).toMatch(/2.*(fail|✗)/i);
70
+ });
71
+ it('handles no tests', () => {
72
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask }));
73
+ expect(lastFrame()).toContain('Create user schema');
74
+ });
75
+ });
76
+ describe('Selection', () => {
77
+ it('shows selection indicator when selected', () => {
78
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask, isSelected: true }));
79
+ expect(lastFrame()).toContain('▶');
80
+ });
81
+ it('hides selection indicator when not selected', () => {
82
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask, isSelected: false }));
83
+ expect(lastFrame()).not.toContain('▶');
84
+ });
85
+ });
86
+ describe('Display Modes', () => {
87
+ it('renders compact mode', () => {
88
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask, compact: true }));
89
+ expect(lastFrame()).toContain('Create user schema');
90
+ });
91
+ it('renders full mode by default', () => {
92
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask }));
93
+ expect(lastFrame()).toContain('Create user schema');
94
+ });
95
+ });
96
+ describe('Description', () => {
97
+ it('shows description when present', () => {
98
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask, description: "Define the database schema" }));
99
+ expect(lastFrame()).toContain('Define the database schema');
100
+ });
101
+ it('truncates long description in compact mode', () => {
102
+ const longDesc = 'A'.repeat(100);
103
+ const { lastFrame } = render(_jsx(TaskCard, { ...sampleTask, description: longDesc, compact: true }));
104
+ // Should not show full description in compact mode
105
+ const output = lastFrame() || '';
106
+ expect(output.length).toBeLessThan(150);
107
+ });
108
+ });
109
+ });
@@ -0,0 +1,36 @@
1
+ import { TaskStatus, TaskPriority } from './TaskCard.js';
2
+ export interface Activity {
3
+ id: string;
4
+ type: 'status_change' | 'comment' | 'test_run' | 'file_change';
5
+ timestamp: string;
6
+ user?: string;
7
+ detail: string;
8
+ }
9
+ export interface AcceptanceCriterion {
10
+ id: string;
11
+ text: string;
12
+ done: boolean;
13
+ }
14
+ export interface TaskDetailData {
15
+ id: string;
16
+ title: string;
17
+ status: TaskStatus;
18
+ priority?: TaskPriority;
19
+ assignee?: string;
20
+ description?: string;
21
+ tests?: {
22
+ passing: number;
23
+ failing: number;
24
+ };
25
+ activity: Activity[];
26
+ files: string[];
27
+ acceptanceCriteria: AcceptanceCriterion[];
28
+ }
29
+ export interface TaskDetailProps {
30
+ task: TaskDetailData;
31
+ onBack?: () => void;
32
+ onClaim?: () => void;
33
+ onRelease?: () => void;
34
+ onStatusChange?: (newStatus: TaskStatus) => void;
35
+ }
36
+ export declare function TaskDetail({ task, onBack, onClaim, onRelease, onStatusChange, }: TaskDetailProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text, useInput } from 'ink';
3
+ import { Card } from './ui/Card.js';
4
+ import { Badge } from './ui/Badge.js';
5
+ const statusLabels = {
6
+ pending: 'pending',
7
+ in_progress: 'in progress',
8
+ completed: 'completed',
9
+ };
10
+ const priorityColors = {
11
+ high: 'red',
12
+ medium: 'yellow',
13
+ low: 'green',
14
+ };
15
+ export function TaskDetail({ task, onBack, onClaim, onRelease, onStatusChange, }) {
16
+ useInput((input, key) => {
17
+ if (key.escape && onBack) {
18
+ onBack();
19
+ }
20
+ else if (input === 'c' && onClaim && !task.assignee) {
21
+ onClaim();
22
+ }
23
+ else if (input === 'r' && onRelease && task.assignee) {
24
+ onRelease();
25
+ }
26
+ else if (input === 'd' && onStatusChange && task.status !== 'completed') {
27
+ onStatusChange('completed');
28
+ }
29
+ else if (input === 's' && onStatusChange && task.status === 'pending') {
30
+ onStatusChange('in_progress');
31
+ }
32
+ });
33
+ const completedCriteria = task.acceptanceCriteria.filter((c) => c.done).length;
34
+ const totalCriteria = task.acceptanceCriteria.length;
35
+ const totalTests = task.tests ? task.tests.passing + task.tests.failing : 0;
36
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: task.title }) }), _jsxs(Box, { marginBottom: 1, children: [_jsx(Badge, { variant: task.status === 'completed'
37
+ ? 'success'
38
+ : task.status === 'in_progress'
39
+ ? 'warning'
40
+ : 'neutral', children: statusLabels[task.status] }), task.priority && (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: priorityColors[task.priority], children: ["[", task.priority, "]"] }) })), task.assignee && (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { dimColor: true, children: ["@", task.assignee] }) }))] }), task.description && (_jsx(Card, { variant: "outlined", children: _jsx(Text, { children: task.description }) })), task.acceptanceCriteria.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Acceptance Criteria " }), _jsxs(Text, { dimColor: true, children: ["(", completedCriteria, "/", totalCriteria, ")"] })] }), task.acceptanceCriteria.map((criterion) => (_jsxs(Box, { children: [_jsx(Text, { color: criterion.done ? 'green' : 'gray', children: criterion.done ? '[x] ' : '[ ] ' }), _jsx(Text, { color: criterion.done ? 'green' : undefined, children: criterion.text })] }, criterion.id)))] })), task.tests && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { bold: true, children: "Tests: " }), _jsxs(Badge, { variant: task.tests.failing > 0 ? 'error' : 'success', children: [task.tests.passing, "/", totalTests] }), task.tests.failing > 0 && (_jsx(Box, { marginLeft: 1, children: _jsxs(Text, { color: "red", children: [task.tests.failing, " \u2717 failing"] }) }))] })), task.files.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Files" }) }), task.files.map((file, i) => (_jsx(Box, { children: _jsxs(Text, { dimColor: true, children: ["\u2022 ", file] }) }, i)))] })), task.activity.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Activity" }) }), task.activity.map((entry) => (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: entry.timestamp }), entry.user && _jsxs(Text, { color: "cyan", children: [" @", entry.user] }), _jsxs(Text, { children: [": ", entry.detail] })] }, entry.id)))] })), _jsxs(Box, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { dimColor: true, children: "Actions: " }), !task.assignee ? (_jsx(Text, { color: "green", children: "[c]laim " })) : (_jsx(Text, { color: "yellow", children: "[r]elease " })), task.status === 'pending' && _jsx(Text, { children: "[s]tart " }), task.status !== 'completed' && _jsx(Text, { children: "[d]one " }), _jsx(Text, { dimColor: true, children: "| Esc back" })] })] }));
41
+ }
@@ -0,0 +1 @@
1
+ export {};