@assistkick/create 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/package.json +2 -1
  2. package/templates/assistkick-product-system/GITHUB_APP_SETUP.md +88 -0
  3. package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +231 -0
  4. package/templates/assistkick-product-system/packages/backend/src/routes/kanban.ts +4 -4
  5. package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +49 -2
  6. package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +82 -0
  7. package/templates/assistkick-product-system/packages/backend/src/server.ts +19 -6
  8. package/templates/assistkick-product-system/packages/backend/src/services/github_app_service.ts +146 -0
  9. package/templates/assistkick-product-system/packages/backend/src/services/init.ts +69 -2
  10. package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +71 -0
  11. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +87 -0
  12. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +194 -0
  13. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +88 -17
  14. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +114 -39
  15. package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +28 -14
  16. package/templates/assistkick-product-system/packages/frontend/src/App.tsx +1 -1
  17. package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +151 -0
  18. package/templates/assistkick-product-system/packages/frontend/src/components/GitRepoModal.tsx +352 -0
  19. package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +208 -95
  20. package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +17 -1
  21. package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +238 -105
  22. package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +15 -13
  23. package/templates/assistkick-product-system/packages/frontend/src/constants/graph.ts +1 -0
  24. package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +4 -0
  25. package/templates/assistkick-product-system/packages/frontend/src/routes/dashboard.tsx +22 -4
  26. package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +486 -38
  27. package/templates/assistkick-product-system/packages/shared/db/migrations/0001_vengeful_wallop.sql +1 -0
  28. package/templates/assistkick-product-system/packages/shared/db/migrations/0002_greedy_excalibur.sql +4 -0
  29. package/templates/assistkick-product-system/packages/shared/db/migrations/0003_lonely_cyclops.sql +17 -0
  30. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0001_snapshot.json +826 -0
  31. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0002_snapshot.json +854 -0
  32. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0003_snapshot.json +862 -0
  33. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +21 -0
  34. package/templates/assistkick-product-system/packages/shared/db/schema.ts +10 -3
  35. package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +54 -1
  36. package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +25 -0
  37. package/templates/assistkick-product-system/packages/shared/lib/pipeline-state-store.ts +4 -0
  38. package/templates/assistkick-product-system/packages/shared/lib/pipeline.ts +329 -89
  39. package/templates/assistkick-product-system/packages/shared/lib/pipeline_orchestrator.ts +186 -0
  40. package/templates/assistkick-product-system/packages/shared/lib/session.ts +10 -6
  41. package/templates/assistkick-product-system/packages/shared/tools/db_explorer.ts +275 -0
  42. package/templates/assistkick-product-system/packages/shared/tools/end_session.ts +2 -2
  43. package/templates/assistkick-product-system/packages/shared/tools/get_kanban.ts +2 -1
  44. package/templates/assistkick-product-system/packages/shared/tools/move_card.ts +3 -2
  45. package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -2
  46. package/templates/assistkick-product-system/tests/kanban.test.ts +1 -1
  47. package/templates/assistkick-product-system/tests/pipeline_stats_all_cards.test.ts +1 -1
  48. package/templates/assistkick-product-system/tests/web_terminal.test.ts +189 -150
  49. package/templates/skills/assistkick-bootstrap/SKILL.md +33 -25
  50. package/templates/skills/assistkick-code-reviewer/SKILL.md +23 -15
  51. package/templates/skills/assistkick-db-explorer/SKILL.md +86 -0
  52. package/templates/skills/assistkick-debugger/SKILL.md +30 -22
  53. package/templates/skills/assistkick-developer/SKILL.md +37 -29
  54. package/templates/skills/assistkick-interview/SKILL.md +34 -26
@@ -1,6 +1,8 @@
1
1
  /**
2
- * Terminal view component embeds an xterm.js terminal connected
3
- * to the backend PTY via WebSocket. Admin-only access.
2
+ * Terminal view — multi-session manager with chat-like layout.
3
+ * Left sidebar lists all terminal sessions; right panel shows the active session's terminal.
4
+ * Each session is permanently bound to a project chosen at creation time.
5
+ * Admin-only access.
4
6
  */
5
7
 
6
8
  import React, { useEffect, useRef, useCallback, useState } from 'react';
@@ -8,78 +10,80 @@ import { Terminal } from '@xterm/xterm';
8
10
  import { FitAddon } from '@xterm/addon-fit';
9
11
  import { WebLinksAddon } from '@xterm/addon-web-links';
