tlc-claude-code 1.4.1 → 1.4.4

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 (91) hide show
  1. package/dashboard/dist/App.js +229 -35
  2. package/dashboard/dist/components/AgentRegistryPane.d.ts +35 -0
  3. package/dashboard/dist/components/AgentRegistryPane.js +89 -0
  4. package/dashboard/dist/components/AgentRegistryPane.test.d.ts +1 -0
  5. package/dashboard/dist/components/AgentRegistryPane.test.js +200 -0
  6. package/dashboard/dist/components/RouterPane.d.ts +5 -0
  7. package/dashboard/dist/components/RouterPane.js +65 -0
  8. package/dashboard/dist/components/RouterPane.test.d.ts +1 -0
  9. package/dashboard/dist/components/RouterPane.test.js +176 -0
  10. package/dashboard/dist/components/accessibility.test.d.ts +1 -0
  11. package/dashboard/dist/components/accessibility.test.js +116 -0
  12. package/dashboard/dist/components/layout/MobileNav.d.ts +16 -0
  13. package/dashboard/dist/components/layout/MobileNav.js +31 -0
  14. package/dashboard/dist/components/layout/MobileNav.test.d.ts +1 -0
  15. package/dashboard/dist/components/layout/MobileNav.test.js +111 -0
  16. package/dashboard/dist/components/performance.test.d.ts +1 -0
  17. package/dashboard/dist/components/performance.test.js +114 -0
  18. package/dashboard/dist/components/responsive.test.d.ts +1 -0
  19. package/dashboard/dist/components/responsive.test.js +114 -0
  20. package/dashboard/dist/components/ui/Dropdown.d.ts +22 -0
  21. package/dashboard/dist/components/ui/Dropdown.js +109 -0
  22. package/dashboard/dist/components/ui/Dropdown.test.d.ts +1 -0
  23. package/dashboard/dist/components/ui/Dropdown.test.js +105 -0
  24. package/dashboard/dist/components/ui/Modal.d.ts +13 -0
  25. package/dashboard/dist/components/ui/Modal.js +25 -0
  26. package/dashboard/dist/components/ui/Modal.test.d.ts +1 -0
  27. package/dashboard/dist/components/ui/Modal.test.js +91 -0
  28. package/dashboard/dist/components/ui/Skeleton.d.ts +32 -0
  29. package/dashboard/dist/components/ui/Skeleton.js +48 -0
  30. package/dashboard/dist/components/ui/Skeleton.test.d.ts +1 -0
  31. package/dashboard/dist/components/ui/Skeleton.test.js +125 -0
  32. package/dashboard/dist/components/ui/Toast.d.ts +32 -0
  33. package/dashboard/dist/components/ui/Toast.js +21 -0
  34. package/dashboard/dist/components/ui/Toast.test.d.ts +1 -0
  35. package/dashboard/dist/components/ui/Toast.test.js +118 -0
  36. package/dashboard/dist/hooks/useTheme.d.ts +37 -0
  37. package/dashboard/dist/hooks/useTheme.js +96 -0
  38. package/dashboard/dist/hooks/useTheme.test.d.ts +1 -0
  39. package/dashboard/dist/hooks/useTheme.test.js +94 -0
  40. package/dashboard/dist/hooks/useWebSocket.d.ts +17 -0
  41. package/dashboard/dist/hooks/useWebSocket.js +100 -0
  42. package/dashboard/dist/hooks/useWebSocket.test.d.ts +1 -0
  43. package/dashboard/dist/hooks/useWebSocket.test.js +115 -0
  44. package/dashboard/dist/stores/projectStore.d.ts +44 -0
  45. package/dashboard/dist/stores/projectStore.js +76 -0
  46. package/dashboard/dist/stores/projectStore.test.d.ts +1 -0
  47. package/dashboard/dist/stores/projectStore.test.js +114 -0
  48. package/dashboard/dist/stores/uiStore.d.ts +29 -0
  49. package/dashboard/dist/stores/uiStore.js +72 -0
  50. package/dashboard/dist/stores/uiStore.test.d.ts +1 -0
  51. package/dashboard/dist/stores/uiStore.test.js +93 -0
  52. package/dashboard/package.json +3 -3
  53. package/docker-compose.dev.yml +6 -1
  54. package/package.json +5 -2
  55. package/server/dashboard/index.html +1336 -779
  56. package/server/index.js +178 -0
  57. package/server/lib/agent-cleanup.js +177 -0
  58. package/server/lib/agent-cleanup.test.js +359 -0
  59. package/server/lib/agent-hooks.js +126 -0
  60. package/server/lib/agent-hooks.test.js +303 -0
  61. package/server/lib/agent-metadata.js +179 -0
  62. package/server/lib/agent-metadata.test.js +383 -0
  63. package/server/lib/agent-persistence.js +191 -0
  64. package/server/lib/agent-persistence.test.js +475 -0
  65. package/server/lib/agent-registry-command.js +340 -0
  66. package/server/lib/agent-registry-command.test.js +334 -0
  67. package/server/lib/agent-registry.js +155 -0
  68. package/server/lib/agent-registry.test.js +239 -0
  69. package/server/lib/agent-state.js +236 -0
  70. package/server/lib/agent-state.test.js +375 -0
  71. package/server/lib/api-provider.js +186 -0
  72. package/server/lib/api-provider.test.js +336 -0
  73. package/server/lib/cli-detector.js +166 -0
  74. package/server/lib/cli-detector.test.js +269 -0
  75. package/server/lib/cli-provider.js +212 -0
  76. package/server/lib/cli-provider.test.js +349 -0
  77. package/server/lib/debug.test.js +62 -0
  78. package/server/lib/devserver-router-api.js +249 -0
  79. package/server/lib/devserver-router-api.test.js +426 -0
  80. package/server/lib/model-router.js +245 -0
  81. package/server/lib/model-router.test.js +313 -0
  82. package/server/lib/output-schemas.js +269 -0
  83. package/server/lib/output-schemas.test.js +307 -0
  84. package/server/lib/provider-interface.js +153 -0
  85. package/server/lib/provider-interface.test.js +394 -0
  86. package/server/lib/provider-queue.js +158 -0
  87. package/server/lib/provider-queue.test.js +315 -0
  88. package/server/lib/router-config.js +221 -0
  89. package/server/lib/router-config.test.js +237 -0
  90. package/server/lib/router-setup-command.js +419 -0
  91. package/server/lib/router-setup-command.test.js +375 -0
