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,62 @@
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 { ProjectCard } from './ProjectCard.js';
5
+ export function ProjectList({ projects, onSelect, sortBy = 'name', filter = '', }) {
6
+ const [selectedIndex, setSelectedIndex] = useState(0);
7
+ // Filter projects
8
+ const filteredProjects = useMemo(() => {
9
+ if (!filter)
10
+ return projects;
11
+ const lowerFilter = filter.toLowerCase();
12
+ return projects.filter(p => p.name.toLowerCase().includes(lowerFilter) ||
13
+ p.description?.toLowerCase().includes(lowerFilter));
14
+ }, [projects, filter]);
15
+ // Sort projects
16
+ const sortedProjects = useMemo(() => {
17
+ const sorted = [...filteredProjects];
18
+ switch (sortBy) {
19
+ case 'name':
20
+ sorted.sort((a, b) => a.name.localeCompare(b.name));
21
+ break;
22
+ case 'activity':
23
+ // Sort by lastActivity (assuming ISO date string or relative time)
24
+ sorted.sort((a, b) => {
25
+ if (!a.lastActivity)
26
+ return 1;
27
+ if (!b.lastActivity)
28
+ return -1;
29
+ return b.lastActivity.localeCompare(a.lastActivity);
30
+ });
31
+ break;
32
+ case 'status':
33
+ // Sort by test status (failing first)
34
+ sorted.sort((a, b) => {
35
+ const aFailing = a.tests?.failing || 0;
36
+ const bFailing = b.tests?.failing || 0;
37
+ return bFailing - aFailing;
38
+ });
39
+ break;
40
+ }
41
+ return sorted;
42
+ }, [filteredProjects, sortBy]);
43
+ // Handle keyboard navigation
44
+ useInput((input, key) => {
45
+ if (sortedProjects.length === 0)
46
+ return;
47
+ if (key.downArrow || input === 'j') {
48
+ setSelectedIndex(prev => Math.min(prev + 1, sortedProjects.length - 1));
49
+ }
50
+ else if (key.upArrow || input === 'k') {
51
+ setSelectedIndex(prev => Math.max(prev - 1, 0));
52
+ }
53
+ else if (key.return && onSelect) {
54
+ onSelect(sortedProjects[selectedIndex]);
55
+ }
56
+ });
57
+ // Empty state
58
+ if (sortedProjects.length === 0) {
59
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { dimColor: true, children: filter ? `No projects matching "${filter}"` : 'No projects found' }), _jsx(Text, { dimColor: true, children: filter ? 'Try a different search term' : 'Run /tlc:new-project to create one' })] }));
60
+ }
61
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { dimColor: true, children: [sortedProjects.length, " project", sortedProjects.length !== 1 ? 's' : '', filter && ` matching "${filter}"`] }) }), sortedProjects.map((project, index) => (_jsx(Box, { marginBottom: 1, children: _jsx(ProjectCard, { ...project, isSelected: index === selectedIndex }) }, project.id))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191/k \u2193/j navigate \u2022 Enter select" }) })] }));
62
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
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 { ProjectList } from './ProjectList.js';
5
+ const sampleProjects = [
6
+ {
7
+ id: '1',
8
+ name: 'Alpha Project',
9
+ description: 'First project',
10
+ tests: { passing: 10, failing: 0, total: 10 },
11
+ coverage: 85,
12
+ lastActivity: '1 hour ago',
13
+ },
14
+ {
15
+ id: '2',
16
+ name: 'Beta Project',
17
+ description: 'Second project',
18
+ tests: { passing: 8, failing: 2, total: 10 },
19
+ coverage: 65,
20
+ lastActivity: '2 hours ago',
21
+ },
22
+ {
23
+ id: '3',
24
+ name: 'Gamma Project',
25
+ description: 'Third project',
26
+ tests: { passing: 5, failing: 5, total: 10 },
27
+ coverage: 50,
28
+ lastActivity: '3 hours ago',
29
+ },
30
+ ];
31
+ describe('ProjectList', () => {
32
+ it('renders list of projects', () => {
33
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects }));
34
+ expect(lastFrame()).toContain('Alpha Project');
35
+ expect(lastFrame()).toContain('Beta Project');
36
+ expect(lastFrame()).toContain('Gamma Project');
37
+ });
38
+ it('shows project count', () => {
39
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects }));
40
+ expect(lastFrame()).toContain('3 projects');
41
+ });
42
+ it('shows single project count correctly', () => {
43
+ const { lastFrame } = render(_jsx(ProjectList, { projects: [sampleProjects[0]] }));
44
+ expect(lastFrame()).toContain('1 project');
45
+ });
46
+ it('filters projects by name', () => {
47
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects, filter: "Alpha" }));
48
+ expect(lastFrame()).toContain('Alpha Project');
49
+ expect(lastFrame()).not.toContain('Beta Project');
50
+ expect(lastFrame()).toContain('matching "Alpha"');
51
+ });
52
+ it('filters projects by description', () => {
53
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects, filter: "Second" }));
54
+ expect(lastFrame()).toContain('Beta Project');
55
+ expect(lastFrame()).not.toContain('Alpha Project');
56
+ });
57
+ it('shows empty state when no projects', () => {
58
+ const { lastFrame } = render(_jsx(ProjectList, { projects: [] }));
59
+ expect(lastFrame()).toContain('No projects found');
60
+ expect(lastFrame()).toContain('/tlc:new-project');
61
+ });
62
+ it('shows empty state when filter matches nothing', () => {
63
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects, filter: "nonexistent" }));
64
+ expect(lastFrame()).toContain('No projects matching');
65
+ expect(lastFrame()).toContain('Try a different search term');
66
+ });
67
+ it('sorts by name by default', () => {
68
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects }));
69
+ const output = lastFrame() || '';
70
+ const alphaIndex = output.indexOf('Alpha');
71
+ const betaIndex = output.indexOf('Beta');
72
+ const gammaIndex = output.indexOf('Gamma');
73
+ expect(alphaIndex).toBeLessThan(betaIndex);
74
+ expect(betaIndex).toBeLessThan(gammaIndex);
75
+ });
76
+ it('sorts by status (failing first)', () => {
77
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects, sortBy: "status" }));
78
+ const output = lastFrame() || '';
79
+ // Gamma has most failures (5), should be first
80
+ const gammaIndex = output.indexOf('Gamma');
81
+ const alphaIndex = output.indexOf('Alpha');
82
+ expect(gammaIndex).toBeLessThan(alphaIndex);
83
+ });
84
+ it('shows navigation hint', () => {
85
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects }));
86
+ expect(lastFrame()).toContain('↑/k ↓/j navigate');
87
+ expect(lastFrame()).toContain('Enter select');
88
+ });
89
+ it('first project is selected by default', () => {
90
+ const { lastFrame } = render(_jsx(ProjectList, { projects: sampleProjects }));
91
+ expect(lastFrame()).toContain('▶');
92
+ });
93
+ });
@@ -0,0 +1,32 @@
1
+ export interface TLCConfig {
2
+ project?: string;
3
+ version?: string;
4
+ quality?: {
5
+ coverageThreshold?: number;
6
+ qualityScoreThreshold?: number;
7
+ };
8
+ git?: {
9
+ mainBranch?: string;
10
+ };
11
+ paths?: {
12
+ planning?: string;
13
+ tests?: string;
14
+ };
15
+ team?: {
16
+ enabled?: boolean;
17
+ };
18
+ testFrameworks?: {
19
+ primary?: string;
20
+ e2e?: string;
21
+ };
22
+ }
23
+ export interface SettingsPanelProps {
24
+ config: TLCConfig;
25
+ configPath?: string;
26
+ isEditing?: boolean;
27
+ isActive?: boolean;
28
+ onEdit?: () => void;
29
+ onSave?: (config: TLCConfig) => void;
30
+ onCancel?: () => void;
31
+ }
32
+ export declare function SettingsPanel({ config, configPath, isEditing, isActive, onEdit, onSave, onCancel, }: SettingsPanelProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,154 @@
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
+ function formatValue(value) {
5
+ if (value === undefined || value === null || value === '') {
6
+ return '—';
7
+ }
8
+ if (typeof value === 'boolean') {
9
+ return value ? 'enabled' : 'disabled';
10
+ }
11
+ return String(value);
12
+ }
13
+ function buildSections(config) {
14
+ return [
15
+ {
16
+ key: 'project',
17
+ label: 'Project',
18
+ items: [
19
+ { key: 'project', label: 'Name', value: config.project, type: 'string' },
20
+ { key: 'version', label: 'Version', value: config.version, type: 'string' },
21
+ ],
22
+ },
23
+ {
24
+ key: 'quality',
25
+ label: 'Quality',
26
+ items: [
27
+ {
28
+ key: 'coverageThreshold',
29
+ label: 'Coverage Threshold',
30
+ value: config.quality?.coverageThreshold,
31
+ type: 'number',
32
+ },
33
+ {
34
+ key: 'qualityScoreThreshold',
35
+ label: 'Quality Score Threshold',
36
+ value: config.quality?.qualityScoreThreshold,
37
+ type: 'number',
38
+ },
39
+ ],
40
+ },
41
+ {
42
+ key: 'git',
43
+ label: 'Git',
44
+ items: [
45
+ {
46
+ key: 'mainBranch',
47
+ label: 'Main Branch',
48
+ value: config.git?.mainBranch,
49
+ type: 'string',
50
+ },
51
+ ],
52
+ },
53
+ {
54
+ key: 'paths',
55
+ label: 'Paths',
56
+ items: [
57
+ {
58
+ key: 'planning',
59
+ label: 'Planning Directory',
60
+ value: config.paths?.planning,
61
+ type: 'string',
62
+ },
63
+ {
64
+ key: 'tests',
65
+ label: 'Tests Directory',
66
+ value: config.paths?.tests,
67
+ type: 'string',
68
+ },
69
+ ],
70
+ },
71
+ {
72
+ key: 'team',
73
+ label: 'Team',
74
+ items: [
75
+ {
76
+ key: 'enabled',
77
+ label: 'Team Mode',
78
+ value: config.team?.enabled,
79
+ type: 'boolean',
80
+ },
81
+ ],
82
+ },
83
+ {
84
+ key: 'testFrameworks',
85
+ label: 'Test Frameworks',
86
+ items: [
87
+ {
88
+ key: 'primary',
89
+ label: 'Primary Framework',
90
+ value: config.testFrameworks?.primary,
91
+ type: 'string',
92
+ },
93
+ {
94
+ key: 'e2e',
95
+ label: 'E2E Framework',
96
+ value: config.testFrameworks?.e2e,
97
+ type: 'string',
98
+ },
99
+ ],
100
+ },
101
+ ];
102
+ }
103
+ export function SettingsPanel({ config, configPath = '.tlc.json', isEditing = false, isActive = true, onEdit, onSave, onCancel, }) {
104
+ const [activeSection, setActiveSection] = useState(0);
105
+ const [activeItem, setActiveItem] = useState(0);
106
+ const sections = useMemo(() => buildSections(config), [config]);
107
+ const currentSection = sections[activeSection];
108
+ const totalItems = currentSection?.items.length || 0;
109
+ useInput((input, key) => {
110
+ if (!isActive)
111
+ return;
112
+ // Edit mode controls
113
+ if (isEditing) {
114
+ if (key.escape && onCancel) {
115
+ onCancel();
116
+ }
117
+ else if ((key.return || input === 's') && onSave) {
118
+ onSave(config);
119
+ }
120
+ return;
121
+ }
122
+ // View mode controls
123
+ if (input === 'e' && onEdit) {
124
+ onEdit();
125
+ }
126
+ // Section navigation (Tab)
127
+ if (key.tab) {
128
+ setActiveSection((prev) => (prev + 1) % sections.length);
129
+ setActiveItem(0);
130
+ }
131
+ // Item navigation
132
+ if (key.downArrow || input === 'j') {
133
+ setActiveItem((prev) => Math.min(prev + 1, totalItems - 1));
134
+ }
135
+ else if (key.upArrow || input === 'k') {
136
+ setActiveItem((prev) => Math.max(prev - 1, 0));
137
+ }
138
+ }, { isActive });
139
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Settings" }), isEditing && _jsx(Text, { color: "yellow", children: " [editing]" })] }), _jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "Config: " }), _jsx(Text, { color: "cyan", children: configPath })] }), sections.map((section, sectionIdx) => {
140
+ const isSectionActive = sectionIdx === activeSection;
141
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, borderStyle: isSectionActive ? 'double' : 'single', borderColor: isSectionActive ? 'cyan' : 'gray', paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: isSectionActive ? 'cyan' : 'white', children: section.label }) }), section.items.map((item, itemIdx) => {
142
+ const isItemActive = isSectionActive && itemIdx === activeItem;
143
+ const displayValue = formatValue(item.value);
144
+ const isNotSet = displayValue === '—';
145
+ return (_jsxs(Box, { children: [_jsx(Text, { color: isItemActive ? 'cyan' : undefined, children: isItemActive ? '▶ ' : ' ' }), _jsx(Box, { width: 24, children: _jsxs(Text, { dimColor: true, children: [item.label, ":"] }) }), _jsxs(Text, { color: isNotSet
146
+ ? 'gray'
147
+ : item.type === 'boolean'
148
+ ? item.value
149
+ ? 'green'
150
+ : 'yellow'
151
+ : 'white', bold: isItemActive, children: [displayValue, item.type === 'number' && !isNotSet && '%'] }), isEditing && isItemActive && (_jsx(Text, { color: "yellow", children: " \u2190" }))] }, item.key));
152
+ })] }, section.key));
153
+ }), _jsx(Box, { marginTop: 1, children: isEditing ? (_jsx(Text, { dimColor: true, children: "Enter/s save \u2022 Esc cancel \u2022 \u2191/k \u2193/j navigate" })) : (_jsx(Text, { dimColor: true, children: "e edit \u2022 Tab section \u2022 \u2191/k \u2193/j navigate" })) })] }));
154
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,196 @@
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 { SettingsPanel } from './SettingsPanel.js';
5
+ const sampleConfig = {
6
+ project: 'my-app',
7
+ version: '1.2.0',
8
+ quality: {
9
+ coverageThreshold: 80,
10
+ qualityScoreThreshold: 75,
11
+ },
12
+ git: {
13
+ mainBranch: 'main',
14
+ },
15
+ paths: {
16
+ planning: '.planning',
17
+ tests: 'src/__tests__',
18
+ },
19
+ team: {
20
+ enabled: true,
21
+ },
22
+ testFrameworks: {
23
+ primary: 'vitest',
24
+ e2e: 'playwright',
25
+ },
26
+ };
27
+ describe('SettingsPanel', () => {
28
+ describe('Config Display', () => {
29
+ it('shows project name', () => {
30
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
31
+ expect(lastFrame()).toContain('my-app');
32
+ });
33
+ it('shows version', () => {
34
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
35
+ expect(lastFrame()).toContain('1.2.0');
36
+ });
37
+ it('shows coverage threshold', () => {
38
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
39
+ expect(lastFrame()).toContain('80');
40
+ });
41
+ it('shows quality threshold', () => {
42
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
43
+ expect(lastFrame()).toContain('75');
44
+ });
45
+ it('shows main branch', () => {
46
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
47
+ expect(lastFrame()).toContain('main');
48
+ });
49
+ it('shows test framework', () => {
50
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
51
+ expect(lastFrame()).toContain('vitest');
52
+ });
53
+ });
54
+ describe('Category Grouping', () => {
55
+ it('shows Quality section', () => {
56
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
57
+ expect(lastFrame()).toMatch(/quality/i);
58
+ });
59
+ it('shows Git section', () => {
60
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
61
+ expect(lastFrame()).toMatch(/git/i);
62
+ });
63
+ it('shows Paths section', () => {
64
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
65
+ expect(lastFrame()).toMatch(/paths/i);
66
+ });
67
+ it('shows Team section', () => {
68
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
69
+ expect(lastFrame()).toMatch(/team/i);
70
+ });
71
+ it('shows Test Frameworks section', () => {
72
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
73
+ expect(lastFrame()).toMatch(/test|framework/i);
74
+ });
75
+ });
76
+ describe('Edit Mode', () => {
77
+ it('shows edit hint', () => {
78
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
79
+ expect(lastFrame()).toMatch(/e|edit/i);
80
+ });
81
+ it('shows editable state when editing', () => {
82
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig, isEditing: true }));
83
+ expect(lastFrame()).toMatch(/editing|edit mode/i);
84
+ });
85
+ it('shows save hint in edit mode', () => {
86
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig, isEditing: true }));
87
+ expect(lastFrame()).toMatch(/save|enter|s/i);
88
+ });
89
+ it('shows cancel hint in edit mode', () => {
90
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig, isEditing: true }));
91
+ expect(lastFrame()).toMatch(/cancel|esc/i);
92
+ });
93
+ });
94
+ describe('Config File Path', () => {
95
+ it('shows config file path', () => {
96
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig, configPath: ".tlc.json" }));
97
+ expect(lastFrame()).toContain('.tlc.json');
98
+ });
99
+ it('shows default path when not specified', () => {
100
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
101
+ expect(lastFrame()).toMatch(/\.tlc\.json|config/i);
102
+ });
103
+ });
104
+ describe('Missing Config', () => {
105
+ it('handles empty config gracefully', () => {
106
+ const emptyConfig = {
107
+ project: '',
108
+ version: '',
109
+ quality: {},
110
+ git: {},
111
+ paths: {},
112
+ team: {},
113
+ testFrameworks: {},
114
+ };
115
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: emptyConfig }));
116
+ expect(lastFrame()).toMatch(/settings|config/i);
117
+ });
118
+ it('shows not configured message for missing values', () => {
119
+ const partialConfig = {
120
+ project: 'test',
121
+ version: '1.0.0',
122
+ quality: {},
123
+ git: {},
124
+ paths: {},
125
+ team: {},
126
+ testFrameworks: {},
127
+ };
128
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: partialConfig }));
129
+ expect(lastFrame()).toMatch(/not.*set|default|—/i);
130
+ });
131
+ });
132
+ describe('Validation', () => {
133
+ it('shows validation for coverage threshold', () => {
134
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig, isEditing: true }));
135
+ // Should indicate valid range
136
+ expect(lastFrame()).toBeDefined();
137
+ });
138
+ });
139
+ describe('Callbacks', () => {
140
+ it('calls onSave when save triggered', () => {
141
+ const onSave = vi.fn();
142
+ render(_jsx(SettingsPanel, { config: sampleConfig, isEditing: true, onSave: onSave }));
143
+ // Save happens on Enter/s key
144
+ });
145
+ it('calls onCancel when cancel triggered', () => {
146
+ const onCancel = vi.fn();
147
+ render(_jsx(SettingsPanel, { config: sampleConfig, isEditing: true, onCancel: onCancel }));
148
+ // Cancel happens on Esc key
149
+ });
150
+ it('calls onEdit when edit triggered', () => {
151
+ const onEdit = vi.fn();
152
+ render(_jsx(SettingsPanel, { config: sampleConfig, onEdit: onEdit }));
153
+ // Edit happens on 'e' key
154
+ });
155
+ });
156
+ describe('Navigation', () => {
157
+ it('shows navigation hints', () => {
158
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
159
+ expect(lastFrame()).toMatch(/↑|↓|j|k|navigate/i);
160
+ });
161
+ it('shows section navigation hint', () => {
162
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
163
+ expect(lastFrame()).toMatch(/tab|section/i);
164
+ });
165
+ });
166
+ describe('Team Settings', () => {
167
+ it('shows team enabled status', () => {
168
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
169
+ expect(lastFrame()).toMatch(/enabled|true|yes/i);
170
+ });
171
+ it('shows team disabled status', () => {
172
+ const disabledTeam = {
173
+ ...sampleConfig,
174
+ team: { enabled: false },
175
+ };
176
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: disabledTeam }));
177
+ expect(lastFrame()).toMatch(/disabled|false|no|solo/i);
178
+ });
179
+ });
180
+ describe('Header', () => {
181
+ it('shows Settings title', () => {
182
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
183
+ expect(lastFrame()).toMatch(/settings/i);
184
+ });
185
+ });
186
+ describe('Paths Display', () => {
187
+ it('shows planning path', () => {
188
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
189
+ expect(lastFrame()).toContain('.planning');
190
+ });
191
+ it('shows tests path', () => {
192
+ const { lastFrame } = render(_jsx(SettingsPanel, { config: sampleConfig }));
193
+ expect(lastFrame()).toContain('src/__tests__');
194
+ });
195
+ });
196
+ });
@@ -0,0 +1,16 @@
1
+ import { ConnectionState } from './ConnectionStatus.js';
2
+ import { Environment } from './EnvironmentBadge.js';
3
+ export interface StatusBarProps {
4
+ projectName?: string;
5
+ version?: string;
6
+ branch?: string;
7
+ environment?: Environment;
8
+ connectionState?: ConnectionState;
9
+ currentPhase?: number;
10
+ totalPhases?: number;
11
+ testsPassing?: number;
12
+ testsTotal?: number;
13
+ testsFailing?: number;
14
+ width?: number;
15
+ }
16
+ export declare function StatusBar({ projectName, version, branch, environment, connectionState, currentPhase, totalPhases, testsPassing, testsTotal, testsFailing, width, }: StatusBarProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { Box, Text } from 'ink';
4
+ const connectionConfig = {
5
+ connected: { icon: '●', color: 'green' },
6
+ connecting: { icon: '◐', color: 'yellow' },
7
+ disconnected: { icon: '○', color: 'red' },
8
+ };
9
+ const envColors = {
10
+ local: 'green',
11
+ vps: 'cyan',
12
+ staging: 'yellow',
13
+ production: 'red',
14
+ };
15
+ export function StatusBar({ projectName, version, branch, environment, connectionState, currentPhase, totalPhases, testsPassing, testsTotal, testsFailing, width, }) {
16
+ const sections = [];
17
+ // Project name and version
18
+ if (projectName) {
19
+ sections.push(_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "white", children: projectName }), version && _jsxs(Text, { dimColor: true, children: [" v", version] })] }, "project"));
20
+ }
21
+ // Branch
22
+ if (branch) {
23
+ sections.push(_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "\u2387 " }), _jsx(Text, { color: "cyan", children: branch })] }, "branch"));
24
+ }
25
+ // Environment
26
+ if (environment) {
27
+ const color = envColors[environment];
28
+ sections.push(_jsx(Box, { children: _jsxs(Text, { color: color, children: [environment === 'production' ? '⚠ ' : '', environment] }) }, "env"));
29
+ }
30
+ // Phase progress
31
+ if (currentPhase !== undefined && totalPhases !== undefined) {
32
+ sections.push(_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Phase " }), _jsxs(Text, { children: [currentPhase, "/", totalPhases] })] }, "phase"));
33
+ }
34
+ // Test status
35
+ if (testsPassing !== undefined && testsTotal !== undefined) {
36
+ const hasFailing = testsFailing !== undefined && testsFailing > 0;
37
+ sections.push(_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "Tests " }), _jsxs(Text, { color: hasFailing ? 'red' : 'green', children: [testsPassing, "/", testsTotal] }), hasFailing && (_jsxs(Text, { color: "red", children: [" (", testsFailing, " fail)"] }))] }, "tests"));
38
+ }
39
+ // Connection status
40
+ if (connectionState) {
41
+ const config = connectionConfig[connectionState];
42
+ sections.push(_jsx(Box, { children: _jsx(Text, { color: config.color, children: config.icon }) }, "connection"));
43
+ }
44
+ // Help hint (always shown)
45
+ sections.push(_jsx(Box, { children: _jsx(Text, { dimColor: true, children: "? help" }) }, "help"));
46
+ return (_jsx(Box, { width: width, justifyContent: "space-between", children: sections.map((section, idx) => (_jsxs(React.Fragment, { children: [section, idx < sections.length - 1 && (_jsx(Text, { dimColor: true, children: " \u2502 " }))] }, idx))) }));
47
+ }
@@ -0,0 +1 @@
1
+ export {};