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

apowers313 / aiforge / 20962792399

13 Jan 2026 03:39PM UTC coverage: 84.707%. First build
20962792399

push

github

apowers313
feat: initial commit

787 of 905 branches covered (86.96%)

Branch coverage included in aggregate %.

4248 of 5039 new or added lines in 70 files covered. (84.3%)

4248 of 5039 relevant lines covered (84.3%)

13.11 hits per line

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

90.4
/src/client/hooks/useShells.ts
1
import { useQuery, useMutation, useQueryClient, type UseQueryResult, type UseMutationResult } from '@tanstack/react-query';
1✔
2
import { api } from '@client/services/api';
1✔
3
import { useUIStore } from '@client/stores/uiStore';
1✔
4
import { queryKeys } from './queryKeys';
1✔
5
import type { Shell } from '@shared/types';
6

7
/**
8
 * Hook to fetch shells for a specific project
9
 */
10
export function useShells(projectId: string | null): UseQueryResult<Shell[]> {
1✔
11
  return useQuery({
36✔
12
    queryKey: queryKeys.shells.byProject(projectId ?? ''),
36✔
13
    queryFn: async () => {
36✔
14
      if (!projectId) return [];
15!
15
      const result = await api.getShells(projectId);
15✔
16
      return result.shells;
11✔
17
    },
15✔
18
    enabled: !!projectId,
36✔
19
    placeholderData: [],
36✔
20
  });
36✔
21
}
36✔
22

23
/**
24
 * Hook to fetch shells for all projects at once
25
 */
26
export function useAllShells(projectIds: string[]): UseQueryResult<Shell[]> {
1✔
27
  const queryClient = useQueryClient();
21✔
28

29
  return useQuery({
21✔
30
    queryKey: ['shells', 'all', projectIds.join(',')],
21✔
31
    queryFn: async () => {
21✔
32
      const results = await Promise.all(
3✔
33
        projectIds.map(async (projectId) => {
3✔
34
          const result = await api.getShells(projectId);
4✔
35
          // Also populate individual project shell caches
36
          queryClient.setQueryData(queryKeys.shells.byProject(projectId), result.shells);
2✔
37
          return result.shells;
2✔
38
        }),
3✔
39
      );
3✔
40
      return results.flat();
1✔
41
    },
3✔
42
    enabled: projectIds.length > 0,
21✔
43
  });
21✔
44
}
21✔
45

46
/**
47
 * Hook to create a new shell
48
 */
49
export function useCreateShell(): UseMutationResult<{ shell: Shell }, Error, { projectId: string; name?: string }> {
1✔
50
  const queryClient = useQueryClient();
33✔
51

52
  return useMutation({
33✔
53
    mutationFn: ({ projectId, name }: { projectId: string; name?: string }) =>
33✔
54
      api.createShell(projectId, name),
5✔
55
    onSuccess: (data) => {
33✔
56
      void queryClient.invalidateQueries({
5✔
57
        queryKey: queryKeys.shells.byProject(data.shell.projectId),
5✔
58
      });
5✔
59
    },
5✔
60
  });
33✔
61
}
33✔
62

63
interface DeleteShellContext {
64
  previousShells: Shell[] | undefined;
65
  projectId: string;
66
}
67

68
/**
69
 * Hook to delete a shell
70
 */
71
export function useDeleteShell(): UseMutationResult<{ shellId: string; projectId: string }, Error, { shellId: string; projectId: string }, DeleteShellContext> {
1✔
72
  const queryClient = useQueryClient();
39✔
73
  const setActiveShell = useUIStore((state) => state.setActiveShell);
39✔
74
  const activeShellId = useUIStore((state) => state.activeShellId);
39✔
75

76
  return useMutation({
39✔
77
    mutationFn: ({ shellId, projectId }: { shellId: string; projectId: string }) =>
39✔
78
      api.deleteShell(shellId).then(() => ({ shellId, projectId })),
5✔
79
    // Optimistic update
80
    onMutate: async ({ shellId, projectId }) => {
39✔
81
      await queryClient.cancelQueries({ queryKey: queryKeys.shells.byProject(projectId) });
5✔
82

83
      const previousShells = queryClient.getQueryData<Shell[]>(
5✔
84
        queryKeys.shells.byProject(projectId),
5✔
85
      );
5✔
86

87
      queryClient.setQueryData<Shell[]>(
5✔
88
        queryKeys.shells.byProject(projectId),
5✔
89
        (old) => old?.filter((s) => s.id !== shellId) ?? [],
5✔
90
      );
5✔
91

92
      // Clear active shell if it's the one being deleted
93
      if (activeShellId === shellId) {
5✔
94
        setActiveShell(null);
1✔
95
      }
1✔
96

97
      return { previousShells, projectId };
5✔
98
    },
5✔
99
    onError: (_err, _variables, context) => {
39✔
100
      if (context?.previousShells) {
1!
NEW
101
        queryClient.setQueryData(
×
NEW
102
          queryKeys.shells.byProject(context.projectId),
×
NEW
103
          context.previousShells,
×
NEW
104
        );
×
NEW
105
      }
×
106
    },
1✔
107
    onSettled: (_data, _error, variables) => {
39✔
108
      void queryClient.invalidateQueries({
5✔
109
        queryKey: queryKeys.shells.byProject(variables.projectId),
5✔
110
      });
5✔
111
    },
5✔
112
  });
39✔
113
}
39✔
114

