tlc-claude-code 1.2.27 → 1.2.29

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 (179) hide show
  1. package/README.md +9 -4
  2. package/dashboard/dist/components/ActivityFeed.d.ts +17 -0
  3. package/dashboard/dist/components/ActivityFeed.js +42 -0
  4. package/dashboard/dist/components/ActivityFeed.test.d.ts +1 -0
  5. package/dashboard/dist/components/ActivityFeed.test.js +162 -0
  6. package/dashboard/dist/components/BranchSelector.d.ts +16 -0
  7. package/dashboard/dist/components/BranchSelector.js +49 -0
  8. package/dashboard/dist/components/BranchSelector.test.d.ts +1 -0
  9. package/dashboard/dist/components/BranchSelector.test.js +166 -0
  10. package/dashboard/dist/components/CommandPalette.d.ts +17 -0
  11. package/dashboard/dist/components/CommandPalette.js +118 -0
  12. package/dashboard/dist/components/CommandPalette.test.d.ts +1 -0
  13. package/dashboard/dist/components/CommandPalette.test.js +181 -0
  14. package/dashboard/dist/components/ConnectionStatus.d.ts +16 -0
  15. package/dashboard/dist/components/ConnectionStatus.js +27 -0
  16. package/dashboard/dist/components/ConnectionStatus.test.d.ts +1 -0
  17. package/dashboard/dist/components/ConnectionStatus.test.js +121 -0
  18. package/dashboard/dist/components/DeviceFrame.d.ts +19 -0
  19. package/dashboard/dist/components/DeviceFrame.js +52 -0
  20. package/dashboard/dist/components/DeviceFrame.test.d.ts +1 -0
  21. package/dashboard/dist/components/DeviceFrame.test.js +118 -0
  22. package/dashboard/dist/components/EnvironmentBadge.d.ts +11 -0
  23. package/dashboard/dist/components/EnvironmentBadge.js +16 -0
  24. package/dashboard/dist/components/EnvironmentBadge.test.d.ts +1 -0
  25. package/dashboard/dist/components/EnvironmentBadge.test.js +102 -0
  26. package/dashboard/dist/components/FocusIndicator.d.ts +19 -0
  27. package/dashboard/dist/components/FocusIndicator.js +47 -0
  28. package/dashboard/dist/components/FocusIndicator.test.d.ts +1 -0
  29. package/dashboard/dist/components/FocusIndicator.test.js +117 -0
  30. package/dashboard/dist/components/KeyboardHelp.d.ts +15 -0
  31. package/dashboard/dist/components/KeyboardHelp.js +61 -0
  32. package/dashboard/dist/components/KeyboardHelp.test.d.ts +1 -0
  33. package/dashboard/dist/components/KeyboardHelp.test.js +131 -0
  34. package/dashboard/dist/components/LogSearch.d.ts +13 -0
  35. package/dashboard/dist/components/LogSearch.js +43 -0
  36. package/dashboard/dist/components/LogSearch.test.d.ts +1 -0
  37. package/dashboard/dist/components/LogSearch.test.js +100 -0
  38. package/dashboard/dist/components/LogStream.d.ts +21 -0
  39. package/dashboard/dist/components/LogStream.js +123 -0
  40. package/dashboard/dist/components/LogStream.test.d.ts +1 -0
  41. package/dashboard/dist/components/LogStream.test.js +159 -0
  42. package/dashboard/dist/components/PreviewPanel.d.ts +18 -0
  43. package/dashboard/dist/components/PreviewPanel.js +73 -0
  44. package/dashboard/dist/components/PreviewPanel.test.d.ts +1 -0
  45. package/dashboard/dist/components/PreviewPanel.test.js +124 -0
  46. package/dashboard/dist/components/ProjectCard.d.ts +18 -0
  47. package/dashboard/dist/components/ProjectCard.js +19 -0
  48. package/dashboard/dist/components/ProjectCard.test.d.ts +1 -0
  49. package/dashboard/dist/components/ProjectCard.test.js +53 -0
  50. package/dashboard/dist/components/ProjectDetail.d.ts +44 -0
  51. package/dashboard/dist/components/ProjectDetail.js +65 -0
  52. package/dashboard/dist/components/ProjectDetail.test.d.ts +1 -0
  53. package/dashboard/dist/components/ProjectDetail.test.js +196 -0
  54. package/dashboard/dist/components/ProjectList.d.ts +11 -0
  55. package/dashboard/dist/components/ProjectList.js +62 -0
  56. package/dashboard/dist/components/ProjectList.test.d.ts +1 -0
  57. package/dashboard/dist/components/ProjectList.test.js +93 -0
  58. package/dashboard/dist/components/SettingsPanel.d.ts +32 -0
  59. package/dashboard/dist/components/SettingsPanel.js +154 -0
  60. package/dashboard/dist/components/SettingsPanel.test.d.ts +1 -0
  61. package/dashboard/dist/components/SettingsPanel.test.js +196 -0
  62. package/dashboard/dist/components/StatusBar.d.ts +16 -0
  63. package/dashboard/dist/components/StatusBar.js +47 -0
  64. package/dashboard/dist/components/StatusBar.test.d.ts +1 -0
  65. package/dashboard/dist/components/StatusBar.test.js +123 -0
  66. package/dashboard/dist/components/TaskBoard.d.ts +22 -0
  67. package/dashboard/dist/components/TaskBoard.js +102 -0
  68. package/dashboard/dist/components/TaskBoard.test.d.ts +1 -0
  69. package/dashboard/dist/components/TaskBoard.test.js +113 -0
  70. package/dashboard/dist/components/TaskCard.d.ts +17 -0
  71. package/dashboard/dist/components/TaskCard.js +29 -0
  72. package/dashboard/dist/components/TaskCard.test.d.ts +1 -0
  73. package/dashboard/dist/components/TaskCard.test.js +109 -0
  74. package/dashboard/dist/components/TaskDetail.d.ts +36 -0
  75. package/dashboard/dist/components/TaskDetail.js +41 -0
  76. package/dashboard/dist/components/TaskDetail.test.d.ts +1 -0
  77. package/dashboard/dist/components/TaskDetail.test.js +164 -0
  78. package/dashboard/dist/components/TaskFilter.d.ts +12 -0
  79. package/dashboard/dist/components/TaskFilter.js +138 -0
  80. package/dashboard/dist/components/TaskFilter.test.d.ts +1 -0
  81. package/dashboard/dist/components/TaskFilter.test.js +109 -0
  82. package/dashboard/dist/components/TeamPanel.d.ts +15 -0
  83. package/dashboard/dist/components/TeamPanel.js +24 -0
  84. package/dashboard/dist/components/TeamPanel.test.d.ts +1 -0
  85. package/dashboard/dist/components/TeamPanel.test.js +109 -0
  86. package/dashboard/dist/components/TeamPresence.d.ts +14 -0
  87. package/dashboard/dist/components/TeamPresence.js +31 -0
  88. package/dashboard/dist/components/TeamPresence.test.d.ts +1 -0
  89. package/dashboard/dist/components/TeamPresence.test.js +144 -0
  90. package/dashboard/dist/components/layout/Header.d.ts +9 -0
  91. package/dashboard/dist/components/layout/Header.js +11 -0
  92. package/dashboard/dist/components/layout/Header.test.d.ts +1 -0
  93. package/dashboard/dist/components/layout/Header.test.js +35 -0
  94. package/dashboard/dist/components/layout/Shell.d.ts +10 -0
  95. package/dashboard/dist/components/layout/Shell.js +5 -0
  96. package/dashboard/dist/components/layout/Shell.test.d.ts +1 -0
  97. package/dashboard/dist/components/layout/Shell.test.js +34 -0
  98. package/dashboard/dist/components/layout/Sidebar.d.ts +14 -0
  99. package/dashboard/dist/components/layout/Sidebar.js +8 -0
  100. package/dashboard/dist/components/layout/Sidebar.test.d.ts +1 -0
  101. package/dashboard/dist/components/layout/Sidebar.test.js +40 -0
  102. package/dashboard/dist/components/ui/Badge.d.ts +9 -0
  103. package/dashboard/dist/components/ui/Badge.js +13 -0
  104. package/dashboard/dist/components/ui/Badge.test.d.ts +1 -0
  105. package/dashboard/dist/components/ui/Badge.test.js +69 -0
  106. package/dashboard/dist/components/ui/Button.d.ts +12 -0
  107. package/dashboard/dist/components/ui/Button.js +14 -0
  108. package/dashboard/dist/components/ui/Button.test.d.ts +1 -0
  109. package/dashboard/dist/components/ui/Button.test.js +81 -0
  110. package/dashboard/dist/components/ui/Card.d.ts +21 -0
  111. package/dashboard/dist/components/ui/Card.js +20 -0
  112. package/dashboard/dist/components/ui/Card.test.d.ts +1 -0
  113. package/dashboard/dist/components/ui/Card.test.js +82 -0
  114. package/dashboard/dist/components/ui/Input.d.ts +13 -0
  115. package/dashboard/dist/components/ui/Input.js +8 -0
  116. package/dashboard/dist/components/ui/Input.test.d.ts +1 -0
  117. package/dashboard/dist/components/ui/Input.test.js +68 -0
  118. package/dashboard/dist/styles/tokens.d.ts +150 -0
  119. package/dashboard/dist/styles/tokens.js +184 -0
  120. package/dashboard/dist/styles/tokens.test.d.ts +1 -0
  121. package/dashboard/dist/styles/tokens.test.js +95 -0
  122. package/dashboard/dist/test/setup.d.ts +1 -0
  123. package/dashboard/dist/test/setup.js +1 -0
  124. package/dashboard/package.json +3 -0
  125. package/package.json +15 -4
  126. package/scripts/capture-screenshots.js +170 -0
  127. package/scripts/docs-update.js +253 -0
  128. package/scripts/generate-screenshots.js +321 -0
  129. package/scripts/project-docs.js +377 -0
  130. package/scripts/vps-setup.sh +477 -0
  131. package/server/lib/adapters/base-adapter.js +114 -0
  132. package/server/lib/adapters/base-adapter.test.js +90 -0
  133. package/server/lib/adapters/claude-adapter.js +141 -0
  134. package/server/lib/adapters/claude-adapter.test.js +180 -0
  135. package/server/lib/adapters/deepseek-adapter.js +153 -0
  136. package/server/lib/adapters/deepseek-adapter.test.js +193 -0
  137. package/server/lib/adapters/openai-adapter.js +190 -0
  138. package/server/lib/adapters/openai-adapter.test.js +231 -0
  139. package/server/lib/budget-tracker.js +169 -0
  140. package/server/lib/budget-tracker.test.js +165 -0
  141. package/server/lib/claude-injector.js +85 -0
  142. package/server/lib/claude-injector.test.js +161 -0
  143. package/server/lib/consensus-engine.js +135 -0
  144. package/server/lib/consensus-engine.test.js +152 -0
  145. package/server/lib/context-builder.js +112 -0
  146. package/server/lib/context-builder.test.js +120 -0
  147. package/server/lib/file-collector.js +322 -0
  148. package/server/lib/file-collector.test.js +307 -0
  149. package/server/lib/memory-classifier.js +175 -0
  150. package/server/lib/memory-classifier.test.js +169 -0
  151. package/server/lib/memory-committer.js +138 -0
  152. package/server/lib/memory-committer.test.js +136 -0
  153. package/server/lib/memory-hooks.js +127 -0
  154. package/server/lib/memory-hooks.test.js +136 -0
  155. package/server/lib/memory-init.js +104 -0
  156. package/server/lib/memory-init.test.js +119 -0
  157. package/server/lib/memory-observer.js +149 -0
  158. package/server/lib/memory-observer.test.js +158 -0
  159. package/server/lib/memory-reader.js +243 -0
  160. package/server/lib/memory-reader.test.js +216 -0
  161. package/server/lib/memory-storage.js +120 -0
  162. package/server/lib/memory-storage.test.js +136 -0
  163. package/server/lib/memory-writer.js +176 -0
  164. package/server/lib/memory-writer.test.js +231 -0
  165. package/server/lib/overdrive-command.js +30 -6
  166. package/server/lib/overdrive-command.test.js +8 -1
  167. package/server/lib/pattern-detector.js +216 -0
  168. package/server/lib/pattern-detector.test.js +241 -0
  169. package/server/lib/relevance-scorer.js +175 -0
  170. package/server/lib/relevance-scorer.test.js +107 -0
  171. package/server/lib/review-command.js +238 -0
  172. package/server/lib/review-command.test.js +245 -0
  173. package/server/lib/review-orchestrator.js +273 -0
  174. package/server/lib/review-orchestrator.test.js +300 -0
  175. package/server/lib/review-reporter.js +288 -0
  176. package/server/lib/review-reporter.test.js +240 -0
  177. package/server/lib/session-summary.js +90 -0
  178. package/server/lib/session-summary.test.js +156 -0
  179. package/templates/docs-sync.yml +91 -0