10
12
  import '@xterm/xterm/css/xterm.css';
13
+ import { apiClient } from '../api/client';
14
+ import type { Project } from '../hooks/useProjects';
11
15
 
12
16
  interface TerminalViewProps {
13
17
  visible: boolean;
18
+ projects: Project[];
19
+ }
20
+
21
+ interface SessionInfo {
22
+ id: string;
23
+ name: string;
24
+ projectId: string;
25
+ projectName: string;
26
+ state: 'idle' | 'running';
27
+ createdAt: string;
14
28
  }
15
29
 
16
30
  type ConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'error';
17
31
 
18
- export function TerminalView({ visible }: TerminalViewProps) {
32
+ interface ActiveConnection {
33
+ sessionId: string;
34
+ ws: WebSocket;
35
+ terminal: Terminal;
36
+ fitAddon: FitAddon;
37
+ }
38
+
39
+ export function TerminalView({ visible, projects }: TerminalViewProps) {
19
40
  const containerRef = useRef<HTMLDivElement>(null);
20
- const terminalRef = useRef<Terminal | null>(null);
21
- const fitAddonRef = useRef<FitAddon | null>(null);
22
- const wsRef = useRef<WebSocket | null>(null);
41
+ const connectionRef = useRef<ActiveConnection | null>(null);
42
+ const [sessions, setSessions] = useState<SessionInfo[]>([]);
43
+ const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
23
44
  const [status, setStatus] = useState<ConnectionStatus>('disconnected');
24
45
  const [errorMsg, setErrorMsg] = useState('');
46
+ const [newSessionProjectId, setNewSessionProjectId] = useState<string>('');
47
+ const [creating, setCreating] = useState(false);
25
48
 
26
- const connect = useCallback(() => {
27
- if (wsRef.current?.readyState === WebSocket.OPEN) return;
28
-
29
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
30
- const wsUrl = `${protocol}//${window.location.host}/api/terminal`;
31
-
32
- setStatus('connecting');
33
- setErrorMsg('');
34
-
35
- const ws = new WebSocket(wsUrl);
36
- wsRef.current = ws;
37
-
38
- ws.onopen = () => {
39
- setStatus('connected');
40
-
41
- // Send initial resize
42
- if (terminalRef.current && fitAddonRef.current) {
43
- fitAddonRef.current.fit();
44
- const { cols, rows } = terminalRef.current;
45
- ws.send(JSON.stringify({ type: 'resize', cols, rows }));
46
- }
47
- };
49
+ // Default new session project picker to first project
50
+ useEffect(() => {
51
+ if (projects.length > 0 && !newSessionProjectId) {
52
+ setNewSessionProjectId(projects[0].id);
53
+ }
54
+ }, [projects, newSessionProjectId]);
48
55
 
49
- ws.onmessage = (event) => {
50
- try {
51
- const msg = JSON.parse(event.data);
52
- if (msg.type === 'output' && terminalRef.current) {
53
- terminalRef.current.write(msg.data);
54
- }
55
- } catch {
56
- // Ignore malformed messages
57
- }
58
- };
56
+ const fetchSessions = useCallback(async () => {
57
+ try {
58
+ const data = await apiClient.listTerminalSessions();
59
+ setSessions(data.sessions);
60
+ return data.sessions as SessionInfo[];
61
+ } catch {
62
+ return [] as SessionInfo[];
63
+ }
64
+ }, []);
59
65
 
60
- ws.onclose = (event) => {
61
- wsRef.current = null;
62
- if (event.code === 4001) {
63
- setStatus('error');
64
- setErrorMsg('Authentication required. Please log in.');
65
- } else if (event.code === 4003) {
66
- setStatus('error');
67
- setErrorMsg('Access denied. Admin privileges required.');
68
- } else {
69
- setStatus('disconnected');
70
- }
71
- };
66
+ // Load sessions when tab becomes visible
67
+ useEffect(() => {
68
+ if (visible) {
69
+ fetchSessions();
70
+ }
71
+ }, [visible, fetchSessions]);
72
72
 
73
- ws.onerror = () => {
74
- setStatus('error');
75
- setErrorMsg('Failed to connect to terminal.');
76
- wsRef.current = null;
77
- };
73
+ const disconnectCurrent = useCallback(() => {
74
+ const conn = connectionRef.current;
75
+ if (!conn) return;
76
+ conn.ws.close();
77
+ conn.terminal.dispose();
78
+ connectionRef.current = null;
79
+ setStatus('disconnected');
80
+ setErrorMsg('');
78
81
  }, []);
79
82
 
80
- // Initialize terminal
81
- useEffect(() => {
82
- if (!containerRef.current || terminalRef.current) return;
83
+ const connectToSession = useCallback((sessionId: string) => {
84
+ disconnectCurrent();
85
+
86
+ if (!containerRef.current) return;
83
87
 
84
88
  const terminal = new Terminal({
85
89
  cursorBlink: true,
@@ -111,55 +115,129 @@ export function TerminalView({ visible }: TerminalViewProps) {
111
115
 
112
116
  const fitAddon = new FitAddon();
113
117
  const webLinksAddon = new WebLinksAddon();
114
-
115
118
  terminal.loadAddon(fitAddon);
116
119
  terminal.loadAddon(webLinksAddon);
117
120
  terminal.open(containerRef.current);
118
-
119
121
  fitAddon.fit();
120
- terminalRef.current = terminal;
121
- fitAddonRef.current = fitAddon;
122
+
123
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
124
+ const wsUrl = `${protocol}//${window.location.host}/api/terminal?sessionId=${encodeURIComponent(sessionId)}`;
125
+
126
+ setStatus('connecting');
127
+ setErrorMsg('');
128
+
129
+ const ws = new WebSocket(wsUrl);
130
+ connectionRef.current = { sessionId, ws, terminal, fitAddon };
131
+
132
+ ws.onopen = () => {
133
+ setStatus('connected');
134
+ fitAddon.fit();
135
+ const { cols, rows } = terminal;
136
+ ws.send(JSON.stringify({ type: 'resize', cols, rows }));
137
+ };
138
+
139
+ ws.onmessage = (event) => {
140
+ try {
141
+ const msg = JSON.parse(event.data);
142
+ if (msg.type === 'output') {
143
+ terminal.write(msg.data);
144
+ }
145
+ } catch {
146
+ // Ignore malformed messages
147
+ }
148
+ };
149
+
150
+ ws.onclose = (event) => {
151
+ if (connectionRef.current?.sessionId === sessionId) {
152
+ connectionRef.current = null;
153
+ }
154
+ if (event.code === 4001) {
155
+ setStatus('error');
156
+ setErrorMsg('Authentication required. Please log in.');
157
+ } else if (event.code === 4003) {
158
+ setStatus('error');
159
+ setErrorMsg('Access denied. Admin privileges required.');
160
+ } else if (event.code === 4004) {
161
+ setStatus('error');
162
+ setErrorMsg('Session not found. It may have been killed.');
163
+ fetchSessions();
164
+ } else {
165
+ setStatus('disconnected');
166
+ }
167
+ };
168
+
169
+ ws.onerror = () => {
170
+ setStatus('error');
171
+ setErrorMsg('Failed to connect to terminal session.');
172
+ if (connectionRef.current?.sessionId === sessionId) {
173
+ connectionRef.current = null;
174
+ }
175
+ };
122
176
 
123
177
  // Forward user input to WebSocket
124
178
  terminal.onData((data) => {
125
- const ws = wsRef.current;
126
- if (ws?.readyState === WebSocket.OPEN) {
179
+ if (ws.readyState === WebSocket.OPEN) {
127
180
  ws.send(JSON.stringify({ type: 'input', data }));
128
181
  }
129
182
  });
183
+ }, [disconnectCurrent, fetchSessions]);
130
184
 
131
- return () => {
132
- terminal.dispose();
133
- terminalRef.current = null;
134
- fitAddonRef.current = null;
135
- };
136
- }, []);
185
+ const handleSelectSession = useCallback((sessionId: string) => {
186
+ setActiveSessionId(sessionId);
187
+ connectToSession(sessionId);
188
+ }, [connectToSession]);
137
189
 
138
- // Connect when visible
139
- useEffect(() => {
140
- if (visible && status === 'disconnected') {
141
- connect();
190
+ const handleCreateSession = useCallback(async () => {
191
+ const projectId = newSessionProjectId || projects[0]?.id;
192
+ if (!projectId) return;
193
+
194
+ const project = projects.find(p => p.id === projectId) || projects[0];
195
+ if (!project) return;
196
+
197
+ setCreating(true);
198
+ try {
199
+ const data = await apiClient.createTerminalSession(project.id, project.name);
200
+ const newSession: SessionInfo = data.session;
201
+ const updated = await fetchSessions();
202
+ const created = updated.find(s => s.id === newSession.id) || newSession;
203
+ setActiveSessionId(created.id);
204
+ connectToSession(created.id);
205
+ } catch {
206
+ // silently ignore — session list stays as-is
207
+ } finally {
208
+ setCreating(false);
142
209
  }
143
- }, [visible, status, connect]);
210
+ }, [newSessionProjectId, projects, fetchSessions, connectToSession]);
144
211
 
145
- // Handle resize
212
+ const handleKillSession = useCallback(async (sessionId: string, e: React.MouseEvent) => {
213
+ e.stopPropagation();
214
+ try {
215
+ await apiClient.killTerminalSession(sessionId);
216
+ if (activeSessionId === sessionId) {
217
+ disconnectCurrent();
218
+ setActiveSessionId(null);
219
+ }
220
+ await fetchSessions();
221
+ } catch {
222
+ // silently ignore
223
+ }
224
+ }, [activeSessionId, disconnectCurrent, fetchSessions]);
225
+
226
+ // Handle window resize — refit when visible
146
227
  useEffect(() => {
147
228
  if (!visible) return;
148
229
 
149
230
  const handleResize = () => {
150
- if (fitAddonRef.current && terminalRef.current) {
151
- fitAddonRef.current.fit();
152
- const { cols, rows } = terminalRef.current;
153
- const ws = wsRef.current;
154
- if (ws?.readyState === WebSocket.OPEN) {
155
- ws.send(JSON.stringify({ type: 'resize', cols, rows }));
156
- }
231
+ const conn = connectionRef.current;
232
+ if (!conn) return;
233
+ conn.fitAddon.fit();
234
+ const { cols, rows } = conn.terminal;
235
+ if (conn.ws.readyState === WebSocket.OPEN) {
236
+ conn.ws.send(JSON.stringify({ type: 'resize', cols, rows }));
157
237
  }
158
238
  };
159
239
 
160
- // Fit on visibility change
161
240
  handleResize();
162
-
163
241
  window.addEventListener('resize', handleResize);
164
242
  return () => window.removeEventListener('resize', handleResize);
165
243
  }, [visible]);
@@ -167,34 +245,89 @@ export function TerminalView({ visible }: TerminalViewProps) {
167
245
  // Clean up WebSocket on unmount
168
246
  useEffect(() => {
169
247
  return () => {
170
- wsRef.current?.close();
171
- wsRef.current = null;
248
+ disconnectCurrent();
172
249
  };
173
- }, []);
250
+ }, [disconnectCurrent]);
251
+
252
+ const activeSession = sessions.find(s => s.id === activeSessionId);
174
253
 
175
254
  return (
176
255
  <div className="terminal-view" style={{ display: visible ? 'flex' : 'none' }}>
177
- {errorMsg && (
178
- <div className="terminal-error">
179
- <span>{errorMsg}</span>
180
- {status !== 'connecting' && (
181
- <button className="terminal-reconnect-btn" onClick={connect}>
182
- Reconnect
183
- </button>
256
+ {/* Left sidebar — session list */}
257
+ <div className="terminal-sidebar">
258
+ <div className="terminal-sidebar-header">Sessions</div>
259
+ <div className="terminal-session-list">
260
+ {sessions.length === 0 && (
261
+ <div className="terminal-no-sessions">No sessions yet</div>
184
262
  )}
263
+ {sessions.map(session => (
264
+ <div
265
+ key={session.id}
266
+ className={`terminal-session-item${session.id === activeSessionId ? ' terminal-session-item--active' : ''}`}
267
+ onClick={() => handleSelectSession(session.id)}
268
+ >
269
+ <div className="terminal-session-name">{session.name}</div>
270
+ <div className="terminal-session-project">{session.projectName}</div>
271
+ <button
272
+ className="terminal-session-kill"
273
+ onClick={(e) => handleKillSession(session.id, e)}
274
+ title="Kill session"
275
+ >
276
+
277
+ </button>
278
+ </div>
279
+ ))}
185
280
  </div>
186
- )}
187
- {status === 'connecting' && (
188
- <div className="terminal-status">Connecting...</div>
189
- )}
190
- <div ref={containerRef} className="terminal-container" />
191
- {status === 'disconnected' && !errorMsg && (
192
- <div className="terminal-overlay">
193
- <button className="terminal-connect-btn" onClick={connect}>
194
- Connect to Terminal
281
+
282
+ {/* New session controls */}
283
+ <div className="terminal-new-session">
284
+ <select
285
+ className="terminal-project-picker"
286
+ value={newSessionProjectId}
287
+ onChange={e => setNewSessionProjectId(e.target.value)}
288
+ disabled={creating || projects.length === 0}
289
+ >
290
+ {projects.map(p => (
291
+ <option key={p.id} value={p.id}>{p.name}</option>
292
+ ))}
293
+ </select>
294
+ <button
295
+ className="terminal-new-session-btn"
296
+ onClick={handleCreateSession}
297
+ disabled={creating || projects.length === 0}
298
+ >
299
+ {creating ? '…' : '+ New'}
195
300
  </button>
196
301
  </div>
197
- )}
302
+ </div>
303
+
304
+ {/* Right panel — terminal content */}
305
+ <div className="terminal-panel">
306
+ {!activeSessionId && (
307
+ <div className="terminal-empty-state">
308
+ Select a session or create a new one
309
+ </div>
310
+ )}
311
+ {activeSession && errorMsg && (
312
+ <div className="terminal-error">
313
+ <span>{errorMsg}</span>
314
+ <button
315
+ className="terminal-reconnect-btn"
316
+ onClick={() => connectToSession(activeSessionId!)}
317
+ >
318
+ Reconnect
319
+ </button>
320
+ </div>
321
+ )}
322
+ {activeSession && status === 'connecting' && (
323
+ <div className="terminal-status">Connecting…</div>
324
+ )}
325
+ <div
326
+ ref={containerRef}
327
+ className="terminal-container"
328
+ style={{ display: activeSessionId ? 'flex' : 'none' }}
329
+ />
330
+ </div>
198
331
  </div>
199
332
  );
200
333
  }
@@ -19,32 +19,24 @@ interface ToolbarProps {
19
19
  onProjectCreate: (name: string) => Promise<any>;
20
20
  onProjectRename: (id: string, name: string) => Promise<void>;
21
21
  onProjectArchive: (id: string) => Promise<void>;
22
+ onOpenGitModal?: (project: Project) => void;
22
23
  }
23
24
 
24
25
  export function Toolbar({
25
26
  activeTab, onTabChange, completeness, onFit,
26
27
  onSettingsToggle, settingsOpen, theme, onThemeToggle, isAdmin, onLogout,
27
- projects, selectedProjectId, onProjectSelect, onProjectCreate, onProjectRename, onProjectArchive,
28
+ projects, selectedProjectId, onProjectSelect, onProjectCreate, onProjectRename, onProjectArchive, onOpenGitModal,
28
29
  }: ToolbarProps) {
29
- const tabs = ['graph', 'kanban', 'coherence'];
30
+ const tabs = ['kanban', 'terminal', 'graph', 'coherence'];
30
31
  if (isAdmin) {
31
32
  tabs.push('users');
32
- tabs.push('terminal');
33
33
  }
34
34
 
35
+ const tabLabels: Record<string, string> = { terminal: 'Chat' };
36
+
35
37
  return (
36
38
  <div className="toolbar">
37
39
  <div className="tab-bar">
38
- {tabs.map(tab => (
39
- <button
40
- key={tab}
41
- className={`tab-btn${activeTab === tab ? ' active' : ''}`}
42
- onClick={() => onTabChange(tab)}
43
- >
44
- {tab.charAt(0).toUpperCase() + tab.slice(1)}
45
- </button>
46
- ))}
47
- <div className="tab-bar-separator" />
48
40
  <ProjectSelector
49
41
  projects={projects}
50
42
  selectedProjectId={selectedProjectId}
@@ -52,7 +44,17 @@ export function Toolbar({
52
44
  onCreate={onProjectCreate}
53
45
  onRename={onProjectRename}
54
46
  onArchive={onProjectArchive}
47
+ onOpenGitModal={onOpenGitModal}
55
48
  />
49
+ {tabs.map(tab => (
50
+ <button
51
+ key={tab}
52
+ className={`tab-btn${activeTab === tab ? ' active' : ''}`}
53
+ onClick={() => onTabChange(tab)}
54
+ >
55
+ {tabLabels[tab] || tab.charAt(0).toUpperCase() + tab.slice(1)}
56
+ </button>
57
+ ))}
56
58
  </div>
57
59
 
58
60
  <div className="toolbar-spacer" />
@@ -169,6 +169,7 @@ export const ALL_NODE_TYPES = [
169
169
  ];
170
170
 
171
171
  export const COLUMNS = [
172
+ { id: 'backlog', label: 'Backlog' },
172
173
  { id: 'todo', label: 'Todo' },
173
174
  { id: 'in_progress', label: 'In Progress' },
174
175
  { id: 'in_review', label: 'In Review' },
@@ -8,6 +8,10 @@ export interface Project {
8
8
  name: string;
9
9
  isDefault: number;
10
10
  archivedAt: string | null;
11
+ repoUrl: string | null;
12
+ githubInstallationId: string | null;
13
+ githubRepoFullName: string | null;
14
+ baseBranch: string | null;
11
15
  createdAt: string;
12
16
  updatedAt: string;
13
17
  }
@@ -11,6 +11,7 @@ import { TerminalView } from '../components/TerminalView';
11
11
  import { UsersView } from '../components/UsersView';
12
12
  import { SidePanel, openSidePanel, closeSidePanel } from '../components/SidePanel';
13
13
  import { QaIssueSheet } from '../components/QaIssueSheet';
14
+ import { GitRepoModal } from '../components/GitRepoModal';
14
15
  import { useTheme } from '../hooks/useTheme';
15
16
  import { useGraph } from '../hooks/useGraph';
16
17
  import { useAuth } from '../hooks/useAuth';
@@ -22,7 +23,7 @@ const VALID_TABS = new Set(['graph', 'kanban', 'coherence', 'users', 'terminal']
22
23
 
23
24
  function tabFromPath(pathname: string): string {
24
25
  const seg = pathname.replace(/^\//, '');
25
- return VALID_TABS.has(seg) ? seg : 'graph';
26
+ return VALID_TABS.has(seg) ? seg : 'kanban';
26
27
  }
27
28
 
28
29
  export function DashboardRoute() {
@@ -33,7 +34,7 @@ export function DashboardRoute() {
33
34
  const isAdmin = user?.role === 'admin';
34
35
  const {
35
36
  projects, selectedProjectId, selectProject,
36
- createProject, renameProject, archiveProject,
37
+ createProject, renameProject, archiveProject, refetchProjects,
37
38
  } = useProjects();
38
39
  const { graphData, error: graphError } = useGraph(selectedProjectId);
39
40
  const graphRef = useRef<GraphViewHandle>(null);
@@ -42,6 +43,13 @@ export function DashboardRoute() {
42
43
  const [legendVisible, setLegendVisible] = useState(true);
43
44
  const [hiddenTypes, setHiddenTypes] = useState<Set<string>>(new Set());
44
45
 
46
+ // Git Repo Modal state
47
+ const [gitModalProject, setGitModalProject] = useState<any>(null);
48
+
49
+ const handleOpenGitModal = useCallback((project: any) => {
50
+ setGitModalProject(project);
51
+ }, []);
52
+
45
53
  // QA Issue Sheet state
46
54
  const [qaOpen, setQaOpen] = useState(false);
47
55
  const [qaFeatureId, setQaFeatureId] = useState<string | null>(null);
@@ -146,6 +154,7 @@ export function DashboardRoute() {
146
154
  onProjectCreate={createProject}
147
155
  onProjectRename={renameProject}
148
156
  onProjectArchive={archiveProject}
157
+ onOpenGitModal={handleOpenGitModal}
149
158
  />
150
159
 
151
160
  <div className="main-content">
@@ -196,8 +205,8 @@ export function DashboardRoute() {
196
205
  {/* Users View (admin-only) */}
197
206
  {isAdmin && <UsersView visible={activeTab === 'users'} />}
198
207
 
199
- {/* Terminal View (admin-only) */}
200
- {isAdmin && <TerminalView visible={activeTab === 'terminal'} />}
208
+ {/* Chat View */}
209
+ <TerminalView visible={activeTab === 'terminal'} projects={projects} />
201
210
 
202
211
  {/* Side Panel */}
203
212
  <SidePanel graphData={graphData} onEdgeClick={handleEdgeClick} />
@@ -215,6 +224,15 @@ export function DashboardRoute() {
215
224
  />
216
225
  </div>
217
226
 
227
+ {/* Git Repo Modal */}
228
+ {gitModalProject && (
229
+ <GitRepoModal
230
+ project={gitModalProject}
231
+ onClose={() => setGitModalProject(null)}
232
+ onProjectUpdated={refetchProjects}
233
+ />
234
+ )}
235
+
218
236
  </div>
219
237
  </ToastProvider>
220
238
  );