• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

apowers313 / aiforge / 21152598554

19 Jan 2026 10:00PM UTC coverage: 83.846% (+0.9%) from 82.966%
21152598554

push

github

apowers313
Merge branch 'master' of https://github.com/apowers313/aiforge

1604 of 1876 branches covered (85.5%)

Branch coverage included in aggregate %.

8242 of 9867 relevant lines covered (83.53%)

20.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.97
/src/client/stores/uiStore.ts
1
import { createWithEqualityFn } from 'zustand/traditional';
1✔
2
import { shallow } from 'zustand/shallow';
1✔
3
import { DEFAULT_TERMINAL_THEME_ID } from '@shared/terminalThemes';
1✔
4
import type { ContextType, ContextSidebarTab } from '@shared/types';
5

6
interface WorkspaceStateUpdate {
7
  sidebarCollapsed?: boolean;
8
  sidebarWidth?: number;
9
  expandedProjectIds?: string[];
10
  activeShellId?: string | null;
11
  terminalFontSize?: number;
12
  terminalTheme?: string;
13
  contextSidebarPinned?: boolean;
14
  contextSidebarWidth?: number;
15
}
16

17
interface UIState {
18
  // Sidebar state
19
  sidebarCollapsed: boolean;
20
  sidebarWidth: number;
21
  expandedProjectIds: string[];
22

23
  // Modal state
24
  addProjectModalOpen: boolean;
25

26
  // Selection state (UI-only, no server sync needed)
27
  selectedProjectId: string | null;
28
  activeShellId: string | null;
29

30
  // Terminal settings
31
  terminalFontSize: number;
32
  terminalTheme: string;
33

34
  // Shell activity tracking (for AI shell activity indicator)
35
  // Maps shellId -> timestamp of last activity (input or output)
36
  shellActivityTimestamps: Record<string, number>;
37

38
  // Context sidebar state
39
  contextSidebarOpen: boolean;
40
  contextSidebarPinned: boolean;
41
  contextSidebarWidth: number;
42
  contextSidebarActiveTab: ContextSidebarTab;
43
  selectedContextType: ContextType | null;
44
  selectedContextId: string | null;
45

46
  // File preview state (shown in center panel)
47
  previewProjectId: string | null;
48
  previewFilePath: string | null;
49

50
  // Actions
51
  toggleSidebar: () => void;
52
  setSidebarCollapsed: (collapsed: boolean) => void;
53
  setSidebarWidth: (width: number) => void;
54
  openAddProjectModal: () => void;
55
  closeAddProjectModal: () => void;
56
  toggleProjectExpanded: (projectId: string) => void;
57
  setProjectExpanded: (projectId: string, expanded: boolean) => void;
58
  setSelectedProject: (id: string | null) => void;
59
  setActiveShell: (id: string | null) => void;
60
  setTerminalFontSize: (size: number) => void;
61
  setTerminalTheme: (themeId: string) => void;
62
  setWorkspaceState: (state: WorkspaceStateUpdate) => void;
63
  recordShellActivity: (shellId: string) => void;
64
  getShellActivityTimestamp: (shellId: string) => number | undefined;
65

66
  // Context sidebar actions
67
  openContextSidebar: (type: ContextType, id: string) => void;
68
  closeContextSidebar: () => void;
69
  toggleContextSidebar: (type: ContextType, id: string) => void;
70
  toggleContextSidebarPin: () => void;
71
  setContextSidebarWidth: (width: number) => void;
72
  setContextSidebarTab: (tab: ContextSidebarTab) => void;
73

74
  // File preview actions
75
  openFilePreview: (projectId: string, filePath: string) => void;
76
  closeFilePreview: () => void;
77

78
  reset: () => void;
79
}
80

81
const DEFAULT_TERMINAL_FONT_SIZE = 14;
1✔
82
const DEFAULT_SIDEBAR_WIDTH = 280;
1✔
83
const MIN_SIDEBAR_WIDTH = 180;
1✔
84
const MAX_SIDEBAR_WIDTH = 600;
1✔
85

86
// Context sidebar constants
87
const DEFAULT_CONTEXT_SIDEBAR_WIDTH = 320;
1✔
88
const MIN_CONTEXT_SIDEBAR_WIDTH = 280;
1✔
89
const MAX_CONTEXT_SIDEBAR_WIDTH = 500;
1✔
90

91
const initialState = {
1✔
92
  sidebarCollapsed: false,
1✔
93
  sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
1✔
94
  addProjectModalOpen: false,
1✔
95
  expandedProjectIds: [] as string[],
1✔
96
  selectedProjectId: null as string | null,
1✔
97
  activeShellId: null as string | null,
1✔
98
  terminalFontSize: DEFAULT_TERMINAL_FONT_SIZE,
1✔
99
  terminalTheme: DEFAULT_TERMINAL_THEME_ID,
1✔
100
  shellActivityTimestamps: {} as Record<string, number>,
1✔
101
  // Context sidebar initial state
102
  contextSidebarOpen: false,
1✔
103
  contextSidebarPinned: false,
1✔
104
  contextSidebarWidth: DEFAULT_CONTEXT_SIDEBAR_WIDTH,
1✔
105
  contextSidebarActiveTab: 'urls' as ContextSidebarTab,
1✔
106
  selectedContextType: null as ContextType | null,
1✔
107
  selectedContextId: null as string | null,
1✔
108
  // File preview initial state
109
  previewProjectId: null as string | null,
1✔
110
  previewFilePath: null as string | null,
1✔
111
};
1✔
112

113
export const useUIStore = createWithEqualityFn<UIState>()((set) => ({
1✔
114
  ...initialState,
13✔
115

116
  toggleSidebar: (): void => {
13✔
117
    set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed }));
6✔
118
  },
6✔
119

120
  setSidebarCollapsed: (collapsed: boolean): void => {
13✔
121
    set({ sidebarCollapsed: collapsed });
2✔
122
  },
2✔
123

124
  setSidebarWidth: (width: number): void => {
13✔
125
    const clampedWidth = Math.min(MAX_SIDEBAR_WIDTH, Math.max(MIN_SIDEBAR_WIDTH, width));
20✔
126
    set({ sidebarWidth: clampedWidth });
20✔
127
  },
20✔
128

129
  openAddProjectModal: (): void => {
13✔
130
    set({ addProjectModalOpen: true });
7✔
131
  },
7✔
132

133
  closeAddProjectModal: (): void => {
13✔
134
    set({ addProjectModalOpen: false });
3✔
135
  },
3✔
136

137
  toggleProjectExpanded: (projectId: string): void => {
13✔
138
    set((state) => {
8✔
139
      const index = state.expandedProjectIds.indexOf(projectId);
8✔
140
      if (index >= 0) {
8✔
141
        return {
1✔
142
          expandedProjectIds: state.expandedProjectIds.filter((id) => id !== projectId),
1✔
143
        };
1✔
144
      }
1✔
145
      return {
7✔
146
        expandedProjectIds: [...state.expandedProjectIds, projectId],
7✔
147
      };
7✔
148
    });
8✔
149
  },
8✔
150

151
  setProjectExpanded: (projectId: string, expanded: boolean): void => {
13✔
152
    set((state) => {
5✔
153
      const isCurrentlyExpanded = state.expandedProjectIds.includes(projectId);
5✔
154
      if (expanded && !isCurrentlyExpanded) {
5✔
155
        return {
2✔
156
          expandedProjectIds: [...state.expandedProjectIds, projectId],
2✔
157
        };
2✔
158
      }
2✔
159
      if (!expanded && isCurrentlyExpanded) {
5✔
160
        return {
1✔
161
          expandedProjectIds: state.expandedProjectIds.filter((id) => id !== projectId),
1✔
162
        };
1✔
163
      }
1✔
164
      return {};
2✔
165
    });
5✔
166
  },
5✔
167

168
  setSelectedProject: (id: string | null): void => {
13✔
169
    set({ selectedProjectId: id });
2✔
170
  },
2✔
171

172
  setActiveShell: (id: string | null): void => {
13✔
173
    // Selecting a shell clears any file preview
174
    set({
14✔
175
      activeShellId: id,
14✔
176
      previewProjectId: null,
14✔
177
      previewFilePath: null,
14✔
178
    });
14✔
179
  },
14✔
180

181
  setTerminalFontSize: (size: number): void => {
13✔
182
    set({ terminalFontSize: size });
×
183
  },
×
184

185
  setTerminalTheme: (themeId: string): void => {
13✔
186
    set({ terminalTheme: themeId });
×
187
  },
×
188

189
  setWorkspaceState: (state: WorkspaceStateUpdate): void => {
13✔
190
    set((current) => ({
2✔
191
      sidebarCollapsed: state.sidebarCollapsed ?? current.sidebarCollapsed,
2!
192
      sidebarWidth: state.sidebarWidth ?? current.sidebarWidth,
2✔
193
      expandedProjectIds: state.expandedProjectIds ?? current.expandedProjectIds,
2!
194
      activeShellId: state.activeShellId !== undefined ? state.activeShellId : current.activeShellId,
2!
195
      terminalFontSize: state.terminalFontSize ?? current.terminalFontSize,
2✔
196
      terminalTheme: state.terminalTheme ?? current.terminalTheme,
2✔
197
      contextSidebarPinned: state.contextSidebarPinned ?? current.contextSidebarPinned,
2✔
198
      contextSidebarWidth: state.contextSidebarWidth ?? current.contextSidebarWidth,
2✔
199
    }));
2✔
200
  },
2✔
201

202
  recordShellActivity: (shellId: string): void => {
13✔
203
    set((state) => ({
3✔
204
      shellActivityTimestamps: {
3✔
205
        ...state.shellActivityTimestamps,
3✔
206
        [shellId]: Date.now(),
3✔
207
      },
3✔
208
    }));
3✔
209
  },
3✔
210

211
  getShellActivityTimestamp: (shellId: string): number | undefined => {
13✔
212
    // This is a selector, not an action - but zustand allows it
213
    return useUIStore.getState().shellActivityTimestamps[shellId];
×
214
  },
×
215

216
  // Context sidebar actions
217
  openContextSidebar: (type: ContextType, id: string): void => {
13✔
218
    // Determine the default tab based on type
219
    const defaultTab: ContextSidebarTab = type === 'project' ? 'urls' : 'todos';
26✔
220
    set({
26✔
221
      contextSidebarOpen: true,
26✔
222
      selectedContextType: type,
26✔
223
      selectedContextId: id,
26✔
224
      contextSidebarActiveTab: defaultTab,
26✔
225
    });
26✔
226
  },
26✔
227

228
  closeContextSidebar: (): void => {
13✔
229
    set({
3✔
230
      contextSidebarOpen: false,
3✔
231
      selectedContextType: null,
3✔
232
      selectedContextId: null,
3✔
233
    });
3✔
234
  },
3✔
235

236
  toggleContextSidebar: (type: ContextType, id: string): void => {
13✔
237
    set((state) => {
12✔
238
      // If same item clicked and sidebar is open, close it
239
      if (state.contextSidebarOpen && state.selectedContextId === id && state.selectedContextType === type) {
12✔
240
        return {
3✔
241
          contextSidebarOpen: false,
3✔
242
          selectedContextType: null,
3✔
243
          selectedContextId: null,
3✔
244
        };
3✔
245
      }
3✔
246
      // Otherwise, open with the new item
247
      const defaultTab: ContextSidebarTab = type === 'project' ? 'urls' : 'todos';
12✔
248
      // When selecting a project, clear the active shell so only the project is highlighted
249
      const updates: Partial<UIState> = {
12✔
250
        contextSidebarOpen: true,
12✔
251
        selectedContextType: type,
12✔
252
        selectedContextId: id,
12✔
253
        contextSidebarActiveTab: defaultTab,
12✔
254
      };
12✔
255
      if (type === 'project') {
12✔
256
        updates.activeShellId = null;
6✔
257
      }
6✔
258
      return updates;
9✔
259
    });
12✔
260
  },
12✔
261

262
  toggleContextSidebarPin: (): void => {
13✔
263
    set((state) => ({ contextSidebarPinned: !state.contextSidebarPinned }));
6✔
264
  },
6✔
265

266
  setContextSidebarWidth: (width: number): void => {
13✔
267
    const clampedWidth = Math.min(MAX_CONTEXT_SIDEBAR_WIDTH, Math.max(MIN_CONTEXT_SIDEBAR_WIDTH, width));
5✔
268
    set({ contextSidebarWidth: clampedWidth });
5✔
269
  },
5✔
270

271
  setContextSidebarTab: (tab: ContextSidebarTab): void => {
13✔
272
    set({ contextSidebarActiveTab: tab });
5✔
273
  },
5✔
274

275
  // File preview actions
276
  openFilePreview: (projectId: string, filePath: string): void => {
13✔
277
    // Opening file preview clears the active shell
278
    set({
2✔
279
      previewProjectId: projectId,
2✔
280
      previewFilePath: filePath,
2✔
281
      activeShellId: null,
2✔
282
    });
2✔
283
  },
2✔
284

285
  closeFilePreview: (): void => {
13✔
286
    set({
×
287
      previewProjectId: null,
×
288
      previewFilePath: null,
×
289
    });
×
290
  },
×
291

292
  reset: (): void => {
13✔
293
    set(initialState);
161✔
294
  },
161✔
295
}), shallow);
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc