@stigmer/react 0.0.53 → 0.0.55
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.
- package/execution/ArtifactCard.d.ts +11 -1
- package/execution/ArtifactCard.d.ts.map +1 -1
- package/execution/ArtifactCard.js +22 -3
- package/execution/ArtifactCard.js.map +1 -1
- package/execution/ArtifactPreviewModal.d.ts.map +1 -1
- package/execution/ArtifactPreviewModal.js +1 -1
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/ArtifactsWidget.d.ts +26 -19
- package/execution/ArtifactsWidget.d.ts.map +1 -1
- package/execution/ArtifactsWidget.js +32 -26
- package/execution/ArtifactsWidget.js.map +1 -1
- package/execution/MessageThread.d.ts +10 -1
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +21 -17
- package/execution/MessageThread.js.map +1 -1
- package/execution/SandboxContext.d.ts +32 -0
- package/execution/SandboxContext.d.ts.map +1 -0
- package/execution/SandboxContext.js +26 -0
- package/execution/SandboxContext.js.map +1 -0
- package/execution/SetupProgress.d.ts +23 -13
- package/execution/SetupProgress.d.ts.map +1 -1
- package/execution/SetupProgress.js +18 -12
- package/execution/SetupProgress.js.map +1 -1
- package/execution/ToolArgsView.d.ts.map +1 -1
- package/execution/ToolArgsView.js +3 -1
- package/execution/ToolArgsView.js.map +1 -1
- package/execution/ToolCallDetail.d.ts.map +1 -1
- package/execution/ToolCallDetail.js +3 -1
- package/execution/ToolCallDetail.js.map +1 -1
- package/execution/ToolCallItem.d.ts.map +1 -1
- package/execution/ToolCallItem.js +7 -1
- package/execution/ToolCallItem.js.map +1 -1
- package/execution/WriteBackCard.d.ts +34 -0
- package/execution/WriteBackCard.d.ts.map +1 -0
- package/execution/WriteBackCard.js +75 -0
- package/execution/WriteBackCard.js.map +1 -0
- package/execution/WriteBacksWidget.d.ts +49 -0
- package/execution/WriteBacksWidget.d.ts.map +1 -0
- package/execution/WriteBacksWidget.js +44 -0
- package/execution/WriteBacksWidget.js.map +1 -0
- package/execution/__tests__/file-path-resolver.test.d.ts +2 -0
- package/execution/__tests__/file-path-resolver.test.d.ts.map +1 -0
- package/execution/__tests__/file-path-resolver.test.js +180 -0
- package/execution/__tests__/file-path-resolver.test.js.map +1 -0
- package/execution/file-path-resolver.d.ts +3 -3
- package/execution/file-path-resolver.d.ts.map +1 -1
- package/execution/file-path-resolver.js +23 -12
- package/execution/file-path-resolver.js.map +1 -1
- package/execution/index.d.ts +9 -0
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +5 -0
- package/execution/index.js.map +1 -1
- package/execution/sandbox-path-normalizer.d.ts +46 -0
- package/execution/sandbox-path-normalizer.d.ts.map +1 -0
- package/execution/sandbox-path-normalizer.js +73 -0
- package/execution/sandbox-path-normalizer.js.map +1 -0
- package/execution/useArtifactContent.d.ts +5 -1
- package/execution/useArtifactContent.d.ts.map +1 -1
- package/execution/useArtifactContent.js +6 -2
- package/execution/useArtifactContent.js.map +1 -1
- package/execution/useWorkspaceWriteBacks.d.ts +40 -0
- package/execution/useWorkspaceWriteBacks.d.ts.map +1 -0
- package/execution/useWorkspaceWriteBacks.js +41 -0
- package/execution/useWorkspaceWriteBacks.js.map +1 -0
- package/github/GitHubRepoPicker.d.ts +5 -2
- package/github/GitHubRepoPicker.d.ts.map +1 -1
- package/github/GitHubRepoPicker.js +133 -36
- package/github/GitHubRepoPicker.js.map +1 -1
- package/github/index.d.ts +1 -0
- package/github/index.d.ts.map +1 -1
- package/github/index.js +1 -0
- package/github/index.js.map +1 -1
- package/github/useGitHubSearch.d.ts +20 -0
- package/github/useGitHubSearch.d.ts.map +1 -0
- package/github/useGitHubSearch.js +127 -0
- package/github/useGitHubSearch.js.map +1 -0
- package/index.d.ts +6 -6
- package/index.d.ts.map +1 -1
- package/index.js +3 -3
- package/index.js.map +1 -1
- package/package.json +4 -4
- package/session/index.d.ts +4 -0
- package/session/index.d.ts.map +1 -1
- package/session/index.js +2 -0
- package/session/index.js.map +1 -1
- package/session/useSessionArtifacts.d.ts +73 -0
- package/session/useSessionArtifacts.d.ts.map +1 -0
- package/session/useSessionArtifacts.js +95 -0
- package/session/useSessionArtifacts.js.map +1 -0
- package/session/useSessionWriteBacks.d.ts +56 -0
- package/session/useSessionWriteBacks.d.ts.map +1 -0
- package/session/useSessionWriteBacks.js +56 -0
- package/session/useSessionWriteBacks.js.map +1 -0
- package/src/execution/ArtifactCard.tsx +40 -0
- package/src/execution/ArtifactPreviewModal.tsx +2 -0
- package/src/execution/ArtifactsWidget.tsx +67 -43
- package/src/execution/MessageThread.tsx +23 -1
- package/src/execution/SandboxContext.ts +47 -0
- package/src/execution/SetupProgress.tsx +33 -14
- package/src/execution/ToolArgsView.tsx +3 -1
- package/src/execution/ToolCallDetail.tsx +3 -1
- package/src/execution/ToolCallItem.tsx +7 -1
- package/src/execution/WriteBackCard.tsx +210 -0
- package/src/execution/WriteBacksWidget.tsx +82 -0
- package/src/execution/__tests__/file-path-resolver.test.ts +295 -0
- package/src/execution/file-path-resolver.ts +24 -12
- package/src/execution/index.ts +13 -0
- package/src/execution/sandbox-path-normalizer.ts +80 -0
- package/src/execution/useArtifactContent.ts +6 -1
- package/src/execution/useWorkspaceWriteBacks.ts +56 -0
- package/src/github/GitHubRepoPicker.tsx +413 -108
- package/src/github/index.ts +5 -0
- package/src/github/useGitHubSearch.ts +162 -0
- package/src/index.ts +14 -0
- package/src/session/index.ts +12 -0
- package/src/session/useSessionArtifacts.ts +143 -0
- package/src/session/useSessionWriteBacks.ts +94 -0
- package/styles.css +1 -1
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type KeyboardEvent,
|
|
10
10
|
} from "react";
|
|
11
11
|
import { useGitHubRepos, type GitHubRepo, type GitHubBranch } from "./useGitHubRepos";
|
|
12
|
+
import { useGitHubSearch } from "./useGitHubSearch";
|
|
12
13
|
|
|
13
14
|
export interface GitHubRepoPickerProps {
|
|
14
15
|
/** GitHub access token for API calls. */
|
|
@@ -20,6 +21,8 @@ export interface GitHubRepoPickerProps {
|
|
|
20
21
|
readonly className?: string;
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
type PickerMode = "my-repos" | "all-github";
|
|
25
|
+
|
|
23
26
|
// ---------------------------------------------------------------------------
|
|
24
27
|
// Recent repos (localStorage persistence)
|
|
25
28
|
// ---------------------------------------------------------------------------
|
|
@@ -52,7 +55,7 @@ function addRecentRepo(repo: RecentRepo): void {
|
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
// ---------------------------------------------------------------------------
|
|
55
|
-
// Repo grouping
|
|
58
|
+
// Repo grouping (used in "my-repos" mode)
|
|
56
59
|
// ---------------------------------------------------------------------------
|
|
57
60
|
|
|
58
61
|
interface RepoGroup {
|
|
@@ -71,7 +74,6 @@ function groupRepos(
|
|
|
71
74
|
filteredRepos.map((r) => [`${r.owner}/${r.name}`, r]),
|
|
72
75
|
);
|
|
73
76
|
|
|
74
|
-
// Recent group — only include entries that exist in the filtered set
|
|
75
77
|
const recentMatched = recentEntries
|
|
76
78
|
.map((r) => repoLookup.get(`${r.owner}/${r.name}`))
|
|
77
79
|
.filter((r): r is GitHubRepo => r !== undefined);
|
|
@@ -85,7 +87,6 @@ function groupRepos(
|
|
|
85
87
|
});
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
// Owner groups — personal (User) repos first, then orgs by repo count
|
|
89
90
|
const ownerMap = new Map<
|
|
90
91
|
string,
|
|
91
92
|
{ ownerType: "User" | "Organization"; repos: GitHubRepo[] }
|
|
@@ -136,17 +137,21 @@ function HighlightMatch({ text, query }: { text: string; query: string }) {
|
|
|
136
137
|
// ---------------------------------------------------------------------------
|
|
137
138
|
|
|
138
139
|
const LIST_ID = "stgm-repo-list";
|
|
140
|
+
const GITHUB_INSTALLATIONS_URL = "https://github.com/settings/installations";
|
|
139
141
|
|
|
140
142
|
/**
|
|
141
143
|
* Styled component for browsing and selecting a GitHub repository.
|
|
142
144
|
*
|
|
143
145
|
* Features:
|
|
144
|
-
* -
|
|
146
|
+
* - Two modes: "My Repos" (user's own repos) and "All GitHub" (public search)
|
|
147
|
+
* - Owner-grouped sections in My Repos mode
|
|
145
148
|
* - Recently selected repos pinned at top
|
|
146
|
-
* - Fixed
|
|
149
|
+
* - Fixed max-height with scroll shadow indicators
|
|
147
150
|
* - Keyboard navigation (Arrow keys, Enter, Escape)
|
|
148
151
|
* - Search with match highlighting
|
|
149
152
|
* - Branch selector after repo selection
|
|
153
|
+
* - Manual URL entry for repos not discoverable via search
|
|
154
|
+
* - Link to manage GitHub App repository access
|
|
150
155
|
*
|
|
151
156
|
* All visual properties flow through `--stgm-*` tokens.
|
|
152
157
|
*/
|
|
@@ -156,17 +161,16 @@ export function GitHubRepoPicker({
|
|
|
156
161
|
onCancel,
|
|
157
162
|
className,
|
|
158
163
|
}: GitHubRepoPickerProps) {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
// Branch selection state
|
|
164
|
+
const [mode, setMode] = useState<PickerMode>("my-repos");
|
|
165
|
+
const [showManualEntry, setShowManualEntry] = useState(false);
|
|
166
|
+
|
|
167
|
+
// My Repos data
|
|
168
|
+
const myRepos = useGitHubRepos(token);
|
|
169
|
+
|
|
170
|
+
// All GitHub search data
|
|
171
|
+
const githubSearch = useGitHubSearch(token);
|
|
172
|
+
|
|
173
|
+
// Branch selection state (shared across modes)
|
|
170
174
|
const [selectedRepo, setSelectedRepo] = useState<{
|
|
171
175
|
owner: string;
|
|
172
176
|
name: string;
|
|
@@ -177,6 +181,10 @@ export function GitHubRepoPicker({
|
|
|
177
181
|
const [selectedBranch, setSelectedBranch] = useState("");
|
|
178
182
|
const [loadingBranches, setLoadingBranches] = useState(false);
|
|
179
183
|
|
|
184
|
+
// Manual URL state
|
|
185
|
+
const [manualUrl, setManualUrl] = useState("");
|
|
186
|
+
const [manualBranch, setManualBranch] = useState("");
|
|
187
|
+
|
|
180
188
|
// Keyboard navigation
|
|
181
189
|
const [focusIndex, setFocusIndex] = useState(-1);
|
|
182
190
|
const listRef = useRef<HTMLDivElement>(null);
|
|
@@ -189,13 +197,23 @@ export function GitHubRepoPicker({
|
|
|
189
197
|
// Recent repos
|
|
190
198
|
const [recentRepos, setRecentRepos] = useState<RecentRepo[]>(getRecentRepos);
|
|
191
199
|
|
|
192
|
-
//
|
|
200
|
+
// --- Mode-dependent derived state ---
|
|
201
|
+
|
|
202
|
+
const activeSearch = mode === "my-repos" ? myRepos.search : githubSearch.query;
|
|
203
|
+
const setActiveSearch = mode === "my-repos" ? myRepos.setSearch : githubSearch.setQuery;
|
|
204
|
+
const activeRepos = mode === "my-repos" ? myRepos.repos : githubSearch.results;
|
|
205
|
+
const activeError = mode === "my-repos" ? myRepos.error : githubSearch.error;
|
|
206
|
+
const activeIsLoading = mode === "my-repos" ? myRepos.isLoading : githubSearch.isSearching;
|
|
207
|
+
|
|
208
|
+
// Group repos only in "my-repos" mode
|
|
193
209
|
const groups = useMemo(
|
|
194
|
-
() => groupRepos(
|
|
195
|
-
[
|
|
210
|
+
() => (mode === "my-repos" ? groupRepos(activeRepos, recentRepos) : []),
|
|
211
|
+
[mode, activeRepos, recentRepos],
|
|
196
212
|
);
|
|
197
213
|
|
|
214
|
+
// Flat list for keyboard nav: grouped in my-repos, flat in all-github
|
|
198
215
|
const flatItems = useMemo(() => {
|
|
216
|
+
if (mode === "all-github") return [...activeRepos];
|
|
199
217
|
const items: GitHubRepo[] = [];
|
|
200
218
|
for (const group of groups) {
|
|
201
219
|
for (const repo of group.repos) {
|
|
@@ -203,12 +221,12 @@ export function GitHubRepoPicker({
|
|
|
203
221
|
}
|
|
204
222
|
}
|
|
205
223
|
return items;
|
|
206
|
-
}, [groups]);
|
|
224
|
+
}, [mode, activeRepos, groups]);
|
|
207
225
|
|
|
208
|
-
// Reset focus index when search changes
|
|
226
|
+
// Reset focus index when search or mode changes
|
|
209
227
|
useEffect(() => {
|
|
210
228
|
setFocusIndex(-1);
|
|
211
|
-
}, [
|
|
229
|
+
}, [activeSearch, mode]);
|
|
212
230
|
|
|
213
231
|
// Scroll focused item into view
|
|
214
232
|
useEffect(() => {
|
|
@@ -238,10 +256,24 @@ export function GitHubRepoPicker({
|
|
|
238
256
|
return () => el.removeEventListener("scroll", updateScrollShadows);
|
|
239
257
|
}, [updateScrollShadows]);
|
|
240
258
|
|
|
241
|
-
// Re-check shadows when repo data changes
|
|
242
259
|
useEffect(() => {
|
|
243
260
|
updateScrollShadows();
|
|
244
|
-
}, [
|
|
261
|
+
}, [activeRepos, updateScrollShadows]);
|
|
262
|
+
|
|
263
|
+
const handleModeSwitch = useCallback(
|
|
264
|
+
(newMode: PickerMode) => {
|
|
265
|
+
if (newMode === mode) return;
|
|
266
|
+
setMode(newMode);
|
|
267
|
+
setFocusIndex(-1);
|
|
268
|
+
if (newMode === "my-repos") {
|
|
269
|
+
githubSearch.setQuery("");
|
|
270
|
+
} else {
|
|
271
|
+
myRepos.setSearch("");
|
|
272
|
+
}
|
|
273
|
+
setTimeout(() => searchRef.current?.focus(), 0);
|
|
274
|
+
},
|
|
275
|
+
[mode, githubSearch, myRepos],
|
|
276
|
+
);
|
|
245
277
|
|
|
246
278
|
const handleRepoClick = useCallback(
|
|
247
279
|
async (repo: GitHubRepo) => {
|
|
@@ -253,11 +285,11 @@ export function GitHubRepoPicker({
|
|
|
253
285
|
});
|
|
254
286
|
setSelectedBranch(repo.defaultBranch);
|
|
255
287
|
setLoadingBranches(true);
|
|
256
|
-
const b = await fetchBranches(repo.owner, repo.name);
|
|
288
|
+
const b = await myRepos.fetchBranches(repo.owner, repo.name);
|
|
257
289
|
setBranches(b);
|
|
258
290
|
setLoadingBranches(false);
|
|
259
291
|
},
|
|
260
|
-
[
|
|
292
|
+
[myRepos],
|
|
261
293
|
);
|
|
262
294
|
|
|
263
295
|
const handleAdd = useCallback(() => {
|
|
@@ -276,8 +308,15 @@ export function GitHubRepoPicker({
|
|
|
276
308
|
}
|
|
277
309
|
}, [selectedRepo, selectedBranch, onSelect]);
|
|
278
310
|
|
|
279
|
-
|
|
280
|
-
|
|
311
|
+
const handleManualAdd = useCallback(() => {
|
|
312
|
+
const url = manualUrl.trim();
|
|
313
|
+
if (!url) return;
|
|
314
|
+
onSelect(url, manualBranch.trim() || "main");
|
|
315
|
+
setManualUrl("");
|
|
316
|
+
setManualBranch("");
|
|
317
|
+
setShowManualEntry(false);
|
|
318
|
+
}, [manualUrl, manualBranch, onSelect]);
|
|
319
|
+
|
|
281
320
|
const handleSearchKeyDown = useCallback(
|
|
282
321
|
(e: KeyboardEvent<HTMLInputElement>) => {
|
|
283
322
|
if (e.key === "ArrowDown") {
|
|
@@ -296,19 +335,32 @@ export function GitHubRepoPicker({
|
|
|
296
335
|
e.preventDefault();
|
|
297
336
|
handleRepoClick(flatItems[focusIndex]);
|
|
298
337
|
} else if (e.key === "Enter") {
|
|
299
|
-
// Prevent form submission when no item is focused
|
|
300
338
|
e.preventDefault();
|
|
301
339
|
} else if (e.key === "Escape") {
|
|
302
340
|
e.preventDefault();
|
|
303
|
-
if (
|
|
304
|
-
|
|
341
|
+
if (activeSearch) {
|
|
342
|
+
setActiveSearch("");
|
|
305
343
|
setFocusIndex(-1);
|
|
306
344
|
} else {
|
|
307
345
|
onCancel?.();
|
|
308
346
|
}
|
|
309
347
|
}
|
|
310
348
|
},
|
|
311
|
-
[flatItems, focusIndex, handleRepoClick, onCancel,
|
|
349
|
+
[flatItems, focusIndex, handleRepoClick, onCancel, activeSearch, setActiveSearch],
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const handleManualKeyDown = useCallback(
|
|
353
|
+
(e: KeyboardEvent<HTMLInputElement>) => {
|
|
354
|
+
if (e.key === "Enter") {
|
|
355
|
+
e.preventDefault();
|
|
356
|
+
handleManualAdd();
|
|
357
|
+
} else if (e.key === "Escape") {
|
|
358
|
+
e.preventDefault();
|
|
359
|
+
setShowManualEntry(false);
|
|
360
|
+
setTimeout(() => searchRef.current?.focus(), 0);
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
[handleManualAdd],
|
|
312
364
|
);
|
|
313
365
|
|
|
314
366
|
// --- Branch selection view ---
|
|
@@ -321,7 +373,6 @@ export function GitHubRepoPicker({
|
|
|
321
373
|
onClick={() => {
|
|
322
374
|
setSelectedRepo(null);
|
|
323
375
|
setBranches([]);
|
|
324
|
-
// Restore focus to search
|
|
325
376
|
setTimeout(() => searchRef.current?.focus(), 0);
|
|
326
377
|
}}
|
|
327
378
|
className="text-muted-foreground hover:text-foreground transition-colors"
|
|
@@ -371,7 +422,58 @@ export function GitHubRepoPicker({
|
|
|
371
422
|
);
|
|
372
423
|
}
|
|
373
424
|
|
|
374
|
-
// ---
|
|
425
|
+
// --- Manual URL entry view ---
|
|
426
|
+
if (showManualEntry) {
|
|
427
|
+
return (
|
|
428
|
+
<div className={["space-y-2", className].filter(Boolean).join(" ")}>
|
|
429
|
+
<div className="flex items-center gap-2 text-xs text-foreground">
|
|
430
|
+
<button
|
|
431
|
+
type="button"
|
|
432
|
+
onClick={() => {
|
|
433
|
+
setShowManualEntry(false);
|
|
434
|
+
setTimeout(() => searchRef.current?.focus(), 0);
|
|
435
|
+
}}
|
|
436
|
+
className="text-muted-foreground hover:text-foreground transition-colors"
|
|
437
|
+
aria-label="Back to repo list"
|
|
438
|
+
>
|
|
439
|
+
<ChevronLeftIcon />
|
|
440
|
+
</button>
|
|
441
|
+
<span className="font-medium">Paste a repository URL</span>
|
|
442
|
+
</div>
|
|
443
|
+
|
|
444
|
+
<input
|
|
445
|
+
type="url"
|
|
446
|
+
placeholder="https://github.com/org/repo"
|
|
447
|
+
value={manualUrl}
|
|
448
|
+
onChange={(e) => setManualUrl(e.target.value)}
|
|
449
|
+
onKeyDown={handleManualKeyDown}
|
|
450
|
+
className="w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
451
|
+
autoFocus
|
|
452
|
+
/>
|
|
453
|
+
<input
|
|
454
|
+
type="text"
|
|
455
|
+
placeholder="Branch (optional, defaults to main)"
|
|
456
|
+
value={manualBranch}
|
|
457
|
+
onChange={(e) => setManualBranch(e.target.value)}
|
|
458
|
+
onKeyDown={handleManualKeyDown}
|
|
459
|
+
className="w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
460
|
+
/>
|
|
461
|
+
|
|
462
|
+
<div className="flex justify-end">
|
|
463
|
+
<button
|
|
464
|
+
type="button"
|
|
465
|
+
onClick={handleManualAdd}
|
|
466
|
+
disabled={!manualUrl.trim()}
|
|
467
|
+
className="rounded-md bg-primary px-2.5 py-1 text-xs text-primary-foreground hover:bg-primary/90 transition-colors disabled:opacity-40"
|
|
468
|
+
>
|
|
469
|
+
Add
|
|
470
|
+
</button>
|
|
471
|
+
</div>
|
|
472
|
+
</div>
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// --- Compute flat index offsets per group for "my-repos" rendering ---
|
|
375
477
|
let runningIndex = 0;
|
|
376
478
|
const groupOffsets = groups.map((g) => {
|
|
377
479
|
const offset = runningIndex;
|
|
@@ -384,7 +486,35 @@ export function GitHubRepoPicker({
|
|
|
384
486
|
<div
|
|
385
487
|
className={["space-y-1.5", className].filter(Boolean).join(" ")}
|
|
386
488
|
>
|
|
387
|
-
{/*
|
|
489
|
+
{/* Mode toggle */}
|
|
490
|
+
<div className="flex rounded-md border border-border bg-muted/30 p-0.5">
|
|
491
|
+
<button
|
|
492
|
+
type="button"
|
|
493
|
+
onClick={() => handleModeSwitch("my-repos")}
|
|
494
|
+
className={[
|
|
495
|
+
"flex-1 rounded px-2 py-1 text-[0.65rem] font-medium transition-colors",
|
|
496
|
+
mode === "my-repos"
|
|
497
|
+
? "bg-background text-foreground shadow-sm"
|
|
498
|
+
: "text-muted-foreground hover:text-foreground",
|
|
499
|
+
].join(" ")}
|
|
500
|
+
>
|
|
501
|
+
My Repos
|
|
502
|
+
</button>
|
|
503
|
+
<button
|
|
504
|
+
type="button"
|
|
505
|
+
onClick={() => handleModeSwitch("all-github")}
|
|
506
|
+
className={[
|
|
507
|
+
"flex-1 rounded px-2 py-1 text-[0.65rem] font-medium transition-colors",
|
|
508
|
+
mode === "all-github"
|
|
509
|
+
? "bg-background text-foreground shadow-sm"
|
|
510
|
+
: "text-muted-foreground hover:text-foreground",
|
|
511
|
+
].join(" ")}
|
|
512
|
+
>
|
|
513
|
+
All GitHub
|
|
514
|
+
</button>
|
|
515
|
+
</div>
|
|
516
|
+
|
|
517
|
+
{/* Search input */}
|
|
388
518
|
<input
|
|
389
519
|
ref={searchRef}
|
|
390
520
|
type="text"
|
|
@@ -394,17 +524,21 @@ export function GitHubRepoPicker({
|
|
|
394
524
|
aria-activedescendant={
|
|
395
525
|
focusIndex >= 0 ? `stgm-repo-${focusIndex}` : undefined
|
|
396
526
|
}
|
|
397
|
-
placeholder=
|
|
398
|
-
|
|
399
|
-
|
|
527
|
+
placeholder={
|
|
528
|
+
mode === "my-repos"
|
|
529
|
+
? "Search repositories..."
|
|
530
|
+
: "Search all of GitHub..."
|
|
531
|
+
}
|
|
532
|
+
value={activeSearch}
|
|
533
|
+
onChange={(e) => setActiveSearch(e.target.value)}
|
|
400
534
|
onKeyDown={handleSearchKeyDown}
|
|
401
535
|
className="w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
402
536
|
autoFocus
|
|
403
537
|
/>
|
|
404
538
|
|
|
405
|
-
{
|
|
539
|
+
{activeError && (
|
|
406
540
|
<p className="text-xs text-destructive">
|
|
407
|
-
{
|
|
541
|
+
{activeError}
|
|
408
542
|
</p>
|
|
409
543
|
)}
|
|
410
544
|
|
|
@@ -427,75 +561,28 @@ export function GitHubRepoPicker({
|
|
|
427
561
|
aria-label="Repositories"
|
|
428
562
|
className="max-h-64 overflow-y-auto"
|
|
429
563
|
>
|
|
430
|
-
{
|
|
431
|
-
<
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
{
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
564
|
+
{mode === "my-repos" ? (
|
|
565
|
+
<MyReposList
|
|
566
|
+
groups={groups}
|
|
567
|
+
groupOffsets={groupOffsets}
|
|
568
|
+
flatItems={flatItems}
|
|
569
|
+
focusIndex={focusIndex}
|
|
570
|
+
isLoading={myRepos.isLoading}
|
|
571
|
+
isBackgroundLoading={myRepos.isBackgroundLoading}
|
|
572
|
+
search={myRepos.search}
|
|
573
|
+
onRepoClick={handleRepoClick}
|
|
574
|
+
/>
|
|
438
575
|
) : (
|
|
439
|
-
|
|
440
|
-
{
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
</span>
|
|
450
|
-
)}
|
|
451
|
-
</div>
|
|
452
|
-
|
|
453
|
-
{group.repos.map((repo, ri) => {
|
|
454
|
-
const flatIdx = groupOffsets[gi] + ri;
|
|
455
|
-
return (
|
|
456
|
-
<button
|
|
457
|
-
key={`${group.key}-${repo.id}`}
|
|
458
|
-
id={`stgm-repo-${flatIdx}`}
|
|
459
|
-
type="button"
|
|
460
|
-
data-idx={flatIdx}
|
|
461
|
-
onClick={() => handleRepoClick(repo)}
|
|
462
|
-
className={[
|
|
463
|
-
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-xs transition-colors",
|
|
464
|
-
flatIdx === focusIndex
|
|
465
|
-
? "bg-accent text-foreground"
|
|
466
|
-
: "text-foreground hover:bg-accent/50",
|
|
467
|
-
].join(" ")}
|
|
468
|
-
role="option"
|
|
469
|
-
aria-selected={flatIdx === focusIndex}
|
|
470
|
-
>
|
|
471
|
-
<span className="min-w-0 flex-1 truncate">
|
|
472
|
-
{group.isRecent ? (
|
|
473
|
-
<HighlightMatch
|
|
474
|
-
text={repo.fullName}
|
|
475
|
-
query={search}
|
|
476
|
-
/>
|
|
477
|
-
) : (
|
|
478
|
-
<HighlightMatch
|
|
479
|
-
text={repo.name}
|
|
480
|
-
query={search}
|
|
481
|
-
/>
|
|
482
|
-
)}
|
|
483
|
-
</span>
|
|
484
|
-
<span className="shrink-0 rounded px-1 py-0.5 text-[0.6rem] bg-muted text-muted-foreground">
|
|
485
|
-
{repo.isPrivate ? "private" : "public"}
|
|
486
|
-
</span>
|
|
487
|
-
</button>
|
|
488
|
-
);
|
|
489
|
-
})}
|
|
490
|
-
</div>
|
|
491
|
-
))}
|
|
492
|
-
|
|
493
|
-
{isBackgroundLoading && (
|
|
494
|
-
<div className="py-1 text-center text-[0.6rem] text-muted-foreground">
|
|
495
|
-
Loading more...
|
|
496
|
-
</div>
|
|
497
|
-
)}
|
|
498
|
-
</>
|
|
576
|
+
<SearchResultsList
|
|
577
|
+
results={githubSearch.results}
|
|
578
|
+
focusIndex={focusIndex}
|
|
579
|
+
isSearching={githubSearch.isSearching}
|
|
580
|
+
query={githubSearch.query}
|
|
581
|
+
totalCount={githubSearch.totalCount}
|
|
582
|
+
hasMore={githubSearch.hasMore}
|
|
583
|
+
onRepoClick={handleRepoClick}
|
|
584
|
+
onLoadMore={githubSearch.loadMore}
|
|
585
|
+
/>
|
|
499
586
|
)}
|
|
500
587
|
</div>
|
|
501
588
|
|
|
@@ -509,10 +596,228 @@ export function GitHubRepoPicker({
|
|
|
509
596
|
/>
|
|
510
597
|
)}
|
|
511
598
|
</div>
|
|
599
|
+
|
|
600
|
+
{/* Footer: manual URL + manage access */}
|
|
601
|
+
<div className="flex items-center gap-3 border-t border-border pt-1.5 text-[0.65rem] text-muted-foreground">
|
|
602
|
+
<button
|
|
603
|
+
type="button"
|
|
604
|
+
onClick={() => setShowManualEntry(true)}
|
|
605
|
+
className="hover:text-foreground transition-colors"
|
|
606
|
+
>
|
|
607
|
+
Paste a URL
|
|
608
|
+
</button>
|
|
609
|
+
<span className="opacity-30">·</span>
|
|
610
|
+
<a
|
|
611
|
+
href={GITHUB_INSTALLATIONS_URL}
|
|
612
|
+
target="_blank"
|
|
613
|
+
rel="noopener noreferrer"
|
|
614
|
+
className="hover:text-foreground transition-colors"
|
|
615
|
+
>
|
|
616
|
+
Manage access
|
|
617
|
+
</a>
|
|
618
|
+
</div>
|
|
512
619
|
</div>
|
|
513
620
|
);
|
|
514
621
|
}
|
|
515
622
|
|
|
623
|
+
// ---------------------------------------------------------------------------
|
|
624
|
+
// My Repos list (grouped)
|
|
625
|
+
// ---------------------------------------------------------------------------
|
|
626
|
+
|
|
627
|
+
function MyReposList({
|
|
628
|
+
groups,
|
|
629
|
+
groupOffsets,
|
|
630
|
+
flatItems,
|
|
631
|
+
focusIndex,
|
|
632
|
+
isLoading,
|
|
633
|
+
isBackgroundLoading,
|
|
634
|
+
search,
|
|
635
|
+
onRepoClick,
|
|
636
|
+
}: {
|
|
637
|
+
groups: RepoGroup[];
|
|
638
|
+
groupOffsets: number[];
|
|
639
|
+
flatItems: GitHubRepo[];
|
|
640
|
+
focusIndex: number;
|
|
641
|
+
isLoading: boolean;
|
|
642
|
+
isBackgroundLoading: boolean;
|
|
643
|
+
search: string;
|
|
644
|
+
onRepoClick: (repo: GitHubRepo) => void;
|
|
645
|
+
}) {
|
|
646
|
+
if (isLoading) return <LoadingSkeleton />;
|
|
647
|
+
|
|
648
|
+
if (flatItems.length === 0) {
|
|
649
|
+
return (
|
|
650
|
+
<div className="py-4 text-center text-xs text-muted-foreground">
|
|
651
|
+
{search ? "No repos match your search" : "No repositories found"}
|
|
652
|
+
</div>
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
return (
|
|
657
|
+
<>
|
|
658
|
+
{groups.map((group, gi) => (
|
|
659
|
+
<div key={group.key}>
|
|
660
|
+
<div className="sticky top-0 z-[1] bg-card/95 px-2 py-1 text-[0.65rem] font-medium text-muted-foreground backdrop-blur-sm">
|
|
661
|
+
{group.label}
|
|
662
|
+
{!group.isRecent && (
|
|
663
|
+
<span className="ml-1 opacity-50">
|
|
664
|
+
({group.repos.length})
|
|
665
|
+
</span>
|
|
666
|
+
)}
|
|
667
|
+
</div>
|
|
668
|
+
|
|
669
|
+
{group.repos.map((repo, ri) => {
|
|
670
|
+
const flatIdx = groupOffsets[gi] + ri;
|
|
671
|
+
return (
|
|
672
|
+
<RepoRow
|
|
673
|
+
key={`${group.key}-${repo.id}`}
|
|
674
|
+
repo={repo}
|
|
675
|
+
flatIdx={flatIdx}
|
|
676
|
+
focusIndex={focusIndex}
|
|
677
|
+
displayName={group.isRecent ? repo.fullName : repo.name}
|
|
678
|
+
query={search}
|
|
679
|
+
onClick={onRepoClick}
|
|
680
|
+
/>
|
|
681
|
+
);
|
|
682
|
+
})}
|
|
683
|
+
</div>
|
|
684
|
+
))}
|
|
685
|
+
|
|
686
|
+
{isBackgroundLoading && (
|
|
687
|
+
<div className="py-1 text-center text-[0.6rem] text-muted-foreground">
|
|
688
|
+
Loading more...
|
|
689
|
+
</div>
|
|
690
|
+
)}
|
|
691
|
+
</>
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// ---------------------------------------------------------------------------
|
|
696
|
+
// All GitHub search results list (flat)
|
|
697
|
+
// ---------------------------------------------------------------------------
|
|
698
|
+
|
|
699
|
+
function SearchResultsList({
|
|
700
|
+
results,
|
|
701
|
+
focusIndex,
|
|
702
|
+
isSearching,
|
|
703
|
+
query,
|
|
704
|
+
totalCount,
|
|
705
|
+
hasMore,
|
|
706
|
+
onRepoClick,
|
|
707
|
+
onLoadMore,
|
|
708
|
+
}: {
|
|
709
|
+
results: readonly GitHubRepo[];
|
|
710
|
+
focusIndex: number;
|
|
711
|
+
isSearching: boolean;
|
|
712
|
+
query: string;
|
|
713
|
+
totalCount: number;
|
|
714
|
+
hasMore: boolean;
|
|
715
|
+
onRepoClick: (repo: GitHubRepo) => void;
|
|
716
|
+
onLoadMore: () => void;
|
|
717
|
+
}) {
|
|
718
|
+
if (!query) {
|
|
719
|
+
return (
|
|
720
|
+
<div className="py-6 text-center text-xs text-muted-foreground">
|
|
721
|
+
Type to search all of GitHub
|
|
722
|
+
</div>
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (isSearching && results.length === 0) {
|
|
727
|
+
return <LoadingSkeleton />;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if (results.length === 0) {
|
|
731
|
+
return (
|
|
732
|
+
<div className="py-4 text-center text-xs text-muted-foreground">
|
|
733
|
+
No repositories found
|
|
734
|
+
</div>
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
return (
|
|
739
|
+
<>
|
|
740
|
+
{totalCount > 0 && (
|
|
741
|
+
<div className="px-2 py-1 text-[0.6rem] text-muted-foreground">
|
|
742
|
+
{totalCount.toLocaleString()} {totalCount === 1 ? "result" : "results"}
|
|
743
|
+
</div>
|
|
744
|
+
)}
|
|
745
|
+
|
|
746
|
+
{results.map((repo, i) => (
|
|
747
|
+
<RepoRow
|
|
748
|
+
key={repo.id}
|
|
749
|
+
repo={repo}
|
|
750
|
+
flatIdx={i}
|
|
751
|
+
focusIndex={focusIndex}
|
|
752
|
+
displayName={repo.fullName}
|
|
753
|
+
query={query}
|
|
754
|
+
onClick={onRepoClick}
|
|
755
|
+
/>
|
|
756
|
+
))}
|
|
757
|
+
|
|
758
|
+
{isSearching && (
|
|
759
|
+
<div className="py-1 text-center text-[0.6rem] text-muted-foreground">
|
|
760
|
+
Searching...
|
|
761
|
+
</div>
|
|
762
|
+
)}
|
|
763
|
+
|
|
764
|
+
{hasMore && !isSearching && (
|
|
765
|
+
<button
|
|
766
|
+
type="button"
|
|
767
|
+
onClick={onLoadMore}
|
|
768
|
+
className="w-full py-1.5 text-center text-[0.65rem] text-muted-foreground hover:text-foreground transition-colors"
|
|
769
|
+
>
|
|
770
|
+
Load more results
|
|
771
|
+
</button>
|
|
772
|
+
)}
|
|
773
|
+
</>
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// ---------------------------------------------------------------------------
|
|
778
|
+
// Shared repo row
|
|
779
|
+
// ---------------------------------------------------------------------------
|
|
780
|
+
|
|
781
|
+
function RepoRow({
|
|
782
|
+
repo,
|
|
783
|
+
flatIdx,
|
|
784
|
+
focusIndex,
|
|
785
|
+
displayName,
|
|
786
|
+
query,
|
|
787
|
+
onClick,
|
|
788
|
+
}: {
|
|
789
|
+
repo: GitHubRepo;
|
|
790
|
+
flatIdx: number;
|
|
791
|
+
focusIndex: number;
|
|
792
|
+
displayName: string;
|
|
793
|
+
query: string;
|
|
794
|
+
onClick: (repo: GitHubRepo) => void;
|
|
795
|
+
}) {
|
|
796
|
+
return (
|
|
797
|
+
<button
|
|
798
|
+
id={`stgm-repo-${flatIdx}`}
|
|
799
|
+
type="button"
|
|
800
|
+
data-idx={flatIdx}
|
|
801
|
+
onClick={() => onClick(repo)}
|
|
802
|
+
className={[
|
|
803
|
+
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-xs transition-colors",
|
|
804
|
+
flatIdx === focusIndex
|
|
805
|
+
? "bg-accent text-foreground"
|
|
806
|
+
: "text-foreground hover:bg-accent/50",
|
|
807
|
+
].join(" ")}
|
|
808
|
+
role="option"
|
|
809
|
+
aria-selected={flatIdx === focusIndex}
|
|
810
|
+
>
|
|
811
|
+
<span className="min-w-0 flex-1 truncate">
|
|
812
|
+
<HighlightMatch text={displayName} query={query} />
|
|
813
|
+
</span>
|
|
814
|
+
<span className="shrink-0 rounded px-1 py-0.5 text-[0.6rem] bg-muted text-muted-foreground">
|
|
815
|
+
{repo.isPrivate ? "private" : "public"}
|
|
816
|
+
</span>
|
|
817
|
+
</button>
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
|
|
516
821
|
// ---------------------------------------------------------------------------
|
|
517
822
|
// Loading skeleton
|
|
518
823
|
// ---------------------------------------------------------------------------
|