@@ -0,0 +1,115 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
3
+ import { render } from 'ink-testing-library';
4
+ import { Text } from 'ink';
5
+ import { useWebSocket } from './useWebSocket.js';
6
+ // Mock WebSocket
7
+ class MockWebSocket {
8
+ url;
9
+ static CONNECTING = 0;
10
+ static OPEN = 1;
11
+ static CLOSING = 2;
12
+ static CLOSED = 3;
13
+ readyState = MockWebSocket.CONNECTING;
14
+ onopen = null;
15
+ onclose = null;
16
+ onmessage = null;
17
+ onerror = null;
18
+ constructor(url) {
19
+ this.url = url;
20
+ setTimeout(() => {
21
+ this.readyState = MockWebSocket.OPEN;
22
+ this.onopen?.(new Event('open'));
23
+ }, 10);
24
+ }
25
+ send = vi.fn();
26
+ close = vi.fn(() => {
27
+ this.readyState = MockWebSocket.CLOSED;
28
+ this.onclose?.(new CloseEvent('close'));
29
+ });
30
+ }
31
+ // Test component that uses the hook
32
+ function WebSocketConsumer({ url }) {
33
+ const { status, isConnected, error, send, subscribe, reconnect, disconnect } = useWebSocket(url);
34
+ return (_jsxs(Text, { children: ["status:", status, "|connected:", String(isConnected), "|error:", error ? error.message : 'null'] }));
35
+ }
36
+ describe('useWebSocket', () => {
37
+ let originalWebSocket;
38
+ beforeEach(() => {
39
+ originalWebSocket = global.WebSocket;
40
+ global.WebSocket = MockWebSocket;
41
+ });
42
+ afterEach(() => {
43
+ global.WebSocket = originalWebSocket;
44
+ vi.clearAllMocks();
45
+ });
46
+ describe('Connection', () => {
47
+ it('connects on mount', () => {
48
+ const { lastFrame } = render(_jsx(WebSocketConsumer, { url: "ws://localhost:3147" }));
49
+ // Initially connecting
50
+ expect(lastFrame()).toContain('status:');
51
+ });
52
+ it('provides connection status', () => {
53
+ const { lastFrame } = render(_jsx(WebSocketConsumer, { url: "ws://localhost:3147" }));
54
+ expect(lastFrame()).toContain('status:');
55
+ });
56
+ it('starts in connecting state', () => {
57
+ const { lastFrame } = render(_jsx(WebSocketConsumer, { url: "ws://localhost:3147" }));
58
+ expect(lastFrame()).toContain('status:connecting');
59
+ });
60
+ it('shows isConnected false initially', () => {
61
+ const { lastFrame } = render(_jsx(WebSocketConsumer, { url: "ws://localhost:3147" }));
62
+ expect(lastFrame()).toContain('connected:false');
63
+ });
64
+ });
65
+ describe('Send Messages', () => {
66
+ it('provides send function via hook', () => {
67
+ const TestSend = () => {
68
+ const { send } = useWebSocket('ws://localhost:3147');
69
+ return _jsxs(Text, { children: ["hasSend:", String(typeof send === 'function')] });
70
+ };
71
+ const { lastFrame } = render(_jsx(TestSend, {}));
72
+ expect(lastFrame()).toContain('hasSend:true');
73
+ });
74
+ });
75
+ describe('Subscribe', () => {
76
+ it('provides subscribe function', () => {
77
+ const TestSubscribe = () => {
78
+ const { subscribe } = useWebSocket('ws://localhost:3147');
79
+ return _jsxs(Text, { children: ["hasSubscribe:", String(typeof subscribe === 'function')] });
80
+ };
81
+ const { lastFrame } = render(_jsx(TestSubscribe, {}));
82
+ expect(lastFrame()).toContain('hasSubscribe:true');
83
+ });
84
+ });
85
+ describe('Reconnection', () => {
86
+ it('provides reconnect function', () => {
87
+ const TestReconnect = () => {
88
+ const { reconnect } = useWebSocket('ws://localhost:3147');
89
+ return _jsxs(Text, { children: ["hasReconnect:", String(typeof reconnect === 'function')] });
90
+ };
91
+ const { lastFrame } = render(_jsx(TestReconnect, {}));
92
+ expect(lastFrame()).toContain('hasReconnect:true');
93
+ });
94
+ });
95
+ describe('Disconnect', () => {
96
+ it('provides disconnect function', () => {
97
+ const TestDisconnect = () => {
98
+ const { disconnect } = useWebSocket('ws://localhost:3147');
99
+ return _jsxs(Text, { children: ["hasDisconnect:", String(typeof disconnect === 'function')] });
100
+ };
101
+ const { lastFrame } = render(_jsx(TestDisconnect, {}));
102
+ expect(lastFrame()).toContain('hasDisconnect:true');
103
+ });
104
+ });
105
+ describe('Error Handling', () => {
106
+ it('provides error state', () => {
107
+ const { lastFrame } = render(_jsx(WebSocketConsumer, { url: "ws://localhost:3147" }));
108
+ expect(lastFrame()).toContain('error:');
109
+ });
110
+ it('starts with no error', () => {
111
+ const { lastFrame } = render(_jsx(WebSocketConsumer, { url: "ws://localhost:3147" }));
112
+ expect(lastFrame()).toContain('error:null');
113
+ });
114
+ });
115
+ });
@@ -0,0 +1,44 @@
1
+ export interface ProjectPhase {
2
+ current: number;
3
+ total: number;
4
+ name: string;
5
+ }
6
+ export interface ProjectTests {
7
+ passing: number;
8
+ failing: number;
9
+ total: number;
10
+ }
11
+ export interface Project {
12
+ id: string;
13
+ name: string;
14
+ description: string;
15
+ phase: ProjectPhase;
16
+ tests: ProjectTests;
17
+ coverage: number;
18
+ lastActivity: string;
19
+ }
20
+ export interface ProjectState {
21
+ projects: Project[];
22
+ selectedProject: Project | null;
23
+ loading: boolean;
24
+ error: string | null;
25
+ }
26
+ export interface ProjectActions {
27
+ setProjects: (projects: Project[]) => void;
28
+ addProject: (project: Project) => void;
29
+ removeProject: (id: string) => void;
30
+ updateProject: (id: string, updates: Partial<Project>) => void;
31
+ selectProject: (id: string) => void;
32
+ clearSelection: () => void;
33
+ setLoading: (loading: boolean) => void;
34
+ setError: (error: string | null) => void;
35
+ clearError: () => void;
36
+ getFilteredProjects: (query: string) => Project[];
37
+ }
38
+ export interface ProjectStore {
39
+ getState: () => ProjectState & ProjectActions;
40
+ setState: (partial: Partial<ProjectState>) => void;
41
+ subscribe: (listener: (state: ProjectState & ProjectActions) => void) => () => void;
42
+ }
43
+ export declare function createProjectStore(): ProjectStore;
44
+ export declare const projectStore: ProjectStore;
@@ -0,0 +1,76 @@
1
+ // Project Store - manages project list and selection
2
+ export function createProjectStore() {
3
+ let state = {
4
+ projects: [],
5
+ selectedProject: null,
6
+ loading: false,
7
+ error: null,
8
+ };
9
+ const listeners = new Set();
10
+ const notify = () => {
11
+ const fullState = { ...state, ...actions };
12
+ listeners.forEach(listener => listener(fullState));
13
+ };
14
+ const actions = {
15
+ setProjects: (projects) => {
16
+ state = { ...state, projects };
17
+ notify();
18
+ },
19
+ addProject: (project) => {
20
+ state = { ...state, projects: [...state.projects, project] };
21
+ notify();
22
+ },
23
+ removeProject: (id) => {
24
+ state = { ...state, projects: state.projects.filter(p => p.id !== id) };
25
+ notify();
26
+ },
27
+ updateProject: (id, updates) => {
28
+ state = {
29
+ ...state,
30
+ projects: state.projects.map(p => p.id === id ? { ...p, ...updates } : p),
31
+ };
32
+ notify();
33
+ },
34
+ selectProject: (id) => {
35
+ const project = state.projects.find(p => p.id === id) || null;
36
+ state = { ...state, selectedProject: project };
37
+ notify();
38
+ },
39
+ clearSelection: () => {
40
+ state = { ...state, selectedProject: null };
41
+ notify();
42
+ },
43
+ setLoading: (loading) => {
44
+ state = { ...state, loading };
45
+ notify();
46
+ },
47
+ setError: (error) => {
48
+ state = { ...state, error };
49
+ notify();
50
+ },
51
+ clearError: () => {
52
+ state = { ...state, error: null };
53
+ notify();
54
+ },
55
+ getFilteredProjects: (query) => {
56
+ if (!query)
57
+ return state.projects;
58
+ const lowerQuery = query.toLowerCase();
59
+ return state.projects.filter(p => p.name.toLowerCase().includes(lowerQuery) ||
60
+ p.description.toLowerCase().includes(lowerQuery));
61
+ },
62
+ };
63
+ return {
64
+ getState: () => ({ ...state, ...actions }),
65
+ setState: (partial) => {
66
+ state = { ...state, ...partial };
67
+ notify();
68
+ },
69
+ subscribe: (listener) => {
70
+ listeners.add(listener);
71
+ return () => listeners.delete(listener);
72
+ },
73
+ };
74
+ }
75
+ // Default singleton instance
76
+ export const projectStore = createProjectStore();
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,114 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { createProjectStore } from './projectStore.js';
3
+ const sampleProjects = [
4
+ {
5
+ id: '1',
6
+ name: 'TLC',
7
+ description: 'Test-Led Coding framework',
8
+ phase: { current: 33, total: 40, name: 'Multi-Model Router' },
9
+ tests: { passing: 1180, failing: 20, total: 1200 },
10
+ coverage: 87,
11
+ lastActivity: '2 min ago',
12
+ },
13
+ {
14
+ id: '2',
15
+ name: 'Other Project',
16
+ description: 'Another project',
17
+ phase: { current: 1, total: 5, name: 'Setup' },
18
+ tests: { passing: 10, failing: 0, total: 10 },
19
+ coverage: 100,
20
+ lastActivity: '1 hour ago',
21
+ },
22
+ ];
23
+ describe('projectStore', () => {
24
+ let store;
25
+ beforeEach(() => {
26
+ store = createProjectStore();
27
+ });
28
+ describe('Projects List', () => {
29
+ it('starts with empty projects', () => {
30
+ expect(store.getState().projects).toEqual([]);
31
+ });
32
+ it('sets projects', () => {
33
+ store.getState().setProjects(sampleProjects);
34
+ expect(store.getState().projects).toEqual(sampleProjects);
35
+ });
36
+ it('adds a project', () => {
37
+ store.getState().addProject(sampleProjects[0]);
38
+ expect(store.getState().projects).toHaveLength(1);
39
+ });
40
+ it('removes a project', () => {
41
+ store.getState().setProjects(sampleProjects);
42
+ store.getState().removeProject('1');
43
+ expect(store.getState().projects).toHaveLength(1);
44
+ expect(store.getState().projects[0].id).toBe('2');
45
+ });
46
+ it('updates a project', () => {
47
+ store.getState().setProjects(sampleProjects);
48
+ store.getState().updateProject('1', { coverage: 95 });
49
+ expect(store.getState().projects[0].coverage).toBe(95);
50
+ });
51
+ });
52
+ describe('Selected Project', () => {
53
+ it('starts with no selected project', () => {
54
+ expect(store.getState().selectedProject).toBeNull();
55
+ });
56
+ it('selects project by ID', () => {
57
+ store.getState().setProjects(sampleProjects);
58
+ store.getState().selectProject('1');
59
+ expect(store.getState().selectedProject?.id).toBe('1');
60
+ });
61
+ it('clears selection', () => {
62
+ store.getState().setProjects(sampleProjects);
63
+ store.getState().selectProject('1');
64
+ store.getState().clearSelection();
65
+ expect(store.getState().selectedProject).toBeNull();
66
+ });
67
+ it('returns null for non-existent ID', () => {
68
+ store.getState().setProjects(sampleProjects);
69
+ store.getState().selectProject('nonexistent');
70
+ expect(store.getState().selectedProject).toBeNull();
71
+ });
72
+ });
73
+ describe('Loading State', () => {
74
+ it('starts not loading', () => {
75
+ expect(store.getState().loading).toBe(false);
76
+ });
77
+ it('sets loading state', () => {
78
+ store.getState().setLoading(true);
79
+ expect(store.getState().loading).toBe(true);
80
+ });
81
+ });
82
+ describe('Error State', () => {
83
+ it('starts with no error', () => {
84
+ expect(store.getState().error).toBeNull();
85
+ });
86
+ it('sets error', () => {
87
+ store.getState().setError('Failed to load');
88
+ expect(store.getState().error).toBe('Failed to load');
89
+ });
90
+ it('clears error', () => {
91
+ store.getState().setError('Error');
92
+ store.getState().clearError();
93
+ expect(store.getState().error).toBeNull();
94
+ });
95
+ });
96
+ describe('Filtering', () => {
97
+ it('filters by search query', () => {
98
+ store.getState().setProjects(sampleProjects);
99
+ const filtered = store.getState().getFilteredProjects('TLC');
100
+ expect(filtered).toHaveLength(1);
101
+ expect(filtered[0].name).toBe('TLC');
102
+ });
103
+ it('returns all when no filter', () => {
104
+ store.getState().setProjects(sampleProjects);
105
+ const filtered = store.getState().getFilteredProjects('');
106
+ expect(filtered).toHaveLength(2);
107
+ });
108
+ it('is case insensitive', () => {
109
+ store.getState().setProjects(sampleProjects);
110
+ const filtered = store.getState().getFilteredProjects('tlc');
111
+ expect(filtered).toHaveLength(1);
112
+ });
113
+ });
114
+ });
@@ -0,0 +1,29 @@
1
+ export type Theme = 'dark' | 'light';
2
+ export type ViewName = 'projects' | 'tasks' | 'chat' | 'agents' | 'preview' | 'logs' | 'github' | 'health' | 'router' | 'settings';
3
+ export interface UIState {
4
+ theme: Theme;
5
+ sidebarOpen: boolean;
6
+ activeView: ViewName;
7
+ commandPaletteOpen: boolean;
8
+ helpOpen: boolean;
9
+ isMobile: boolean;
10
+ }
11
+ export interface UIActions {
12
+ toggleTheme: () => void;
13
+ setTheme: (theme: Theme) => void;
14
+ toggleSidebar: () => void;
15
+ setSidebarOpen: (open: boolean) => void;
16
+ setActiveView: (view: ViewName | string) => void;
17
+ openCommandPalette: () => void;
18
+ closeCommandPalette: () => void;
19
+ toggleCommandPalette: () => void;
20
+ toggleHelp: () => void;
21
+ setMobile: (isMobile: boolean) => void;
22
+ }
23
+ export interface UIStore {
24
+ getState: () => UIState & UIActions;
25
+ setState: (partial: Partial<UIState>) => void;
26
+ subscribe: (listener: (state: UIState & UIActions) => void) => () => void;
27
+ }
28
+ export declare function createUIStore(): UIStore;
29
+ export declare const uiStore: UIStore;
@@ -0,0 +1,72 @@
1
+ // UI Store - manages UI state like theme, sidebar, active view
2
+ // Using a simple store pattern (compatible with Zustand interface)
3
+ export function createUIStore() {
4
+ let state = {
5
+ theme: 'dark',
6
+ sidebarOpen: true,
7
+ activeView: 'projects',
8
+ commandPaletteOpen: false,
9
+ helpOpen: false,
10
+ isMobile: false,
11
+ };
12
+ const listeners = new Set();
13
+ const notify = () => {
14
+ const fullState = { ...state, ...actions };
15
+ listeners.forEach(listener => listener(fullState));
16
+ };
17
+ const actions = {
18
+ toggleTheme: () => {
19
+ state = { ...state, theme: state.theme === 'dark' ? 'light' : 'dark' };
20
+ notify();
21
+ },
22
+ setTheme: (theme) => {
23
+ state = { ...state, theme };
24
+ notify();
25
+ },
26
+ toggleSidebar: () => {
27
+ state = { ...state, sidebarOpen: !state.sidebarOpen };
28
+ notify();
29
+ },
30
+ setSidebarOpen: (open) => {
31
+ state = { ...state, sidebarOpen: open };
32
+ notify();
33
+ },
34
+ setActiveView: (view) => {
35
+ state = { ...state, activeView: view };
36
+ notify();
37
+ },
38
+ openCommandPalette: () => {
39
+ state = { ...state, commandPaletteOpen: true };
40
+ notify();
41
+ },
42
+ closeCommandPalette: () => {
43
+ state = { ...state, commandPaletteOpen: false };
44
+ notify();
45
+ },
46
+ toggleCommandPalette: () => {
47
+ state = { ...state, commandPaletteOpen: !state.commandPaletteOpen };
48
+ notify();
49
+ },
50
+ toggleHelp: () => {
51
+ state = { ...state, helpOpen: !state.helpOpen };
52
+ notify();
53
+ },
54
+ setMobile: (isMobile) => {
55
+ state = { ...state, isMobile };
56
+ notify();
57
+ },
58
+ };
59
+ return {
60
+ getState: () => ({ ...state, ...actions }),
61
+ setState: (partial) => {
62
+ state = { ...state, ...partial };
63
+ notify();
64
+ },
65
+ subscribe: (listener) => {
66
+ listeners.add(listener);
67
+ return () => listeners.delete(listener);
68
+ },
69
+ };
70
+ }
71
+ // Default singleton instance
72
+ export const uiStore = createUIStore();
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { createUIStore } from './uiStore.js';
3
+ describe('uiStore', () => {
4
+ let store;
5
+ beforeEach(() => {
6
+ store = createUIStore();
7
+ });
8
+ describe('Theme', () => {
9
+ it('defaults to dark theme', () => {
10
+ expect(store.getState().theme).toBe('dark');
11
+ });
12
+ it('toggles theme from dark to light', () => {
13
+ store.getState().toggleTheme();
14
+ expect(store.getState().theme).toBe('light');
15
+ });
16
+ it('toggles theme from light to dark', () => {
17
+ store.getState().setTheme('light');
18
+ store.getState().toggleTheme();
19
+ expect(store.getState().theme).toBe('dark');
20
+ });
21
+ it('sets theme directly', () => {
22
+ store.getState().setTheme('light');
23
+ expect(store.getState().theme).toBe('light');
24
+ });
25
+ });
26
+ describe('Sidebar', () => {
27
+ it('defaults to sidebar open', () => {
28
+ expect(store.getState().sidebarOpen).toBe(true);
29
+ });
30
+ it('toggles sidebar', () => {
31
+ store.getState().toggleSidebar();
32
+ expect(store.getState().sidebarOpen).toBe(false);
33
+ });
34
+ it('sets sidebar state directly', () => {
35
+ store.getState().setSidebarOpen(false);
36
+ expect(store.getState().sidebarOpen).toBe(false);
37
+ });
38
+ });
39
+ describe('Active View', () => {
40
+ it('defaults to projects view', () => {
41
+ expect(store.getState().activeView).toBe('projects');
42
+ });
43
+ it('sets active view', () => {
44
+ store.getState().setActiveView('tasks');
45
+ expect(store.getState().activeView).toBe('tasks');
46
+ });
47
+ it('accepts valid view names', () => {
48
+ const views = ['projects', 'tasks', 'chat', 'agents', 'logs', 'settings'];
49
+ views.forEach(view => {
50
+ store.getState().setActiveView(view);
51
+ expect(store.getState().activeView).toBe(view);
52
+ });
53
+ });
54
+ });
55
+ describe('Command Palette', () => {
56
+ it('defaults to command palette closed', () => {
57
+ expect(store.getState().commandPaletteOpen).toBe(false);
58
+ });
59
+ it('opens command palette', () => {
60
+ store.getState().openCommandPalette();
61
+ expect(store.getState().commandPaletteOpen).toBe(true);
62
+ });
63
+ it('closes command palette', () => {
64
+ store.getState().openCommandPalette();
65
+ store.getState().closeCommandPalette();
66
+ expect(store.getState().commandPaletteOpen).toBe(false);
67
+ });
68
+ it('toggles command palette', () => {
69
+ store.getState().toggleCommandPalette();
70
+ expect(store.getState().commandPaletteOpen).toBe(true);
71
+ store.getState().toggleCommandPalette();
72
+ expect(store.getState().commandPaletteOpen).toBe(false);
73
+ });
74
+ });
75
+ describe('Help Modal', () => {
76
+ it('defaults to help modal closed', () => {
77
+ expect(store.getState().helpOpen).toBe(false);
78
+ });
79
+ it('toggles help modal', () => {
80
+ store.getState().toggleHelp();
81
+ expect(store.getState().helpOpen).toBe(true);
82
+ });
83
+ });
84
+ describe('Mobile Mode', () => {
85
+ it('defaults to desktop mode', () => {
86
+ expect(store.getState().isMobile).toBe(false);
87
+ });
88
+ it('sets mobile mode', () => {
89
+ store.getState().setMobile(true);
90
+ expect(store.getState().isMobile).toBe(true);
91
+ });
92
+ });
93
+ });
@@ -1,10 +1,10 @@
1
1
  {
2
- "name": "tdd-dashboard",
2
+ "name": "tlc-dashboard",
3
3
  "version": "0.1.0",
4
- "description": "TUI dashboard for TDD workflow",
4
+ "description": "TUI dashboard for TLC (Test-Led Coding) workflow",
5
5
  "type": "module",
6
6
  "bin": {
7
- "tdd-dashboard": "./dist/index.js"
7
+ "tlc-dashboard": "./dist/index.js"
8
8
  },
9
9
  "scripts": {
10
10
  "dev": "tsx src/index.tsx",
@@ -99,7 +99,12 @@ services:
99
99
  image: node:20-alpine
100
100
  container_name: tlc-${COMPOSE_PROJECT_NAME:-dev}-dashboard
101
101
  working_dir: /project
102
- command: sh -c "cd /tlc/server && npm install && cd /project && node /tlc/server/index.js --proxy-only"
102
+ command: >
103
+ sh -c "
104
+ cd /tlc/dashboard && npm install && npm run build &&
105
+ cd /tlc/server && npm install &&
106
+ cd /project && node /tlc/server/index.js --proxy-only
107
+ "
103
108
  environment:
104
109
  - TLC_PORT=3147
105
110
  - TLC_PROXY_ONLY=true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tlc-claude-code",
3
- "version": "1.4.1",
3
+ "version": "1.4.4",
4
4
  "description": "TLC - Test Led Coding for Claude Code",
5
5
  "bin": {
6
6
  "tlc": "./bin/tlc.js",
@@ -33,7 +33,9 @@
33
33
  "docs": "node scripts/docs-update.js",
34
34
  "docs:check": "node scripts/docs-update.js --check",
35
35
  "docs:screenshots": "node scripts/generate-screenshots.js",
36
- "docs:capture": "node scripts/capture-screenshots.js"
36
+ "docs:capture": "node scripts/capture-screenshots.js",
37
+ "test:e2e": "npx playwright test",
38
+ "test:e2e:ui": "npx playwright test --ui"
37
39
  },
38
40
  "repository": {
39
41
  "type": "git",
@@ -50,6 +52,7 @@
50
52
  "author": "Jurgen Calleja",
51
53
  "license": "MIT",
52
54
  "devDependencies": {
55
+ "@playwright/test": "^1.58.1",
53
56
  "playwright": "^1.58.1",
54
57
  "text-to-image": "^8.0.1"
55
58
  }