@@ -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 {};
@@ -0,0 +1,164 @@
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 { TaskDetail } from './TaskDetail.js';
5
+ const sampleTask = {
6
+ id: 't1',
7
+ title: 'Create user schema',
8
+ status: 'in_progress',
9
+ priority: 'high',
10
+ assignee: 'alice',
11
+ description: 'Define the database schema for users table with proper constraints and indexes.',
12
+ tests: { passing: 8, failing: 2 },
13
+ activity: [
14
+ { id: 'a1', type: 'status_change', timestamp: '10 min ago', user: 'alice', detail: 'Started work' },
15
+ { id: 'a2', type: 'comment', timestamp: '5 min ago', user: 'bob', detail: 'Looks good so far!' },
16
+ { id: 'a3', type: 'test_run', timestamp: '2 min ago', detail: '8/10 tests passing' },
17
+ ],
18
+ files: [
19
+ 'src/db/schema/users.ts',
20
+ 'src/db/migrations/001_users.sql',
21
+ ],
22
+ acceptanceCriteria: [
23
+ { id: 'ac1', text: 'Has id, email, passwordHash, createdAt', done: true },
24
+ { id: 'ac2', text: 'Email unique constraint', done: true },
25
+ { id: 'ac3', text: 'Password hashed with bcrypt', done: false },
26
+ ],
27
+ };
28
+ describe('TaskDetail', () => {
29
+ describe('Header', () => {
30
+ it('renders task title', () => {
31
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
32
+ expect(lastFrame()).toContain('Create user schema');
33
+ });
34
+ it('renders task status', () => {
35
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
36
+ expect(lastFrame()).toMatch(/in.progress|working/i);
37
+ });
38
+ it('renders priority', () => {
39
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
40
+ expect(lastFrame()).toContain('high');
41
+ });
42
+ it('renders assignee', () => {
43
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
44
+ expect(lastFrame()).toContain('alice');
45
+ });
46
+ });
47
+ describe('Description', () => {
48
+ it('shows full description', () => {
49
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
50
+ expect(lastFrame()).toContain('Define the database schema');
51
+ });
52
+ });
53
+ describe('Acceptance Criteria', () => {
54
+ it('shows acceptance criteria', () => {
55
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
56
+ expect(lastFrame()).toContain('Has id, email');
57
+ expect(lastFrame()).toContain('Email unique');
58
+ });
59
+ it('shows completed criteria with checkmark', () => {
60
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
61
+ expect(lastFrame()).toMatch(/\[x\]|✓/);
62
+ });
63
+ it('shows incomplete criteria without checkmark', () => {
64
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
65
+ expect(lastFrame()).toContain('Password hashed');
66
+ });
67
+ it('shows criteria count', () => {
68
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
69
+ expect(lastFrame()).toContain('2/3');
70
+ });
71
+ });
72
+ describe('Test Status', () => {
73
+ it('shows test counts', () => {
74
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
75
+ expect(lastFrame()).toContain('8');
76
+ expect(lastFrame()).toContain('10');
77
+ });
78
+ it('shows failing tests', () => {
79
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
80
+ expect(lastFrame()).toMatch(/2.*(fail|✗)/i);
81
+ });
82
+ });
83
+ describe('Files', () => {
84
+ it('shows related files', () => {
85
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
86
+ expect(lastFrame()).toContain('users.ts');
87
+ expect(lastFrame()).toContain('001_users.sql');
88
+ });
89
+ });
90
+ describe('Activity', () => {
91
+ it('shows activity entries', () => {
92
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
93
+ expect(lastFrame()).toContain('Started work');
94
+ expect(lastFrame()).toContain('Looks good');
95
+ });
96
+ it('shows activity timestamps', () => {
97
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
98
+ expect(lastFrame()).toContain('10 min ago');
99
+ });
100
+ it('shows activity users', () => {
101
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
102
+ expect(lastFrame()).toContain('alice');
103
+ expect(lastFrame()).toContain('bob');
104
+ });
105
+ });
106
+ describe('Actions', () => {
107
+ it('shows claim action for unassigned task', () => {
108
+ const unassignedTask = { ...sampleTask, assignee: undefined };
109
+ const { lastFrame } = render(_jsx(TaskDetail, { task: unassignedTask }));
110
+ expect(lastFrame()).toContain('c');
111
+ });
112
+ it('shows release action for assigned task', () => {
113
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
114
+ expect(lastFrame()).toContain('r');
115
+ });
116
+ it('shows status change actions', () => {
117
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
118
+ // Shows [d]one action for in-progress task
119
+ expect(lastFrame()).toContain('[d]one');
120
+ });
121
+ });
122
+ describe('Navigation', () => {
123
+ it('shows back navigation hint', () => {
124
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask }));
125
+ expect(lastFrame()).toContain('Esc');
126
+ });
127
+ it('calls onBack when provided', () => {
128
+ const onBack = vi.fn();
129
+ const { lastFrame } = render(_jsx(TaskDetail, { task: sampleTask, onBack: onBack }));
130
+ expect(lastFrame()).toContain('Esc');
131
+ });
132
+ });
133
+ describe('Callbacks', () => {
134
+ it('calls onClaim when claim action triggered', () => {
135
+ const onClaim = vi.fn();
136
+ render(_jsx(TaskDetail, { task: sampleTask, onClaim: onClaim }));
137
+ // Claim happens on 'c' key
138
+ });
139
+ it('calls onRelease when release action triggered', () => {
140
+ const onRelease = vi.fn();
141
+ render(_jsx(TaskDetail, { task: sampleTask, onRelease: onRelease }));
142
+ // Release happens on 'r' key
143
+ });
144
+ it('calls onStatusChange when status changed', () => {
145
+ const onStatusChange = vi.fn();
146
+ render(_jsx(TaskDetail, { task: sampleTask, onStatusChange: onStatusChange }));
147
+ // Status change happens on specific keys
148
+ });
149
+ });
150
+ describe('Minimal Task', () => {
151
+ it('renders with minimal data', () => {
152
+ const minimalTask = {
153
+ id: 'min',
154
+ title: 'Minimal',
155
+ status: 'pending',
156
+ activity: [],
157
+ files: [],
158
+ acceptanceCriteria: [],
159
+ };
160
+ const { lastFrame } = render(_jsx(TaskDetail, { task: minimalTask }));
161
+ expect(lastFrame()).toContain('Minimal');
162
+ });
163
+ });
164
+ });
@@ -0,0 +1,12 @@
1
+ import { TaskStatus, TaskPriority } from './TaskCard.js';
2
+ export interface FilterState {
3
+ assignee?: string;
4
+ status?: TaskStatus[];
5
+ priority?: TaskPriority[];
6
+ }
7
+ export interface TaskFilterProps {
8
+ assignees: string[];
9
+ filters: FilterState;
10
+ onChange: (filters: FilterState) => void;
11
+ }
12
+ export declare function TaskFilter({ assignees, filters, onChange, }: TaskFilterProps): import("react/jsx-runtime").JSX.Element;