115
/**
116
 * Hook to update a shell (e.g., rename)
117
 */
118
export function useUpdateShell(): UseMutationResult<{ shell: Shell }, Error, { shellId: string; updates: { name?: string } }> {
1✔
119
  const queryClient = useQueryClient();
32✔
120

121
  return useMutation({
32✔
122
    mutationFn: ({ shellId, updates }: { shellId: string; updates: { name?: string } }) =>
32✔
123
      api.updateShell(shellId, updates),
2✔
124
    onSuccess: (data) => {
32✔
125
      void queryClient.invalidateQueries({
2✔
126
        queryKey: queryKeys.shells.byProject(data.shell.projectId),
2✔
127
      });
2✔
128
    },
2✔
129
  });
32✔
130
}
32✔
131

132
/**
133
 * Hook to restart a shell
134
 */
135
export function useRestartShell(): UseMutationResult<{ shell: Shell }, Error, string> {
1✔
136
  const queryClient = useQueryClient();
33✔
137

138
  return useMutation({
33✔
139
    mutationFn: (shellId: string) => api.restartShell(shellId),
33✔
140
    onSuccess: (data) => {
33✔
141
      void queryClient.invalidateQueries({
2✔
142
        queryKey: queryKeys.shells.byProject(data.shell.projectId),
2✔
143
      });
2✔
144
    },
2✔
145
  });
33✔
146
}
33✔
147

148
interface ActiveShellId {
149
  activeShellId: string | null;
150
  setActiveShell: (id: string | null) => void;
151
}
152

153
/**
154
 * Hook to get active shell ID from UI store
155
 */
156
export function useActiveShellId(): ActiveShellId {
1✔
157
  const activeShellId = useUIStore((state) => state.activeShellId);
84✔
158
  const setActiveShell = useUIStore((state) => state.setActiveShell);
84✔
159
  return { activeShellId, setActiveShell };
84✔
160
}
84✔
161

162
/**
163
 * Hook to get a specific shell by ID from the cache
164
 * Searches all project shell caches for the shell
165
 */
166
export function useShell(shellId: string | null): Shell | undefined {
1✔
167
  const queryClient = useQueryClient();
2✔
168

169
  if (!shellId) return undefined;
2!
170

171
  // Get all cached shell queries and search for the shell
172
  const cache = queryClient.getQueryCache();
2✔
173
  const shellQueries = cache.findAll({ queryKey: ['shells'] });
2✔
174

175
  for (const query of shellQueries) {
2✔
176
    const shells = query.state.data as Shell[] | undefined;
2✔
177
    if (shells) {
2✔
178
      const shell = shells.find((s) => s.id === shellId);
2✔
179
      if (shell) return shell;
2✔
180
    }
2✔
181
  }
2!
182

NEW
183
  return undefined;
×
NEW
184
}
×
185

186
/**
187
 * Hook to start a shell (activate PTY)
188
 */
189
export function useStartShell(): UseMutationResult<{ shell: Shell }, Error, string> {
1✔
190
  const queryClient = useQueryClient();
2✔
191

192
  return useMutation({
2✔
193
    mutationFn: (shellId: string) => api.startShell(shellId),
2✔
194
    onSuccess: (data) => {
2✔
195
      // Update the shell in the cache
NEW
196
      const projectId = data.shell.projectId;
×
NEW
197
      queryClient.setQueryData<Shell[]>(
×
NEW
198
        queryKeys.shells.byProject(projectId),
×
NEW
199
        (old) => old?.map((s) => (s.id === data.shell.id ? data.shell : s)) ?? [],
×
NEW
200
      );
×
NEW
201
    },
×
202
  });
2✔
203
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc