@agimon-ai/po-git 0.0.0-alpha.8 → 0.0.0-alpha.80
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/LICENSE +149 -21
- package/README.md +16 -4
- package/dist/apiContracts.cjs +34 -1
- package/dist/apiContracts.cjs.map +1 -1
- package/dist/apiContracts.mjs +34 -1
- package/dist/apiContracts.mjs.map +1 -1
- package/dist/extensions/native.mjs +2159 -520
- package/dist/extensions/native.mjs.map +1 -1
- package/dist/extensions/server.cjs +512 -275
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.d.mts +2 -2
- package/dist/extensions/server.d.mts.map +1 -1
- package/dist/extensions/server.mjs +509 -272
- package/dist/extensions/server.mjs.map +1 -1
- package/dist/extensions/web.mjs +1549 -1294
- package/dist/extensions/web.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.mts +2 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{worktreeOperations-BSeyxUai.mjs → worktreeOperations-B3E4JY4H.mjs} +258 -85
- package/dist/worktreeOperations-B3E4JY4H.mjs.map +1 -0
- package/dist/{worktreeOperations-DGUbUnYU.cjs → worktreeOperations-CD7s0YnO.cjs} +288 -85
- package/dist/worktreeOperations-CD7s0YnO.cjs.map +1 -0
- package/llms.txt +1 -1
- package/package.json +21 -19
- package/src/prompts/{po-use-git → codemode-how-to-git}/SKILL.md +35 -32
- package/dist/worktreeOperations-BSeyxUai.mjs.map +0 -1
- package/dist/worktreeOperations-DGUbUnYU.cjs.map +0 -1
package/dist/extensions/web.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createApiClient, defineActivityGroup, defineChannelFile, defineFill, defineGlobalStore, defineLeaderBinding, defineRepositorySettingsPanel, defineSessionStore, defineToolRenderer, defineWebLifecycle, defineWebPlugin, sessionApiAddress } from "@agimon-ai/po-core/web";
|
|
2
|
-
import {
|
|
3
|
-
import { createElement, useCallback, useEffect,
|
|
4
|
-
import {
|
|
2
|
+
import { ArrowRightIcon, BranchIcon, Button, CheckIcon, ChevronDownIcon, ChevronUpIcon, CodeBlock, Dialog, DialogContent, DialogTitle, DiffView, Dot, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptyState, FolderIcon, Input, KebabIcon, MessageIcon, MessageItem, MessageItemBody, ReviewCommentDraft, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SettingsActions, SettingsCard, SettingsNote, SettingsRow, SettingsRowDescription, SettingsRowText, SettingsRowTitle, SettingsSection, SettingsSectionDescription, SettingsSectionHeader, SettingsSectionTitle, ShieldIcon, Spinner, StatusBadge, Tabs, TabsContent, TabsList, TabsTrigger, TrashIcon, buildReviewPrompt } from "@agimon-ai/po-web-components";
|
|
3
|
+
import { createElement, useCallback, useEffect, useEffectEvent, useId, useRef, useState } from "react";
|
|
4
|
+
import { browserTransport } from "@agimon-ai/po-web-security/browser";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { useStore } from "@tanstack/react-store";
|
|
7
7
|
const api = createApiClient({
|
|
@@ -11,6 +11,16 @@ const api = createApiClient({
|
|
|
11
11
|
path: "/branches",
|
|
12
12
|
response: void 0
|
|
13
13
|
},
|
|
14
|
+
deleteBranch: {
|
|
15
|
+
method: "DELETE",
|
|
16
|
+
path: "/branches",
|
|
17
|
+
response: void 0
|
|
18
|
+
},
|
|
19
|
+
openBranchFolder: {
|
|
20
|
+
method: "POST",
|
|
21
|
+
path: "/branches/open",
|
|
22
|
+
response: void 0
|
|
23
|
+
},
|
|
14
24
|
/** Workspace: a top-level worktree session on an existing or new branch. */
|
|
15
25
|
createSession: {
|
|
16
26
|
method: "POST",
|
|
@@ -51,10 +61,14 @@ const api = createApiClient({
|
|
|
51
61
|
"session"
|
|
52
62
|
],
|
|
53
63
|
basePath: "git",
|
|
54
|
-
transport: (input, init) =>
|
|
64
|
+
transport: (input, init) => browserTransport.fetch(input, init),
|
|
55
65
|
sessionAddress: sessionApiAddress
|
|
56
66
|
});
|
|
57
67
|
//#endregion
|
|
68
|
+
//#region src/constants/git.ts
|
|
69
|
+
/** Installs the rift CLI. Shown to the person to copy, and named in the error an agent reads. */
|
|
70
|
+
const RIFT_INSTALL_COMMAND = "npm install -g rift-snapshot";
|
|
71
|
+
//#endregion
|
|
58
72
|
//#region src/extensions/workspaces/(frontend)/_lib/newSessionForm.ts
|
|
59
73
|
const MAX_LISTED = 200;
|
|
60
74
|
/** Local branches first, then remote-only ones, filtered by a case-insensitive substring. */
|
|
@@ -113,20 +127,313 @@ function createRequest(state) {
|
|
|
113
127
|
}
|
|
114
128
|
//#endregion
|
|
115
129
|
//#region src/extensions/workspaces/(frontend)/fill/_components/GitNewSessionDialog.tsx
|
|
116
|
-
|
|
117
|
-
{
|
|
130
|
+
function Field$1({ label, children }) {
|
|
131
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
132
|
+
className: "flex flex-col gap-2 text-base text-po-text",
|
|
133
|
+
children: [/* @__PURE__ */ jsx("span", { children: label }), children]
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/** Inline presentation also lets story capture inspect content, rather than a portal-only root. */
|
|
137
|
+
function GitNewSessionDialogView({ workspaceRoot, branches, loadError = "", initialForm, busy = false, error = "", riftMissing = false, onClose, onCreate, onDeleteBranch, onOpenBranchFolder }) {
|
|
138
|
+
const [form, setForm] = useState({
|
|
118
139
|
mode: "existing",
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
140
|
+
newBranch: "",
|
|
141
|
+
baseRef: branches?.defaultBase ?? "",
|
|
142
|
+
name: "",
|
|
143
|
+
...initialForm
|
|
144
|
+
});
|
|
145
|
+
const [query, setQuery] = useState("");
|
|
146
|
+
const [deleting, setDeleting] = useState();
|
|
147
|
+
const ready = branches?.repository === true && loadError === "";
|
|
148
|
+
const loading = branches === null && loadError === "";
|
|
149
|
+
const mode = ready || loading ? form.mode : "plain";
|
|
150
|
+
const state = {
|
|
151
|
+
...form,
|
|
152
|
+
mode,
|
|
153
|
+
baseRef: form.baseRef || branches?.defaultBase || ""
|
|
154
|
+
};
|
|
155
|
+
const problem = formProblem$1(state);
|
|
156
|
+
const update = (patch) => setForm((current) => ({
|
|
157
|
+
...current,
|
|
158
|
+
...patch
|
|
159
|
+
}));
|
|
160
|
+
const options = branches === null ? [] : branchOptions(branches, query);
|
|
161
|
+
const bases = branches === null ? [] : baseOptions(branches);
|
|
162
|
+
const submit = () => {
|
|
163
|
+
if (!busy && !loading && deleting === void 0 && problem === void 0) onCreate(state);
|
|
164
|
+
};
|
|
165
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
166
|
+
"data-testid": "new-session-dialog",
|
|
167
|
+
"aria-label": "New session",
|
|
168
|
+
className: "shrink-0 overflow-hidden rounded-lg border border-po-border bg-po-panel shadow-sm",
|
|
169
|
+
children: [
|
|
170
|
+
/* @__PURE__ */ jsxs("header", {
|
|
171
|
+
className: "border-b border-po-border-soft px-6 py-4",
|
|
172
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
173
|
+
className: "text-xl font-semibold text-po-hi",
|
|
174
|
+
children: "New session"
|
|
175
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
176
|
+
className: "mt-1 font-mono text-sm text-po-dim",
|
|
177
|
+
children: workspaceRoot
|
|
178
|
+
})]
|
|
179
|
+
}),
|
|
180
|
+
/* @__PURE__ */ jsxs("div", {
|
|
181
|
+
className: "flex flex-col gap-5 p-6",
|
|
182
|
+
children: [
|
|
183
|
+
!ready ? /* @__PURE__ */ jsx("p", {
|
|
184
|
+
"data-testid": "git-new-session-plain-note",
|
|
185
|
+
className: "text-base text-po-dim",
|
|
186
|
+
children: loading ? "Reading branches..." : loadError || "This workspace is not a git checkout. Start a plain session in this folder."
|
|
187
|
+
}) : /* @__PURE__ */ jsxs(Tabs, {
|
|
188
|
+
value: mode,
|
|
189
|
+
onValueChange: (value) => update({ mode: value }),
|
|
190
|
+
className: "flex flex-col gap-5",
|
|
191
|
+
children: [
|
|
192
|
+
/* @__PURE__ */ jsxs(TabsList, {
|
|
193
|
+
"aria-label": "Session kind",
|
|
194
|
+
className: "rounded-md border border-po-border-soft bg-po-bg p-1",
|
|
195
|
+
children: [
|
|
196
|
+
/* @__PURE__ */ jsx(TabsTrigger, {
|
|
197
|
+
"data-testid": "git-new-session-mode-existing",
|
|
198
|
+
value: "existing",
|
|
199
|
+
disabled: busy,
|
|
200
|
+
className: "flex-1 justify-center",
|
|
201
|
+
children: "Existing branch"
|
|
202
|
+
}),
|
|
203
|
+
/* @__PURE__ */ jsx(TabsTrigger, {
|
|
204
|
+
"data-testid": "git-new-session-mode-new",
|
|
205
|
+
value: "new",
|
|
206
|
+
disabled: busy,
|
|
207
|
+
className: "flex-1 justify-center",
|
|
208
|
+
children: "New branch"
|
|
209
|
+
}),
|
|
210
|
+
/* @__PURE__ */ jsx(TabsTrigger, {
|
|
211
|
+
"data-testid": "git-new-session-mode-plain",
|
|
212
|
+
value: "plain",
|
|
213
|
+
disabled: busy,
|
|
214
|
+
className: "flex-1 justify-center",
|
|
215
|
+
children: "No branch"
|
|
216
|
+
})
|
|
217
|
+
]
|
|
218
|
+
}),
|
|
219
|
+
/* @__PURE__ */ jsxs(TabsContent, {
|
|
220
|
+
value: "existing",
|
|
221
|
+
className: "flex flex-col gap-2",
|
|
222
|
+
children: [
|
|
223
|
+
/* @__PURE__ */ jsx("p", {
|
|
224
|
+
className: "text-base font-medium",
|
|
225
|
+
children: "Branch"
|
|
226
|
+
}),
|
|
227
|
+
/* @__PURE__ */ jsx(Input, {
|
|
228
|
+
"data-testid": "git-new-session-branch-search",
|
|
229
|
+
"aria-label": "Find a branch",
|
|
230
|
+
placeholder: "Find a branch",
|
|
231
|
+
value: query,
|
|
232
|
+
onChange: (event) => setQuery(event.target.value),
|
|
233
|
+
disabled: busy
|
|
234
|
+
}),
|
|
235
|
+
/* @__PURE__ */ jsxs("fieldset", {
|
|
236
|
+
"aria-label": "Branches",
|
|
237
|
+
className: "max-h-64 min-w-0 overflow-y-auto rounded-md border border-po-border-soft bg-po-bg p-1",
|
|
238
|
+
children: [options.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
239
|
+
className: "px-3 py-2 text-sm text-po-dim",
|
|
240
|
+
children: "No matching branches"
|
|
241
|
+
}) : null, options.map((branch, index) => /* @__PURE__ */ jsxs("div", { children: [branch.remote !== void 0 && (index === 0 || options[index - 1]?.remote === void 0) ? /* @__PURE__ */ jsx("p", {
|
|
242
|
+
className: "mt-1 border-t border-po-border-soft px-3 pt-2 text-sm text-po-dim",
|
|
243
|
+
children: "Remote branches"
|
|
244
|
+
}) : null, /* @__PURE__ */ jsxs("div", {
|
|
245
|
+
className: "flex min-w-0 items-start gap-1",
|
|
246
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
247
|
+
"data-testid": "git-new-session-branch-option",
|
|
248
|
+
"data-branch": branch.name,
|
|
249
|
+
"data-remote": branch.remote,
|
|
250
|
+
"aria-pressed": form.selected?.key === branch.key,
|
|
251
|
+
disabled: branch.busyAt !== void 0 || busy,
|
|
252
|
+
onClick: () => update({ selected: branch }),
|
|
253
|
+
className: `flex min-w-0 flex-1 flex-col gap-1 rounded px-3 py-2 text-left text-base ${form.selected?.key === branch.key ? "bg-po-tint-green text-po-green" : "text-po-text"}`,
|
|
254
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
255
|
+
className: "flex items-center justify-between gap-2",
|
|
256
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
257
|
+
className: "font-mono break-all",
|
|
258
|
+
children: branch.remote === void 0 ? branch.name : `${branch.remote}/${branch.name}`
|
|
259
|
+
}), form.selected?.key === branch.key ? /* @__PURE__ */ jsx(CheckIcon, { className: "size-4 shrink-0" }) : null]
|
|
260
|
+
}), branch.busyAt !== void 0 ? /* @__PURE__ */ jsxs("span", {
|
|
261
|
+
className: "text-sm text-po-dim",
|
|
262
|
+
children: ["Checked out in ", /* @__PURE__ */ jsx("span", {
|
|
263
|
+
className: "font-mono break-all",
|
|
264
|
+
children: branch.busyAt
|
|
265
|
+
})]
|
|
266
|
+
}) : branch.current ? /* @__PURE__ */ jsx("span", {
|
|
267
|
+
className: "text-sm text-po-dim",
|
|
268
|
+
children: "Current"
|
|
269
|
+
}) : null]
|
|
270
|
+
}), /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
271
|
+
asChild: true,
|
|
272
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
273
|
+
variant: "ghost",
|
|
274
|
+
size: "icon-md",
|
|
275
|
+
className: "mt-1",
|
|
276
|
+
"aria-label": `Actions for ${branch.remote === void 0 ? branch.name : `${branch.remote}/${branch.name}`}`,
|
|
277
|
+
disabled: busy,
|
|
278
|
+
children: /* @__PURE__ */ jsx(KebabIcon, { className: "size-4" })
|
|
279
|
+
})
|
|
280
|
+
}), /* @__PURE__ */ jsxs(DropdownMenuContent, { children: [
|
|
281
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
282
|
+
disabled: branch.busyAt === void 0 || onOpenBranchFolder === void 0,
|
|
283
|
+
title: branch.busyAt === void 0 ? "This branch has no checked-out folder." : branch.busyAt,
|
|
284
|
+
onSelect: () => onOpenBranchFolder?.(branch.name),
|
|
285
|
+
children: [/* @__PURE__ */ jsx(FolderIcon, { className: "size-4" }), "Open in Finder"]
|
|
286
|
+
}),
|
|
287
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
288
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
289
|
+
variant: "destructive",
|
|
290
|
+
disabled: branch.remote !== void 0 || branch.busyAt !== void 0 || branch.current || onDeleteBranch === void 0,
|
|
291
|
+
title: branch.remote !== void 0 ? "Only local branches can be deleted here." : branch.busyAt !== void 0 || branch.current ? "Close this branch’s worktree before deleting it." : void 0,
|
|
292
|
+
onSelect: () => setDeleting(branch),
|
|
293
|
+
children: [/* @__PURE__ */ jsx(TrashIcon, { className: "size-4" }), "Delete branch"]
|
|
294
|
+
})
|
|
295
|
+
] })] })]
|
|
296
|
+
})] }, branch.key))]
|
|
297
|
+
}),
|
|
298
|
+
deleting !== void 0 ? /* @__PURE__ */ jsxs("fieldset", {
|
|
299
|
+
"aria-label": "Confirm branch deletion",
|
|
300
|
+
className: "flex flex-col gap-3 rounded-md border border-po-edge-red bg-po-tint-red p-3",
|
|
301
|
+
children: [/* @__PURE__ */ jsxs("p", {
|
|
302
|
+
className: "text-sm text-po-text",
|
|
303
|
+
children: [
|
|
304
|
+
"Delete local branch ",
|
|
305
|
+
/* @__PURE__ */ jsx("span", {
|
|
306
|
+
className: "font-mono break-all",
|
|
307
|
+
children: deleting.name
|
|
308
|
+
}),
|
|
309
|
+
"? Git will refuse if it contains unmerged commits. No worktree or remote branch will be removed."
|
|
310
|
+
]
|
|
311
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
312
|
+
className: "flex justify-end gap-2",
|
|
313
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
314
|
+
variant: "ghost",
|
|
315
|
+
disabled: busy,
|
|
316
|
+
onClick: () => setDeleting(void 0),
|
|
317
|
+
children: "Cancel deletion"
|
|
318
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
319
|
+
variant: "danger-outline",
|
|
320
|
+
disabled: busy,
|
|
321
|
+
onClick: async () => {
|
|
322
|
+
if (busy || onDeleteBranch === void 0) return;
|
|
323
|
+
if (await onDeleteBranch(deleting.name)) {
|
|
324
|
+
if (form.selected?.key === deleting.key) update({ selected: void 0 });
|
|
325
|
+
setDeleting(void 0);
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
children: "Delete branch"
|
|
329
|
+
})]
|
|
330
|
+
})]
|
|
331
|
+
}) : null
|
|
332
|
+
]
|
|
333
|
+
}),
|
|
334
|
+
/* @__PURE__ */ jsxs(TabsContent, {
|
|
335
|
+
value: "new",
|
|
336
|
+
className: "flex flex-col gap-4",
|
|
337
|
+
children: [/* @__PURE__ */ jsx(Field$1, {
|
|
338
|
+
label: "Branch name",
|
|
339
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
340
|
+
"data-testid": "git-new-session-new-branch",
|
|
341
|
+
"aria-label": "Branch name",
|
|
342
|
+
placeholder: "feature/my-change",
|
|
343
|
+
className: "font-mono",
|
|
344
|
+
value: form.newBranch,
|
|
345
|
+
onChange: (event) => update({ newBranch: event.target.value }),
|
|
346
|
+
disabled: busy
|
|
347
|
+
})
|
|
348
|
+
}), /* @__PURE__ */ jsx(Field$1, {
|
|
349
|
+
label: "From",
|
|
350
|
+
children: /* @__PURE__ */ jsxs(Select, {
|
|
351
|
+
value: form.baseRef || branches?.defaultBase || "",
|
|
352
|
+
onValueChange: (baseRef) => update({ baseRef }),
|
|
353
|
+
disabled: busy,
|
|
354
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
355
|
+
"data-testid": "git-new-session-base",
|
|
356
|
+
"aria-label": "From",
|
|
357
|
+
className: "font-mono",
|
|
358
|
+
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
359
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: bases.map((base) => /* @__PURE__ */ jsx(SelectItem, {
|
|
360
|
+
value: base,
|
|
361
|
+
children: base
|
|
362
|
+
}, base)) })]
|
|
363
|
+
})
|
|
364
|
+
})]
|
|
365
|
+
}),
|
|
366
|
+
/* @__PURE__ */ jsx(TabsContent, {
|
|
367
|
+
value: "plain",
|
|
368
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
369
|
+
className: "text-base text-po-dim",
|
|
370
|
+
children: "Start in this workspace folder, without creating a worktree."
|
|
371
|
+
})
|
|
372
|
+
})
|
|
373
|
+
]
|
|
374
|
+
}),
|
|
375
|
+
/* @__PURE__ */ jsx(Field$1, {
|
|
376
|
+
label: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
377
|
+
"Name",
|
|
378
|
+
" ",
|
|
379
|
+
/* @__PURE__ */ jsx("span", {
|
|
380
|
+
className: "text-sm text-po-dim",
|
|
381
|
+
children: mode === "plain" ? "(optional)" : "(optional, defaults to the branch)"
|
|
382
|
+
})
|
|
383
|
+
] }),
|
|
384
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
385
|
+
"data-testid": "new-session-name",
|
|
386
|
+
"aria-label": "Session name",
|
|
387
|
+
placeholder: "Session name",
|
|
388
|
+
value: form.name,
|
|
389
|
+
onChange: (event) => update({ name: event.target.value }),
|
|
390
|
+
onKeyDown: (event) => {
|
|
391
|
+
if (event.key === "Enter") submit();
|
|
392
|
+
},
|
|
393
|
+
disabled: busy
|
|
394
|
+
})
|
|
395
|
+
}),
|
|
396
|
+
mode !== "plain" ? /* @__PURE__ */ jsx("p", {
|
|
397
|
+
className: "text-sm leading-relaxed text-po-dim",
|
|
398
|
+
children: "Runs in its own worktree, so this checkout stays as it is."
|
|
399
|
+
}) : null,
|
|
400
|
+
riftMissing ? /* @__PURE__ */ jsxs("div", {
|
|
401
|
+
"data-testid": "rift-install-notice",
|
|
402
|
+
role: "alert",
|
|
403
|
+
className: "flex flex-col gap-2 rounded-md border border-po-edge-magenta bg-po-tint-magenta px-3 py-3 text-sm text-po-hi",
|
|
404
|
+
children: [/* @__PURE__ */ jsx("p", { children: "This repository uses Rift, and its CLI is not installed here. Run these in the repository root, then try again." }), /* @__PURE__ */ jsx(CodeBlock, {
|
|
405
|
+
text: `${RIFT_INSTALL_COMMAND}
|
|
406
|
+
rift init`,
|
|
407
|
+
className: "language-bash"
|
|
408
|
+
})]
|
|
409
|
+
}) : error ? /* @__PURE__ */ jsx("p", {
|
|
410
|
+
"data-testid": "new-session-error",
|
|
411
|
+
role: "alert",
|
|
412
|
+
className: "rounded-md border border-po-edge-red bg-po-tint-red p-3 text-sm text-po-red",
|
|
413
|
+
children: error
|
|
414
|
+
}) : null
|
|
415
|
+
]
|
|
416
|
+
}),
|
|
417
|
+
/* @__PURE__ */ jsxs("footer", {
|
|
418
|
+
className: "flex items-center justify-between border-t border-po-border-soft px-6 py-4",
|
|
419
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
420
|
+
"data-testid": "new-session-cancel",
|
|
421
|
+
variant: "ghost",
|
|
422
|
+
disabled: busy,
|
|
423
|
+
onClick: onClose,
|
|
424
|
+
children: "Cancel"
|
|
425
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
426
|
+
"data-testid": "new-session-create",
|
|
427
|
+
variant: "primary",
|
|
428
|
+
title: problem,
|
|
429
|
+
disabled: busy || loading || deleting !== void 0 || problem !== void 0,
|
|
430
|
+
onClick: submit,
|
|
431
|
+
children: busy ? mode === "plain" ? "Creating session..." : "Creating worktree..." : riftMissing ? "Try again" : "Create session"
|
|
432
|
+
})]
|
|
433
|
+
})
|
|
434
|
+
]
|
|
435
|
+
});
|
|
436
|
+
}
|
|
130
437
|
function errorOf$1(body, status) {
|
|
131
438
|
return typeof body === "object" && body !== null && typeof body.error === "string" ? body.error : `The cockpit answered ${String(status)}.`;
|
|
132
439
|
}
|
|
@@ -138,70 +445,50 @@ function errorOf$1(body, status) {
|
|
|
138
445
|
function GitNewSessionDialog({ newSession }) {
|
|
139
446
|
const [branches, setBranches] = useState(null);
|
|
140
447
|
const [loadError, setLoadError] = useState("");
|
|
141
|
-
const [form, setForm] = useState({
|
|
142
|
-
mode: "existing",
|
|
143
|
-
newBranch: "",
|
|
144
|
-
baseRef: "",
|
|
145
|
-
name: ""
|
|
146
|
-
});
|
|
147
|
-
const [query, setQuery] = useState("");
|
|
148
448
|
const [error, setError] = useState("");
|
|
449
|
+
const [riftMissing, setRiftMissing] = useState(false);
|
|
149
450
|
const [busy, setBusy] = useState(false);
|
|
150
451
|
const workspaceId = newSession?.workspaceId;
|
|
151
|
-
const request = newSession?.requestWithStepUp;
|
|
452
|
+
const request = useEffectEvent((input) => newSession?.requestWithStepUp(input));
|
|
152
453
|
useEffect(() => {
|
|
153
|
-
if (workspaceId === void 0
|
|
454
|
+
if (workspaceId === void 0) return;
|
|
154
455
|
let stale = false;
|
|
155
456
|
const url = api.workspace(workspaceId).branches.url();
|
|
156
|
-
request(url)
|
|
457
|
+
request(url)?.then(async (response) => {
|
|
157
458
|
const body = await response.json().catch(() => void 0);
|
|
158
459
|
if (stale) return;
|
|
159
460
|
if (!response.ok) {
|
|
160
461
|
setLoadError(errorOf$1(body, response.status));
|
|
161
462
|
return;
|
|
162
463
|
}
|
|
163
|
-
|
|
164
|
-
setBranches(loaded);
|
|
165
|
-
setForm((current) => ({
|
|
166
|
-
...current,
|
|
167
|
-
baseRef: loaded.defaultBase ?? ""
|
|
168
|
-
}));
|
|
464
|
+
setBranches(body);
|
|
169
465
|
}).catch(() => {
|
|
170
466
|
if (!stale) setLoadError("The branches could not be loaded.");
|
|
171
467
|
});
|
|
172
468
|
return () => {
|
|
173
469
|
stale = true;
|
|
174
470
|
};
|
|
175
|
-
}, [workspaceId
|
|
471
|
+
}, [workspaceId]);
|
|
176
472
|
if (newSession === void 0) return null;
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
const state = {
|
|
180
|
-
...form,
|
|
181
|
-
mode
|
|
182
|
-
};
|
|
183
|
-
const problem = formProblem$1(state);
|
|
184
|
-
const options = branches === null ? [] : branchOptions(branches, query);
|
|
185
|
-
const bases = branches === null ? [] : baseOptions(branches);
|
|
186
|
-
const update = (patch) => setForm((current) => ({
|
|
187
|
-
...current,
|
|
188
|
-
...patch
|
|
189
|
-
}));
|
|
190
|
-
const submit = async () => {
|
|
191
|
-
if (busy || problem !== void 0) return;
|
|
473
|
+
const submit = async (state) => {
|
|
474
|
+
if (busy || formProblem$1(state) !== void 0) return;
|
|
192
475
|
setBusy(true);
|
|
193
476
|
setError("");
|
|
477
|
+
setRiftMissing(false);
|
|
194
478
|
const body = createRequest(state);
|
|
195
479
|
let failure;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
480
|
+
try {
|
|
481
|
+
if (body === void 0) failure = await newSession.createPlain(state.name);
|
|
482
|
+
else {
|
|
483
|
+
const response = await newSession.requestWithStepUp(api.workspace(newSession.workspaceId).createSession.url(), {
|
|
484
|
+
method: "POST",
|
|
485
|
+
headers: { "Content-Type": "application/json" },
|
|
486
|
+
body: JSON.stringify(body)
|
|
487
|
+
});
|
|
488
|
+
const answer = await response.json().catch(() => void 0);
|
|
489
|
+
failure = response.ok ? await newSession.openCreated(answer.sessionId) : errorOf$1(answer, response.status);
|
|
490
|
+
setRiftMissing(!response.ok && answer?.code === "rift_missing");
|
|
491
|
+
}
|
|
205
492
|
} catch {
|
|
206
493
|
failure = "The cockpit is unreachable.";
|
|
207
494
|
}
|
|
@@ -209,161 +496,59 @@ function GitNewSessionDialog({ newSession }) {
|
|
|
209
496
|
setError(failure);
|
|
210
497
|
setBusy(false);
|
|
211
498
|
};
|
|
499
|
+
const branchAction = async (branch, action) => {
|
|
500
|
+
if (busy) return false;
|
|
501
|
+
setBusy(true);
|
|
502
|
+
setError("");
|
|
503
|
+
setRiftMissing(false);
|
|
504
|
+
try {
|
|
505
|
+
const workspace = api.workspace(newSession.workspaceId);
|
|
506
|
+
const response = await newSession.requestWithStepUp(action === "delete" ? workspace.deleteBranch.url() : workspace.openBranchFolder.url(), {
|
|
507
|
+
method: action === "delete" ? "DELETE" : "POST",
|
|
508
|
+
headers: { "Content-Type": "application/json" },
|
|
509
|
+
body: JSON.stringify({ branch })
|
|
510
|
+
});
|
|
511
|
+
const body = await response.json().catch(() => void 0);
|
|
512
|
+
if (!response.ok) {
|
|
513
|
+
setError(errorOf$1(body, response.status));
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
if (action === "delete") setBranches((current) => current === null ? null : {
|
|
517
|
+
...current,
|
|
518
|
+
local: current.local.filter((item) => item.name !== branch)
|
|
519
|
+
});
|
|
520
|
+
return true;
|
|
521
|
+
} catch {
|
|
522
|
+
setError("The cockpit is unreachable.");
|
|
523
|
+
return false;
|
|
524
|
+
} finally {
|
|
525
|
+
setBusy(false);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
212
528
|
return /* @__PURE__ */ jsx(Dialog, {
|
|
213
529
|
open: true,
|
|
214
|
-
onOpenChange: (next) =>
|
|
530
|
+
onOpenChange: (next) => {
|
|
531
|
+
if (!next && !busy) newSession.close();
|
|
532
|
+
},
|
|
215
533
|
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
216
534
|
width: "md",
|
|
217
|
-
|
|
535
|
+
className: "overflow-y-auto p-0",
|
|
218
536
|
"aria-describedby": void 0,
|
|
219
|
-
children: [/* @__PURE__ */ jsx(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
"data-testid": `git-new-session-mode-${entry.mode}`,
|
|
235
|
-
disabled: busy,
|
|
236
|
-
className: mode === entry.mode ? "border-po-blue/60 text-po-hi" : void 0,
|
|
237
|
-
onClick: () => update({ mode: entry.mode }),
|
|
238
|
-
children: entry.label
|
|
239
|
-
}, entry.mode))
|
|
240
|
-
}) : branches === null && loadError === "" ? /* @__PURE__ */ jsx("p", {
|
|
241
|
-
className: "text-sm text-po-dim",
|
|
242
|
-
children: "reading branches…"
|
|
243
|
-
}) : /* @__PURE__ */ jsx("p", {
|
|
244
|
-
"data-testid": "git-new-session-plain-note",
|
|
245
|
-
className: "text-sm text-po-dim",
|
|
246
|
-
children: loadError || "This workspace is not a git checkout, so the session starts in its folder."
|
|
247
|
-
}),
|
|
248
|
-
mode === "existing" && gitReady ? /* @__PURE__ */ jsxs("div", {
|
|
249
|
-
className: "flex flex-col gap-1",
|
|
250
|
-
children: [/* @__PURE__ */ jsx(Input, {
|
|
251
|
-
"data-testid": "git-new-session-branch-search",
|
|
252
|
-
"aria-label": "find a branch",
|
|
253
|
-
value: query,
|
|
254
|
-
autoFocus: true,
|
|
255
|
-
disabled: busy,
|
|
256
|
-
onChange: (event) => setQuery(event.target.value),
|
|
257
|
-
placeholder: "find a branch"
|
|
258
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
259
|
-
role: "listbox",
|
|
260
|
-
"aria-label": "branches",
|
|
261
|
-
className: "max-h-48 overflow-y-auto rounded border border-po-border bg-po-deep p-1",
|
|
262
|
-
children: [options.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
263
|
-
className: "px-2 py-1 text-sm text-po-dim",
|
|
264
|
-
children: "no matching branches"
|
|
265
|
-
}) : null, options.map((option) => /* @__PURE__ */ jsxs(OptionRow, {
|
|
266
|
-
density: "compact",
|
|
267
|
-
active: form.selected?.key === option.key,
|
|
268
|
-
disabled: option.busyAt !== void 0 || busy,
|
|
269
|
-
"data-testid": "git-new-session-branch-option",
|
|
270
|
-
"data-branch": option.name,
|
|
271
|
-
"data-remote": option.remote,
|
|
272
|
-
onClick: () => update({ selected: option }),
|
|
273
|
-
className: `w-full px-2.5 py-1 text-base ${option.busyAt !== void 0 ? "opacity-50" : ""}`,
|
|
274
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
275
|
-
className: "min-w-0 flex-1 truncate",
|
|
276
|
-
children: option.remote === void 0 ? option.name : `${option.remote}/${option.name}`
|
|
277
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
278
|
-
className: "shrink-0 text-sm text-po-dim",
|
|
279
|
-
children: option.current && option.busyAt !== void 0 ? "current · checked out" : option.current ? "current" : option.busyAt !== void 0 ? "checked out" : option.remote !== void 0 ? "remote" : ""
|
|
280
|
-
})]
|
|
281
|
-
}, option.key))]
|
|
282
|
-
})]
|
|
283
|
-
}) : null,
|
|
284
|
-
mode === "new" && gitReady ? /* @__PURE__ */ jsxs("div", {
|
|
285
|
-
className: "flex flex-col gap-2",
|
|
286
|
-
children: [/* @__PURE__ */ jsxs("label", {
|
|
287
|
-
htmlFor: "git-new-session-new-branch",
|
|
288
|
-
className: "flex flex-col gap-1",
|
|
289
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
290
|
-
className: "text-sm text-po-dim",
|
|
291
|
-
children: "new branch"
|
|
292
|
-
}), /* @__PURE__ */ jsx(Input, {
|
|
293
|
-
id: "git-new-session-new-branch",
|
|
294
|
-
"data-testid": "git-new-session-new-branch",
|
|
295
|
-
value: form.newBranch,
|
|
296
|
-
autoFocus: true,
|
|
297
|
-
disabled: busy,
|
|
298
|
-
onChange: (event) => update({ newBranch: event.target.value }),
|
|
299
|
-
placeholder: "feature/my-change"
|
|
300
|
-
})]
|
|
301
|
-
}), /* @__PURE__ */ jsxs("span", {
|
|
302
|
-
className: "flex flex-col gap-1",
|
|
303
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
304
|
-
className: "text-sm text-po-dim",
|
|
305
|
-
children: "from"
|
|
306
|
-
}), /* @__PURE__ */ jsxs(Select, {
|
|
307
|
-
value: form.baseRef,
|
|
308
|
-
onValueChange: (baseRef) => update({ baseRef }),
|
|
309
|
-
disabled: busy,
|
|
310
|
-
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
311
|
-
"data-testid": "git-new-session-base",
|
|
312
|
-
"aria-label": "base branch",
|
|
313
|
-
children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "base branch" })
|
|
314
|
-
}), /* @__PURE__ */ jsx(SelectContent, { children: bases.map((base) => /* @__PURE__ */ jsx(SelectItem, {
|
|
315
|
-
value: base,
|
|
316
|
-
children: base === branches?.current ? `${base} (current)` : base
|
|
317
|
-
}, base)) })]
|
|
318
|
-
})]
|
|
319
|
-
})]
|
|
320
|
-
}) : null,
|
|
321
|
-
/* @__PURE__ */ jsxs("label", {
|
|
322
|
-
htmlFor: "new-session-name",
|
|
323
|
-
className: "flex flex-col gap-1",
|
|
324
|
-
children: [/* @__PURE__ */ jsxs("span", {
|
|
325
|
-
className: "text-sm text-po-dim",
|
|
326
|
-
children: ["name ", /* @__PURE__ */ jsxs("span", {
|
|
327
|
-
className: "text-po-dim",
|
|
328
|
-
children: [
|
|
329
|
-
"(optional",
|
|
330
|
-
mode === "plain" ? "" : "; defaults to the branch",
|
|
331
|
-
")"
|
|
332
|
-
]
|
|
333
|
-
})]
|
|
334
|
-
}), /* @__PURE__ */ jsx(Input, {
|
|
335
|
-
id: "new-session-name",
|
|
336
|
-
"data-testid": "new-session-name",
|
|
337
|
-
value: form.name,
|
|
338
|
-
autoFocus: mode === "plain",
|
|
339
|
-
disabled: busy,
|
|
340
|
-
onChange: (event) => update({ name: event.target.value }),
|
|
341
|
-
onKeyDown: (event) => {
|
|
342
|
-
if (event.key === "Enter") submit();
|
|
343
|
-
},
|
|
344
|
-
placeholder: "session name"
|
|
345
|
-
})]
|
|
346
|
-
}),
|
|
347
|
-
error ? /* @__PURE__ */ jsx("pre", {
|
|
348
|
-
"data-testid": "new-session-error",
|
|
349
|
-
className: "max-h-28 overflow-y-auto rounded border border-po-edge-red bg-po-tint-red/40 px-2.5 py-2 text-sm whitespace-pre-wrap text-po-red",
|
|
350
|
-
children: error
|
|
351
|
-
}) : null,
|
|
352
|
-
/* @__PURE__ */ jsxs(DialogFooter, { children: [/* @__PURE__ */ jsx(Button, {
|
|
353
|
-
variant: "outline",
|
|
354
|
-
"data-testid": "new-session-cancel",
|
|
355
|
-
disabled: busy,
|
|
356
|
-
onClick: () => newSession.close(),
|
|
357
|
-
children: "cancel"
|
|
358
|
-
}), /* @__PURE__ */ jsx(Button, {
|
|
359
|
-
variant: "primary",
|
|
360
|
-
"data-testid": "new-session-create",
|
|
361
|
-
title: problem,
|
|
362
|
-
disabled: busy || problem !== void 0,
|
|
363
|
-
onClick: () => void submit(),
|
|
364
|
-
children: busy ? mode === "plain" ? "creating…" : "creating worktree…" : "create"
|
|
365
|
-
})] })
|
|
366
|
-
] })]
|
|
537
|
+
children: [/* @__PURE__ */ jsx(DialogTitle, {
|
|
538
|
+
className: "sr-only",
|
|
539
|
+
children: "New session"
|
|
540
|
+
}), /* @__PURE__ */ jsx(GitNewSessionDialogView, {
|
|
541
|
+
workspaceRoot: newSession.workspaceRoot,
|
|
542
|
+
branches,
|
|
543
|
+
loadError,
|
|
544
|
+
busy,
|
|
545
|
+
error,
|
|
546
|
+
riftMissing,
|
|
547
|
+
onClose: newSession.close,
|
|
548
|
+
onCreate: (state) => void submit(state),
|
|
549
|
+
onDeleteBranch: (branch) => branchAction(branch, "delete"),
|
|
550
|
+
onOpenBranchFolder: (branch) => void branchAction(branch, "open")
|
|
551
|
+
})]
|
|
367
552
|
})
|
|
368
553
|
});
|
|
369
554
|
}
|
|
@@ -446,11 +631,17 @@ function saveRequestOf(form) {
|
|
|
446
631
|
* AVOID:
|
|
447
632
|
* - Echoing a token back. The view the server sends has none to echo.
|
|
448
633
|
*/
|
|
634
|
+
function Field({ label, children }) {
|
|
635
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
636
|
+
className: "flex flex-col gap-2 text-base text-po-text",
|
|
637
|
+
children: [/* @__PURE__ */ jsx("span", { children: label }), children]
|
|
638
|
+
});
|
|
639
|
+
}
|
|
449
640
|
const METHODS = [
|
|
450
641
|
{
|
|
451
642
|
method: "none",
|
|
452
643
|
title: "This machine's git",
|
|
453
|
-
detail: "
|
|
644
|
+
detail: "Your credential helper, SSH config and agent, as they are."
|
|
454
645
|
},
|
|
455
646
|
{
|
|
456
647
|
method: "ssh",
|
|
@@ -465,10 +656,14 @@ const METHODS = [
|
|
|
465
656
|
];
|
|
466
657
|
function GitAuthForm({ saved, saving, saveError, savedNotice = false, onSave }) {
|
|
467
658
|
const [form, setForm] = useState(() => formFromView(saved));
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
659
|
+
const [edited, setEdited] = useState(false);
|
|
660
|
+
const update = (patch) => {
|
|
661
|
+
setEdited(true);
|
|
662
|
+
setForm((current) => ({
|
|
663
|
+
...current,
|
|
664
|
+
...patch
|
|
665
|
+
}));
|
|
666
|
+
};
|
|
472
667
|
const problem = formProblem(form, saved);
|
|
473
668
|
const tokenSaved = saved.https?.hasToken === true && saved.https.host === form.host.trim().toLowerCase();
|
|
474
669
|
const host = form.host.trim() === "" ? "this host" : form.host.trim();
|
|
@@ -476,133 +671,156 @@ function GitAuthForm({ saved, saving, saveError, savedNotice = false, onSave })
|
|
|
476
671
|
"data-testid": "git-auth-form",
|
|
477
672
|
children: [
|
|
478
673
|
/* @__PURE__ */ jsxs(SettingsSectionHeader, { children: [/* @__PURE__ */ jsx(SettingsSectionTitle, { children: "Connection" }), /* @__PURE__ */ jsx(SettingsSectionDescription, { children: "Worktree sessions use the setup of the workspace they belong to." })] }),
|
|
479
|
-
/* @__PURE__ */ jsx(
|
|
674
|
+
/* @__PURE__ */ jsx("div", {
|
|
480
675
|
"aria-label": "How git reaches your remote",
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
className:
|
|
492
|
-
children:
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
676
|
+
className: "flex flex-col gap-3",
|
|
677
|
+
children: METHODS.map((choice) => /* @__PURE__ */ jsxs("div", {
|
|
678
|
+
className: `overflow-hidden rounded-lg border ${form.method === choice.method ? "border-po-green" : "border-po-border-soft"}`,
|
|
679
|
+
children: [
|
|
680
|
+
/* @__PURE__ */ jsxs("button", {
|
|
681
|
+
type: "button",
|
|
682
|
+
"aria-pressed": form.method === choice.method,
|
|
683
|
+
disabled: saving,
|
|
684
|
+
"data-testid": `git-auth-method-${choice.method}`,
|
|
685
|
+
onClick: () => update({ method: choice.method }),
|
|
686
|
+
className: `flex w-full items-start gap-3 p-4 text-left ${form.method === choice.method ? "bg-po-tint-green" : "bg-po-bg"}`,
|
|
687
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
688
|
+
className: "flex flex-1 flex-col gap-1",
|
|
689
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
690
|
+
className: "text-base font-semibold text-po-hi",
|
|
691
|
+
children: choice.title
|
|
692
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
693
|
+
className: "text-sm leading-relaxed text-po-dim",
|
|
694
|
+
children: choice.detail
|
|
695
|
+
})]
|
|
696
|
+
}), form.method === choice.method ? /* @__PURE__ */ jsx(CheckIcon, { className: "mt-0.5 size-4 shrink-0 text-po-green" }) : null]
|
|
697
|
+
}),
|
|
698
|
+
form.method === choice.method && form.method === "ssh" ? /* @__PURE__ */ jsxs("div", {
|
|
699
|
+
className: "flex flex-col gap-3 border-t border-po-border-soft p-4",
|
|
700
|
+
children: [/* @__PURE__ */ jsx(Field, {
|
|
701
|
+
label: "Key path",
|
|
702
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
703
|
+
"data-testid": "git-auth-key-path",
|
|
704
|
+
value: form.keyPath,
|
|
705
|
+
placeholder: "~/.ssh/id_ed25519",
|
|
706
|
+
spellCheck: false,
|
|
707
|
+
disabled: saving,
|
|
708
|
+
onChange: (event) => update({ keyPath: event.target.value }),
|
|
709
|
+
className: "font-mono"
|
|
710
|
+
})
|
|
711
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
712
|
+
className: "text-sm leading-relaxed text-po-dim",
|
|
713
|
+
children: "Leave blank to use SSH config and ssh-agent. Passphrases need ssh-agent. Trust an unknown host in a terminal first."
|
|
714
|
+
})]
|
|
715
|
+
}) : null,
|
|
716
|
+
form.method === choice.method && form.method === "https" ? /* @__PURE__ */ jsxs("div", {
|
|
717
|
+
className: "flex flex-col gap-4 border-t border-po-border-soft p-4",
|
|
718
|
+
children: [
|
|
719
|
+
/* @__PURE__ */ jsxs("div", {
|
|
720
|
+
className: "grid grid-cols-2 gap-3",
|
|
721
|
+
children: [/* @__PURE__ */ jsx(Field, {
|
|
722
|
+
label: "Host",
|
|
723
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
724
|
+
"data-testid": "git-auth-host",
|
|
725
|
+
value: form.host,
|
|
726
|
+
spellCheck: false,
|
|
727
|
+
disabled: saving,
|
|
728
|
+
onChange: (event) => update({ host: event.target.value }),
|
|
729
|
+
className: "font-mono"
|
|
730
|
+
})
|
|
731
|
+
}), /* @__PURE__ */ jsx(Field, {
|
|
732
|
+
label: "Username",
|
|
733
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
734
|
+
"data-testid": "git-auth-username",
|
|
735
|
+
value: form.username,
|
|
736
|
+
autoComplete: "username",
|
|
737
|
+
spellCheck: false,
|
|
738
|
+
disabled: saving,
|
|
739
|
+
onChange: (event) => update({ username: event.target.value })
|
|
740
|
+
})
|
|
741
|
+
})]
|
|
742
|
+
}),
|
|
743
|
+
/* @__PURE__ */ jsx(Field, {
|
|
744
|
+
label: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
745
|
+
"Token",
|
|
746
|
+
" ",
|
|
747
|
+
tokenSaved ? /* @__PURE__ */ jsx("span", {
|
|
748
|
+
"data-testid": "git-auth-token-saved",
|
|
749
|
+
className: "ml-2 text-sm text-po-green",
|
|
750
|
+
children: "A token is saved"
|
|
751
|
+
}) : null
|
|
752
|
+
] }),
|
|
753
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
754
|
+
"data-testid": "git-auth-token",
|
|
755
|
+
type: "password",
|
|
756
|
+
value: form.token,
|
|
757
|
+
autoComplete: "new-password",
|
|
758
|
+
placeholder: tokenSaved ? "Leave blank to keep it" : "Personal access token",
|
|
759
|
+
disabled: saving,
|
|
760
|
+
onChange: (event) => update({ token: event.target.value })
|
|
761
|
+
})
|
|
762
|
+
}),
|
|
763
|
+
/* @__PURE__ */ jsxs("p", {
|
|
764
|
+
className: "text-sm text-po-dim",
|
|
765
|
+
children: [
|
|
766
|
+
"Sent only to ",
|
|
767
|
+
/* @__PURE__ */ jsx("span", {
|
|
768
|
+
className: "font-mono",
|
|
769
|
+
children: host
|
|
770
|
+
}),
|
|
771
|
+
". Other hosts use this machine's git."
|
|
772
|
+
]
|
|
773
|
+
})
|
|
774
|
+
]
|
|
775
|
+
}) : null
|
|
776
|
+
]
|
|
498
777
|
}, choice.method))
|
|
499
778
|
}),
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
"
|
|
505
|
-
|
|
779
|
+
/* @__PURE__ */ jsxs(SettingsNote, {
|
|
780
|
+
className: "flex items-start gap-2 leading-relaxed",
|
|
781
|
+
children: [/* @__PURE__ */ jsx(ShieldIcon, {
|
|
782
|
+
"aria-hidden": true,
|
|
783
|
+
className: "mt-0.5 size-4 shrink-0"
|
|
784
|
+
}), /* @__PURE__ */ jsxs("span", { children: [
|
|
785
|
+
"Only cockpit pull, push and rebase use these credentials, not the agent.",
|
|
786
|
+
/* @__PURE__ */ jsx("br", {}),
|
|
787
|
+
"Stored in ",
|
|
788
|
+
/* @__PURE__ */ jsx("span", {
|
|
506
789
|
className: "font-mono",
|
|
507
|
-
children: "
|
|
790
|
+
children: "~/.po/git/credentials.json"
|
|
508
791
|
}),
|
|
509
|
-
"
|
|
510
|
-
] })
|
|
511
|
-
|
|
512
|
-
id: "git-auth-key-path",
|
|
513
|
-
"data-testid": "git-auth-key-path",
|
|
514
|
-
value: form.keyPath,
|
|
515
|
-
placeholder: "~/.ssh/id_ed25519",
|
|
516
|
-
spellCheck: false,
|
|
517
|
-
onChange: (event) => update({ keyPath: event.target.value }),
|
|
518
|
-
className: "font-mono text-sm"
|
|
519
|
-
})
|
|
520
|
-
}) }) : null,
|
|
521
|
-
form.method === "https" ? /* @__PURE__ */ jsxs(SettingsCard, { children: [
|
|
522
|
-
/* @__PURE__ */ jsx(SettingsField, {
|
|
523
|
-
id: "git-auth-host",
|
|
524
|
-
label: "Host",
|
|
525
|
-
description: "The one host this token is sent to.",
|
|
526
|
-
children: /* @__PURE__ */ jsx(Input, {
|
|
527
|
-
id: "git-auth-host",
|
|
528
|
-
"data-testid": "git-auth-host",
|
|
529
|
-
value: form.host,
|
|
530
|
-
spellCheck: false,
|
|
531
|
-
onChange: (event) => update({ host: event.target.value }),
|
|
532
|
-
className: "font-mono text-sm"
|
|
533
|
-
})
|
|
534
|
-
}),
|
|
535
|
-
/* @__PURE__ */ jsx(SettingsField, {
|
|
536
|
-
id: "git-auth-username",
|
|
537
|
-
label: "Username",
|
|
538
|
-
children: /* @__PURE__ */ jsx(Input, {
|
|
539
|
-
id: "git-auth-username",
|
|
540
|
-
"data-testid": "git-auth-username",
|
|
541
|
-
value: form.username,
|
|
542
|
-
autoComplete: "username",
|
|
543
|
-
spellCheck: false,
|
|
544
|
-
onChange: (event) => update({ username: event.target.value }),
|
|
545
|
-
className: "text-base"
|
|
546
|
-
})
|
|
547
|
-
}),
|
|
548
|
-
/* @__PURE__ */ jsx(SettingsField, {
|
|
549
|
-
id: "git-auth-token",
|
|
550
|
-
label: "Token",
|
|
551
|
-
description: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
552
|
-
tokenSaved ? /* @__PURE__ */ jsxs("span", {
|
|
553
|
-
"data-testid": "git-auth-token-saved",
|
|
554
|
-
className: "text-po-green",
|
|
555
|
-
children: ["A token is saved.", " "]
|
|
556
|
-
}) : null,
|
|
557
|
-
"Sent only to ",
|
|
558
|
-
host,
|
|
559
|
-
". A remote on any other host keeps using this machine's own git credentials."
|
|
560
|
-
] }),
|
|
561
|
-
children: /* @__PURE__ */ jsx(Input, {
|
|
562
|
-
id: "git-auth-token",
|
|
563
|
-
"data-testid": "git-auth-token",
|
|
564
|
-
type: "password",
|
|
565
|
-
value: form.token,
|
|
566
|
-
autoComplete: "new-password",
|
|
567
|
-
placeholder: tokenSaved ? "Leave blank to keep it" : "Personal access token",
|
|
568
|
-
onChange: (event) => update({ token: event.target.value }),
|
|
569
|
-
className: "text-base"
|
|
570
|
-
})
|
|
571
|
-
})
|
|
572
|
-
] }) : null,
|
|
792
|
+
", readable only by you."
|
|
793
|
+
] })]
|
|
794
|
+
}),
|
|
573
795
|
/* @__PURE__ */ jsxs(SettingsActions, {
|
|
574
796
|
className: "gap-4",
|
|
575
797
|
children: [/* @__PURE__ */ jsx(Button, {
|
|
576
798
|
variant: "primary",
|
|
577
|
-
size: "sm",
|
|
578
799
|
"data-testid": "git-auth-save",
|
|
579
800
|
disabled: problem !== void 0 || saving,
|
|
580
801
|
loading: saving,
|
|
581
|
-
loadingLabel: "Saving",
|
|
582
|
-
onClick: () =>
|
|
802
|
+
loadingLabel: "Saving...",
|
|
803
|
+
onClick: () => {
|
|
804
|
+
if (!saving && problem === void 0) onSave(saveRequestOf(form));
|
|
805
|
+
},
|
|
583
806
|
children: "Save connection"
|
|
584
|
-
}), problem
|
|
585
|
-
"data-testid": "git-auth-problem",
|
|
586
|
-
className: "text-po-dim",
|
|
587
|
-
children: problem
|
|
588
|
-
}) : saveError !== void 0 ? /* @__PURE__ */ jsx(SettingsNote, {
|
|
589
|
-
role: "alert",
|
|
590
|
-
"data-testid": "git-auth-error",
|
|
591
|
-
className: "text-po-red",
|
|
592
|
-
children: saveError
|
|
593
|
-
}) : savedNotice ? /* @__PURE__ */ jsx(SettingsNote, {
|
|
807
|
+
}), savedNotice && !edited && problem === void 0 && saveError === void 0 ? /* @__PURE__ */ jsx("span", {
|
|
594
808
|
"data-testid": "git-auth-saved",
|
|
595
|
-
className: "text-po-green",
|
|
809
|
+
className: "text-sm text-po-green",
|
|
596
810
|
children: "Saved."
|
|
597
811
|
}) : null]
|
|
598
812
|
}),
|
|
599
|
-
/* @__PURE__ */
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
813
|
+
problem !== void 0 ? /* @__PURE__ */ jsx("p", {
|
|
814
|
+
"data-testid": "git-auth-problem",
|
|
815
|
+
className: "text-sm text-po-red",
|
|
816
|
+
children: problem
|
|
817
|
+
}) : null,
|
|
818
|
+
saveError !== void 0 ? /* @__PURE__ */ jsx("p", {
|
|
819
|
+
role: "alert",
|
|
820
|
+
"data-testid": "git-auth-error",
|
|
821
|
+
className: "text-sm text-po-red",
|
|
822
|
+
children: saveError
|
|
823
|
+
}) : null
|
|
606
824
|
]
|
|
607
825
|
});
|
|
608
826
|
}
|
|
@@ -845,7 +1063,10 @@ function optionalText$1(value) {
|
|
|
845
1063
|
}
|
|
846
1064
|
function optionalErrorTarget$1(value) {
|
|
847
1065
|
if (!isRecord$2(value)) return void 0;
|
|
848
|
-
if (value.action === "create") return
|
|
1066
|
+
if (value.action === "create") return value.riftMissing === true ? {
|
|
1067
|
+
action: "create",
|
|
1068
|
+
riftMissing: true
|
|
1069
|
+
} : { action: "create" };
|
|
849
1070
|
if (value.action === "close" && typeof value.id === "string" && value.id !== "") return {
|
|
850
1071
|
action: "close",
|
|
851
1072
|
id: value.id
|
|
@@ -893,200 +1114,404 @@ function requestWorktreeClose(sessionId, id) {
|
|
|
893
1114
|
});
|
|
894
1115
|
}
|
|
895
1116
|
//#endregion
|
|
896
|
-
//#region src/extensions/workspaces/sessions/(frontend)/_components/
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1117
|
+
//#region src/extensions/workspaces/sessions/(frontend)/fill/_components/WorktreeCreateDialog.tsx
|
|
1118
|
+
function WorktreeSessionMenu({ sessionId, sessionReservationId }) {
|
|
1119
|
+
return /* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
1120
|
+
disabled: sessionId === null,
|
|
1121
|
+
"data-testid": `session-worktree-${sessionReservationId ?? sessionId}`,
|
|
1122
|
+
onSelect: () => {
|
|
1123
|
+
if (sessionId !== null) worktreeCreateDialog.update(() => ({
|
|
1124
|
+
sessionId,
|
|
1125
|
+
...sessionReservationId === void 0 ? {} : { reservationId: sessionReservationId }
|
|
1126
|
+
}));
|
|
1127
|
+
},
|
|
1128
|
+
children: "Create Worktree Session..."
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
function WorktreeCreateOverlay(props) {
|
|
1132
|
+
const target = useStore(worktreeCreateDialog.store);
|
|
1133
|
+
const { holdSessionChannels } = props;
|
|
1134
|
+
useEffect(() => {
|
|
1135
|
+
if (target === null) return;
|
|
1136
|
+
return holdSessionChannels?.(target.sessionId, (frame) => {
|
|
1137
|
+
if (frame.type !== worktreesChannel.channel) return;
|
|
1138
|
+
const payload = worktreesChannel.parse(frame.payload);
|
|
1139
|
+
if (payload !== null) worktreesChannel.apply(target.sessionId, payload);
|
|
1140
|
+
});
|
|
1141
|
+
}, [target, holdSessionChannels]);
|
|
1142
|
+
if (target === null) return null;
|
|
1143
|
+
return /* @__PURE__ */ jsx(Dialog, {
|
|
1144
|
+
open: true,
|
|
1145
|
+
onOpenChange: (open) => {
|
|
1146
|
+
if (!open) worktreeCreateDialog.update(() => null);
|
|
1147
|
+
},
|
|
1148
|
+
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
1149
|
+
width: "md",
|
|
1150
|
+
className: "w-96 overflow-y-auto border-0",
|
|
1151
|
+
"data-testid": "session-worktree-dialog",
|
|
1152
|
+
"aria-describedby": void 0,
|
|
1153
|
+
children: [/* @__PURE__ */ jsx(DialogTitle, {
|
|
1154
|
+
className: "sr-only",
|
|
1155
|
+
children: "Create a worktree session"
|
|
1156
|
+
}), /* @__PURE__ */ createElement(WorktreeCreateForm, {
|
|
1157
|
+
...props,
|
|
1158
|
+
key: `${target.sessionId}:${target.reservationId ?? ""}`,
|
|
1159
|
+
sessionId: target.sessionId,
|
|
1160
|
+
sessionReservationId: target.reservationId,
|
|
1161
|
+
onCancel: () => worktreeCreateDialog.update(() => null),
|
|
1162
|
+
onCreated: () => worktreeCreateDialog.update(() => null)
|
|
1163
|
+
})]
|
|
1164
|
+
})
|
|
1165
|
+
});
|
|
928
1166
|
}
|
|
929
|
-
function
|
|
1167
|
+
function WorktreeCreateForm({ sessionId, sessionReservationId, onCancel, onRequested, onCreated }) {
|
|
930
1168
|
const session = useStore(worktreeActivity.store, (state) => worktreeActivity.select(state, sessionId));
|
|
931
1169
|
const [branch, setBranch] = useState("");
|
|
932
1170
|
const [baseRef, setBaseRef] = useState("");
|
|
933
|
-
const
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
requestWorktreeCreate(sessionId, branch, baseRef, sessionReservationId);
|
|
941
|
-
if (sessionReservationId === void 0) {
|
|
942
|
-
setBranch("");
|
|
943
|
-
setBaseRef("");
|
|
1171
|
+
const sent = useRef(void 0);
|
|
1172
|
+
useEffect(() => {
|
|
1173
|
+
const request = sent.current;
|
|
1174
|
+
if (request === void 0) return;
|
|
1175
|
+
if (session.pending !== void 0) {
|
|
1176
|
+
request.sawPending = true;
|
|
1177
|
+
return;
|
|
944
1178
|
}
|
|
945
|
-
|
|
1179
|
+
if (!session.worktrees.some((worktree) => worktree.branch === request.branch) && !request.sawPending) return;
|
|
1180
|
+
sent.current = void 0;
|
|
1181
|
+
if (session.errorTarget?.action !== "create") onCreated?.();
|
|
1182
|
+
}, [
|
|
1183
|
+
session.pending,
|
|
1184
|
+
session.worktrees,
|
|
1185
|
+
session.errorTarget,
|
|
1186
|
+
onCreated
|
|
1187
|
+
]);
|
|
1188
|
+
return /* @__PURE__ */ jsx(WorktreeCreateView, {
|
|
1189
|
+
branch,
|
|
1190
|
+
baseRef,
|
|
1191
|
+
onBranchChange: setBranch,
|
|
1192
|
+
onBaseChange: setBaseRef,
|
|
1193
|
+
busy: session.pending !== void 0,
|
|
1194
|
+
error: session.errorTarget?.action === "create" ? session.error : void 0,
|
|
1195
|
+
riftMissing: session.errorTarget?.action === "create" && session.errorTarget.riftMissing === true,
|
|
1196
|
+
onCancel,
|
|
1197
|
+
onCreate: () => {
|
|
1198
|
+
if (sessionId !== null) {
|
|
1199
|
+
sent.current = {
|
|
1200
|
+
branch: branch.trim(),
|
|
1201
|
+
sawPending: false
|
|
1202
|
+
};
|
|
1203
|
+
onRequested?.(branch.trim());
|
|
1204
|
+
requestWorktreeCreate(sessionId, branch, baseRef, sessionReservationId);
|
|
1205
|
+
}
|
|
1206
|
+
},
|
|
1207
|
+
disabled: sessionId === null
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
function WorktreeCreateView({ branch, baseRef, onBranchChange, onBaseChange, busy = false, error, riftMissing = false, onCancel, onCreate, disabled = false }) {
|
|
1211
|
+
const id = useId();
|
|
946
1212
|
return /* @__PURE__ */ jsxs("div", {
|
|
947
|
-
|
|
948
|
-
className: "flex min-h-0 flex-1 flex-col overflow-auto",
|
|
1213
|
+
className: "rounded-lg border border-po-border-soft bg-po-panel p-5 text-po-text shadow-sm",
|
|
949
1214
|
children: [
|
|
950
|
-
/* @__PURE__ */
|
|
951
|
-
|
|
1215
|
+
/* @__PURE__ */ jsx("h2", {
|
|
1216
|
+
id: `${id}-title`,
|
|
1217
|
+
className: "text-xl font-semibold text-po-hi",
|
|
1218
|
+
children: "Create a worktree session"
|
|
1219
|
+
}),
|
|
1220
|
+
/* @__PURE__ */ jsx("p", {
|
|
1221
|
+
className: "mt-2 text-sm leading-relaxed text-po-dim",
|
|
1222
|
+
children: "Create a separate checkout and session without moving your existing sessions."
|
|
1223
|
+
}),
|
|
1224
|
+
/* @__PURE__ */ jsxs("form", {
|
|
1225
|
+
onSubmit: (event) => {
|
|
1226
|
+
event.preventDefault();
|
|
1227
|
+
if (!busy && !disabled && branch.trim()) onCreate?.();
|
|
1228
|
+
},
|
|
1229
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1230
|
+
className: "mt-5 flex flex-col gap-4",
|
|
1231
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1232
|
+
className: "flex flex-col gap-2",
|
|
1233
|
+
children: [
|
|
1234
|
+
/* @__PURE__ */ jsx("label", {
|
|
1235
|
+
htmlFor: `${id}-branch`,
|
|
1236
|
+
className: "text-base font-medium",
|
|
1237
|
+
children: "Branch"
|
|
1238
|
+
}),
|
|
1239
|
+
/* @__PURE__ */ jsx(Input, {
|
|
1240
|
+
id: `${id}-branch`,
|
|
1241
|
+
"data-testid": "git-worktree-branch",
|
|
1242
|
+
className: "font-mono",
|
|
1243
|
+
placeholder: "wt/fix-auth",
|
|
1244
|
+
value: branch,
|
|
1245
|
+
onChange: (e) => onBranchChange?.(e.target.value),
|
|
1246
|
+
disabled: busy,
|
|
1247
|
+
"aria-invalid": !!error,
|
|
1248
|
+
"aria-describedby": error ? `${id}-error` : void 0
|
|
1249
|
+
}),
|
|
1250
|
+
riftMissing ? /* @__PURE__ */ jsxs("div", {
|
|
1251
|
+
"data-testid": "rift-install-notice",
|
|
1252
|
+
role: "alert",
|
|
1253
|
+
className: "flex flex-col gap-2 rounded-md border border-po-edge-magenta bg-po-tint-magenta px-3 py-3 text-sm text-po-hi",
|
|
1254
|
+
children: [/* @__PURE__ */ jsx("p", { children: "This repository uses Rift, and its CLI is not installed here. Run these in the repository root, then try again." }), /* @__PURE__ */ jsx(CodeBlock, {
|
|
1255
|
+
text: `${RIFT_INSTALL_COMMAND}
|
|
1256
|
+
rift init`,
|
|
1257
|
+
className: "language-bash"
|
|
1258
|
+
})]
|
|
1259
|
+
}) : error && /* @__PURE__ */ jsx("p", {
|
|
1260
|
+
id: `${id}-error`,
|
|
1261
|
+
role: "alert",
|
|
1262
|
+
"data-testid": "git-worktree-error",
|
|
1263
|
+
className: "text-sm text-po-red",
|
|
1264
|
+
children: error
|
|
1265
|
+
})
|
|
1266
|
+
]
|
|
1267
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1268
|
+
className: "flex flex-col gap-2",
|
|
1269
|
+
children: [/* @__PURE__ */ jsx("label", {
|
|
1270
|
+
htmlFor: `${id}-base`,
|
|
1271
|
+
className: "text-base font-medium",
|
|
1272
|
+
children: "Start from"
|
|
1273
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
1274
|
+
size: "sm",
|
|
1275
|
+
id: `${id}-base`,
|
|
1276
|
+
"data-testid": "git-worktree-base",
|
|
1277
|
+
className: "font-mono",
|
|
1278
|
+
placeholder: "Current branch",
|
|
1279
|
+
value: baseRef,
|
|
1280
|
+
onChange: (e) => onBaseChange?.(e.target.value),
|
|
1281
|
+
disabled: busy
|
|
1282
|
+
})]
|
|
1283
|
+
})]
|
|
1284
|
+
}), /* @__PURE__ */ jsxs("footer", {
|
|
1285
|
+
className: "mt-5 flex items-center justify-end gap-2 border-t border-po-border-soft pt-4",
|
|
1286
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
1287
|
+
type: "button",
|
|
1288
|
+
variant: "ghost",
|
|
1289
|
+
disabled: busy,
|
|
1290
|
+
onClick: onCancel,
|
|
1291
|
+
children: "Cancel"
|
|
1292
|
+
}), /* @__PURE__ */ jsxs(Button, {
|
|
1293
|
+
type: "submit",
|
|
1294
|
+
variant: "primary",
|
|
1295
|
+
"data-testid": "git-worktree-create",
|
|
1296
|
+
disabled: busy || disabled || !branch.trim(),
|
|
1297
|
+
children: [busy && /* @__PURE__ */ jsx(Spinner, {
|
|
1298
|
+
className: "size-3",
|
|
1299
|
+
label: "Creating worktree"
|
|
1300
|
+
}), busy ? "Creating worktree..." : riftMissing ? "Try again" : "Create worktree"]
|
|
1301
|
+
})]
|
|
1302
|
+
})]
|
|
1303
|
+
})
|
|
1304
|
+
]
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
//#endregion
|
|
1308
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/WorktreesPanel.tsx
|
|
1309
|
+
function worktreesTab() {
|
|
1310
|
+
return {
|
|
1311
|
+
id: "git-worktrees",
|
|
1312
|
+
label: "worktrees",
|
|
1313
|
+
panel: WorktreesPanel
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
function WorktreesPanel(props) {
|
|
1317
|
+
const session = useStore(worktreeActivity.store, (state) => worktreeActivity.select(state, props.sessionId));
|
|
1318
|
+
const [creating, setCreating] = useState(session.errorTarget?.action === "create");
|
|
1319
|
+
const [requestedBranch, setRequestedBranch] = useState();
|
|
1320
|
+
const sawPending = useRef(false);
|
|
1321
|
+
useEffect(() => {
|
|
1322
|
+
if (!requestedBranch) return;
|
|
1323
|
+
if (session.worktrees.some((w) => w.branch === requestedBranch) || sawPending.current && session.pending === void 0) setRequestedBranch(void 0);
|
|
1324
|
+
else if (session.pending !== void 0) sawPending.current = true;
|
|
1325
|
+
}, [
|
|
1326
|
+
requestedBranch,
|
|
1327
|
+
session.pending,
|
|
1328
|
+
session.worktrees
|
|
1329
|
+
]);
|
|
1330
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1331
|
+
className: "min-h-0 flex-1 overflow-auto",
|
|
1332
|
+
children: /* @__PURE__ */ jsx(WorktreesPanelView, {
|
|
1333
|
+
worktrees: session.worktrees,
|
|
1334
|
+
pending: session.pending,
|
|
1335
|
+
startingBranch: session.pending === void 0 || session.worktrees.some((w) => w.branch === requestedBranch) ? void 0 : requestedBranch,
|
|
1336
|
+
error: session.errorTarget?.action === "create" && creating ? void 0 : session.error,
|
|
1337
|
+
closeErrorId: session.errorTarget?.action === "close" ? session.errorTarget.id : void 0,
|
|
1338
|
+
onNew: () => setCreating(true),
|
|
1339
|
+
onClose: props.sessionId === null ? void 0 : (id) => requestWorktreeClose(props.sessionId, id),
|
|
1340
|
+
form: creating ? /* @__PURE__ */ jsx(WorktreeCreateForm, {
|
|
1341
|
+
...props,
|
|
1342
|
+
onCancel: () => setCreating(false),
|
|
1343
|
+
onCreated: () => setCreating(false),
|
|
1344
|
+
onRequested: (branch) => {
|
|
1345
|
+
sawPending.current = false;
|
|
1346
|
+
setRequestedBranch(branch);
|
|
1347
|
+
}
|
|
1348
|
+
}) : void 0
|
|
1349
|
+
})
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
function WorktreesPanelView({ worktrees, pending, error, closeErrorId, onNew, onClose, form, startingIds = [], startingBranch }) {
|
|
1353
|
+
const groups = [worktrees.filter((w) => !w.orphaned && !w.unowned), worktrees.filter((w) => w.orphaned || w.unowned)];
|
|
1354
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1355
|
+
"data-testid": "git-worktrees-panel",
|
|
1356
|
+
className: "rounded-lg border border-po-border-soft bg-po-panel p-5",
|
|
1357
|
+
children: [
|
|
1358
|
+
/* @__PURE__ */ jsxs("header", {
|
|
1359
|
+
className: "flex items-center justify-between gap-4 border-b border-po-border-soft pb-4",
|
|
952
1360
|
children: [
|
|
953
|
-
/* @__PURE__ */ jsx("
|
|
1361
|
+
/* @__PURE__ */ jsx("h2", {
|
|
954
1362
|
className: "text-lg font-semibold text-po-hi",
|
|
955
|
-
children: "
|
|
1363
|
+
children: "Worktrees"
|
|
956
1364
|
}),
|
|
957
1365
|
/* @__PURE__ */ jsx("span", {
|
|
958
1366
|
"data-testid": "git-worktrees-count",
|
|
959
|
-
className: "
|
|
960
|
-
children:
|
|
1367
|
+
className: "sr-only",
|
|
1368
|
+
children: worktrees.length === 0 ? "none yet" : `${worktrees.length} open`
|
|
961
1369
|
}),
|
|
962
|
-
/* @__PURE__ */ jsx(
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
children:
|
|
967
|
-
className: "h-3 w-3 shrink-0 text-po-faint",
|
|
968
|
-
label: session.pending
|
|
969
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
970
|
-
className: "min-w-0 truncate text-sm text-po-dim",
|
|
971
|
-
children: session.pending
|
|
972
|
-
})]
|
|
1370
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1371
|
+
variant: "outline",
|
|
1372
|
+
size: "sm",
|
|
1373
|
+
onClick: onNew,
|
|
1374
|
+
children: "New worktree"
|
|
973
1375
|
})
|
|
974
1376
|
]
|
|
975
1377
|
}),
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
placeholder: "wt/fix-auth",
|
|
987
|
-
value: branch,
|
|
988
|
-
onChange: (event) => setBranch(event.target.value),
|
|
989
|
-
onKeyDown: (event) => {
|
|
990
|
-
if (event.key === "Enter") create();
|
|
991
|
-
}
|
|
992
|
-
}),
|
|
993
|
-
/* @__PURE__ */ jsx(Input, {
|
|
994
|
-
"data-testid": "git-worktree-base",
|
|
995
|
-
"aria-label": "Base ref",
|
|
996
|
-
size: "sm",
|
|
997
|
-
placeholder: "base (current)",
|
|
998
|
-
value: baseRef,
|
|
999
|
-
onChange: (event) => setBaseRef(event.target.value),
|
|
1000
|
-
className: "w-32 shrink-0"
|
|
1001
|
-
}),
|
|
1002
|
-
/* @__PURE__ */ jsx(Button, {
|
|
1003
|
-
"data-testid": "git-worktree-create",
|
|
1004
|
-
size: "sm",
|
|
1005
|
-
disabled: !canCreate,
|
|
1006
|
-
onClick: create,
|
|
1007
|
-
children: "Create"
|
|
1008
|
-
})
|
|
1009
|
-
]
|
|
1010
|
-
}), createError === void 0 ? /* @__PURE__ */ jsx("p", {
|
|
1011
|
-
className: "text-sm text-po-dim",
|
|
1012
|
-
children: "Creates the worktree outside the repository and starts a session nested under this one."
|
|
1013
|
-
}) : /* @__PURE__ */ jsx("p", {
|
|
1014
|
-
role: "alert",
|
|
1015
|
-
"data-testid": "git-worktree-error",
|
|
1016
|
-
className: "text-sm text-po-red",
|
|
1017
|
-
children: createError
|
|
1378
|
+
form,
|
|
1379
|
+
pending && !startingBranch && /* @__PURE__ */ jsxs("p", {
|
|
1380
|
+
"data-testid": "git-worktree-pending",
|
|
1381
|
+
className: "flex items-center gap-2 pt-3 text-sm text-po-dim",
|
|
1382
|
+
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
1383
|
+
className: "size-3",
|
|
1384
|
+
label: pending
|
|
1385
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1386
|
+
className: "inline-block first-letter:uppercase",
|
|
1387
|
+
children: pending
|
|
1018
1388
|
})]
|
|
1019
1389
|
}),
|
|
1020
|
-
|
|
1390
|
+
error && (!closeErrorId || !worktrees.some((w) => w.id === closeErrorId)) && /* @__PURE__ */ jsx("p", {
|
|
1021
1391
|
role: "alert",
|
|
1022
1392
|
"data-testid": "git-worktree-error",
|
|
1023
|
-
className: "
|
|
1024
|
-
children:
|
|
1025
|
-
})
|
|
1026
|
-
|
|
1393
|
+
className: "text-sm text-po-red",
|
|
1394
|
+
children: error
|
|
1395
|
+
}),
|
|
1396
|
+
worktrees.length === 0 && !startingBranch ? /* @__PURE__ */ jsx(EmptyState, {
|
|
1027
1397
|
"data-testid": "git-worktrees-empty",
|
|
1028
|
-
className: "
|
|
1029
|
-
title: "
|
|
1030
|
-
description: "
|
|
1031
|
-
}) : /* @__PURE__ */ jsx("
|
|
1398
|
+
className: "py-10",
|
|
1399
|
+
title: "No worktrees yet",
|
|
1400
|
+
description: "A worktree gives a branch its own checkout and a session nested under this one."
|
|
1401
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
1032
1402
|
"data-testid": "git-worktrees-list",
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1403
|
+
children: groups.map((group, i) => (group.length > 0 || i === 0 && startingBranch) && /* @__PURE__ */ jsxs("section", { children: [
|
|
1404
|
+
/* @__PURE__ */ jsxs("h3", {
|
|
1405
|
+
className: "flex items-center gap-2 pt-5 text-lg font-semibold text-po-hi",
|
|
1406
|
+
children: [
|
|
1407
|
+
i === 0 ? "Working" : "Needs a look",
|
|
1408
|
+
" ",
|
|
1409
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1410
|
+
className: "text-sm font-normal text-po-dim",
|
|
1411
|
+
children: [group.length + (i === 0 && startingBranch ? 1 : 0), " open"]
|
|
1412
|
+
})
|
|
1413
|
+
]
|
|
1414
|
+
}),
|
|
1415
|
+
i === 1 && /* @__PURE__ */ jsxs("p", {
|
|
1416
|
+
className: "pt-2 text-sm leading-relaxed text-po-dim",
|
|
1417
|
+
children: [
|
|
1418
|
+
group.length === 2 ? "Two worktrees" : group.length === 1 ? "One worktree" : `${group.length} worktrees`,
|
|
1419
|
+
" ",
|
|
1420
|
+
"would like your eye, whenever you are ready."
|
|
1421
|
+
]
|
|
1422
|
+
}),
|
|
1423
|
+
i === 0 && startingBranch && /* @__PURE__ */ jsxs("div", {
|
|
1424
|
+
"data-testid": "git-worktree-starting",
|
|
1425
|
+
className: "flex min-w-0 flex-col gap-2 border-b border-po-border-soft py-4",
|
|
1426
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1427
|
+
className: "flex min-w-0 items-center gap-2",
|
|
1428
|
+
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
1429
|
+
className: "size-3 shrink-0 text-po-dim",
|
|
1430
|
+
label: "Starting session"
|
|
1431
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1432
|
+
className: "min-w-0 flex-1 truncate font-mono text-base font-medium text-po-hi",
|
|
1433
|
+
children: startingBranch
|
|
1434
|
+
})]
|
|
1435
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
1436
|
+
className: "pl-4 text-sm text-po-dim",
|
|
1437
|
+
children: "Starting session..."
|
|
1438
|
+
})]
|
|
1439
|
+
}),
|
|
1440
|
+
group.map((w) => /* @__PURE__ */ jsx(WorktreeRow, {
|
|
1441
|
+
worktree: w,
|
|
1442
|
+
starting: startingIds.includes(w.id) || !!pending?.includes("starting") && w.sessionId === null && !w.orphaned && !w.unowned,
|
|
1443
|
+
disabled: !!pending || !onClose,
|
|
1444
|
+
onClose: () => onClose?.(w.id),
|
|
1445
|
+
error: closeErrorId === w.id ? error : void 0
|
|
1446
|
+
}, w.id))
|
|
1447
|
+
] }, i))
|
|
1040
1448
|
})
|
|
1041
1449
|
]
|
|
1042
1450
|
});
|
|
1043
1451
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1452
|
+
function WorktreeBadge({ worktree }) {
|
|
1453
|
+
if (worktree.orphaned) return /* @__PURE__ */ jsx(StatusBadge, {
|
|
1454
|
+
tone: "neutral",
|
|
1455
|
+
size: "xs",
|
|
1456
|
+
className: "bg-po-tint-yellow text-po-yellow",
|
|
1457
|
+
children: "Orphaned"
|
|
1458
|
+
});
|
|
1459
|
+
if (worktree.unowned) return /* @__PURE__ */ jsx(StatusBadge, {
|
|
1460
|
+
tone: "info",
|
|
1461
|
+
size: "xs",
|
|
1462
|
+
children: "Unowned"
|
|
1463
|
+
});
|
|
1464
|
+
return null;
|
|
1465
|
+
}
|
|
1466
|
+
function WorktreeRow({ worktree, compact = false, starting = false, error, disabled = false, onClose }) {
|
|
1467
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1047
1468
|
"data-testid": `git-worktree-${worktree.id}`,
|
|
1048
|
-
className: "flex min-w-0 flex-col gap-1
|
|
1469
|
+
className: compact ? "flex min-w-0 flex-col gap-1 py-2" : "flex min-w-0 flex-col gap-2 border-b border-po-border-soft py-4",
|
|
1049
1470
|
children: [
|
|
1050
1471
|
/* @__PURE__ */ jsxs("div", {
|
|
1051
|
-
className: "flex min-w-0 items-center gap-
|
|
1472
|
+
className: "flex min-w-0 items-center gap-2",
|
|
1052
1473
|
children: [
|
|
1053
|
-
/* @__PURE__ */ jsx(
|
|
1474
|
+
starting ? /* @__PURE__ */ jsx(Spinner, {
|
|
1475
|
+
className: "size-3 shrink-0 text-po-dim",
|
|
1476
|
+
label: "Starting session"
|
|
1477
|
+
}) : /* @__PURE__ */ jsx(Dot, {
|
|
1478
|
+
size: "md",
|
|
1479
|
+
tone: worktree.orphaned ? "yellow" : worktree.unowned ? "blue" : "green"
|
|
1480
|
+
}),
|
|
1054
1481
|
/* @__PURE__ */ jsx("span", {
|
|
1055
|
-
|
|
1482
|
+
title: worktree.branch,
|
|
1483
|
+
className: "min-w-0 flex-1 truncate font-mono text-base font-medium text-po-hi",
|
|
1056
1484
|
children: worktree.branch
|
|
1057
1485
|
}),
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
children: "
|
|
1067
|
-
})
|
|
1486
|
+
/* @__PURE__ */ jsx(WorktreeBadge, { worktree }),
|
|
1487
|
+
!compact && /* @__PURE__ */ jsx(Button, {
|
|
1488
|
+
variant: "ghost",
|
|
1489
|
+
size: "sm",
|
|
1490
|
+
onClick: onClose,
|
|
1491
|
+
disabled: starting || disabled,
|
|
1492
|
+
"data-testid": `git-worktree-close-${worktree.id}`,
|
|
1493
|
+
"aria-label": `Close ${worktree.branch}`,
|
|
1494
|
+
children: "Close"
|
|
1495
|
+
})
|
|
1068
1496
|
]
|
|
1069
1497
|
}),
|
|
1070
|
-
/* @__PURE__ */ jsx("
|
|
1071
|
-
className: "
|
|
1498
|
+
!compact && /* @__PURE__ */ jsx("p", {
|
|
1499
|
+
className: "truncate pl-4 font-mono text-sm text-po-dim",
|
|
1500
|
+
title: worktree.path,
|
|
1072
1501
|
children: worktree.path
|
|
1073
1502
|
}),
|
|
1074
|
-
/* @__PURE__ */
|
|
1075
|
-
className: "
|
|
1076
|
-
children:
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
className: "px-2 ",
|
|
1082
|
-
onClick: () => requestWorktreeClose(sessionId, worktree.id),
|
|
1083
|
-
children: "close"
|
|
1084
|
-
})]
|
|
1503
|
+
starting && /* @__PURE__ */ jsx("p", {
|
|
1504
|
+
className: "pl-4 text-sm text-po-dim",
|
|
1505
|
+
children: "Starting session..."
|
|
1506
|
+
}),
|
|
1507
|
+
!compact && (worktree.orphaned || worktree.unowned) && /* @__PURE__ */ jsx("p", {
|
|
1508
|
+
className: "pl-4 text-sm text-po-dim first-letter:uppercase",
|
|
1509
|
+
children: worktree.orphaned ? "its folder is missing" : "its session has ended"
|
|
1085
1510
|
}),
|
|
1086
|
-
error
|
|
1511
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
1087
1512
|
role: "alert",
|
|
1088
1513
|
"data-testid": "git-worktree-error",
|
|
1089
|
-
className: "text-sm text-po-red",
|
|
1514
|
+
className: "pl-4 text-sm text-po-red",
|
|
1090
1515
|
children: error
|
|
1091
1516
|
})
|
|
1092
1517
|
]
|
|
@@ -1264,56 +1689,6 @@ var gitChanges_web_default = defineChannelFile(gitChangesChannel);
|
|
|
1264
1689
|
//#region src/extensions/workspaces/sessions/(frontend)/channel/gitWorktrees.web.ts
|
|
1265
1690
|
var gitWorktrees_web_default = defineChannelFile(worktreesChannel);
|
|
1266
1691
|
//#endregion
|
|
1267
|
-
//#region src/extensions/workspaces/sessions/(frontend)/fill/_components/WorktreeCreateDialog.tsx
|
|
1268
|
-
function WorktreeSessionMenu({ sessionId, sessionReservationId }) {
|
|
1269
|
-
return /* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
1270
|
-
disabled: sessionId === null,
|
|
1271
|
-
"data-testid": `session-worktree-${sessionReservationId ?? sessionId}`,
|
|
1272
|
-
onSelect: () => {
|
|
1273
|
-
if (sessionId !== null) worktreeCreateDialog.update(() => ({
|
|
1274
|
-
sessionId,
|
|
1275
|
-
...sessionReservationId === void 0 ? {} : { reservationId: sessionReservationId }
|
|
1276
|
-
}));
|
|
1277
|
-
},
|
|
1278
|
-
children: "create worktree session…"
|
|
1279
|
-
});
|
|
1280
|
-
}
|
|
1281
|
-
function WorktreeCreateOverlay(props) {
|
|
1282
|
-
const target = useStore(worktreeCreateDialog.store);
|
|
1283
|
-
const { holdSessionChannels } = props;
|
|
1284
|
-
useEffect(() => {
|
|
1285
|
-
if (target === null) return;
|
|
1286
|
-
return holdSessionChannels?.(target.sessionId, (frame) => {
|
|
1287
|
-
if (frame.type !== worktreesChannel.channel) return;
|
|
1288
|
-
const payload = worktreesChannel.parse(frame.payload);
|
|
1289
|
-
if (payload !== null) worktreesChannel.apply(target.sessionId, payload);
|
|
1290
|
-
});
|
|
1291
|
-
}, [target, holdSessionChannels]);
|
|
1292
|
-
if (target === null) return null;
|
|
1293
|
-
return /* @__PURE__ */ jsx(Dialog, {
|
|
1294
|
-
open: true,
|
|
1295
|
-
onOpenChange: (open) => {
|
|
1296
|
-
if (!open) worktreeCreateDialog.update(() => null);
|
|
1297
|
-
},
|
|
1298
|
-
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
1299
|
-
width: "lg",
|
|
1300
|
-
"data-testid": "session-worktree-dialog",
|
|
1301
|
-
children: [/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: "create worktree session" }) }), /* @__PURE__ */ jsxs(DialogBody, { children: [/* @__PURE__ */ jsx(DialogDescription, { children: "Creates a separate checkout and a new session. Existing sessions stay in their current directories." }), /* @__PURE__ */ createElement(WorktreesPanel, {
|
|
1302
|
-
...props,
|
|
1303
|
-
key: `${target.sessionId}:${target.reservationId ?? ""}`,
|
|
1304
|
-
sessionId: target.sessionId,
|
|
1305
|
-
sessionReservationId: target.reservationId
|
|
1306
|
-
})] })]
|
|
1307
|
-
})
|
|
1308
|
-
});
|
|
1309
|
-
}
|
|
1310
|
-
//#endregion
|
|
1311
|
-
//#region src/extensions/workspaces/sessions/(frontend)/fill/Worktree.session-menu.web.tsx
|
|
1312
|
-
var Worktree_session_menu_web_default = defineFill({ component: WorktreeSessionMenu });
|
|
1313
|
-
//#endregion
|
|
1314
|
-
//#region src/extensions/workspaces/sessions/(frontend)/fill/WorktreeCreate.overlay.web.tsx
|
|
1315
|
-
var WorktreeCreate_overlay_web_default = defineFill({ component: WorktreeCreateOverlay });
|
|
1316
|
-
//#endregion
|
|
1317
1692
|
//#region src/extensions/workspaces/sessions/(frontend)/_lib/gitReviewApi.ts
|
|
1318
1693
|
/**
|
|
1319
1694
|
* The review tab's half of the session API: the changed-file list and one
|
|
@@ -1370,16 +1745,34 @@ async function fetchReviewFile(sessionId, path, signal) {
|
|
|
1370
1745
|
const gitReview = defineSessionStore({ comments: [] });
|
|
1371
1746
|
function addReviewComment(sessionId, comment) {
|
|
1372
1747
|
const id = globalThis.crypto.randomUUID();
|
|
1373
|
-
gitReview.update(sessionId, (current) => ({
|
|
1374
|
-
...
|
|
1375
|
-
|
|
1376
|
-
|
|
1748
|
+
gitReview.update(sessionId, (current) => ({
|
|
1749
|
+
...current,
|
|
1750
|
+
comments: [...current.comments, {
|
|
1751
|
+
...comment,
|
|
1752
|
+
id
|
|
1753
|
+
}]
|
|
1754
|
+
}));
|
|
1377
1755
|
}
|
|
1378
1756
|
function removeReviewComment(sessionId, id) {
|
|
1379
|
-
gitReview.update(sessionId, (current) => ({
|
|
1757
|
+
gitReview.update(sessionId, (current) => ({
|
|
1758
|
+
...current,
|
|
1759
|
+
comments: current.comments.filter((comment) => comment.id !== id)
|
|
1760
|
+
}));
|
|
1380
1761
|
}
|
|
1381
1762
|
function clearReviewComments(sessionId) {
|
|
1382
|
-
gitReview.update(sessionId, () => ({
|
|
1763
|
+
gitReview.update(sessionId, (current) => ({
|
|
1764
|
+
...current,
|
|
1765
|
+
comments: []
|
|
1766
|
+
}));
|
|
1767
|
+
}
|
|
1768
|
+
function focusReviewFile(sessionId, path) {
|
|
1769
|
+
gitReview.update(sessionId, (current) => ({
|
|
1770
|
+
...current,
|
|
1771
|
+
focus: {
|
|
1772
|
+
path,
|
|
1773
|
+
at: Date.now()
|
|
1774
|
+
}
|
|
1775
|
+
}));
|
|
1383
1776
|
}
|
|
1384
1777
|
//#endregion
|
|
1385
1778
|
//#region src/extensions/workspaces/sessions/(frontend)/_lib/gitReviewView.ts
|
|
@@ -1411,6 +1804,23 @@ function conflictPrompt(branch, conflicts) {
|
|
|
1411
1804
|
].join("\n");
|
|
1412
1805
|
}
|
|
1413
1806
|
/**
|
|
1807
|
+
* The one sync the bar fills in: pull while the branch is behind (a push would
|
|
1808
|
+
* be rejected), push while it is only ahead or has no upstream yet. Nothing
|
|
1809
|
+
* while a rebase is paused, detached, or already level.
|
|
1810
|
+
*/
|
|
1811
|
+
function suggestedSyncAction(changes) {
|
|
1812
|
+
if (changes?.branch === void 0 || changes.rebase !== void 0) return void 0;
|
|
1813
|
+
if (changes.upstream === void 0) return "push";
|
|
1814
|
+
if (changes.upstream.behind > 0) return "pull";
|
|
1815
|
+
return changes.upstream.ahead > 0 ? "push" : void 0;
|
|
1816
|
+
}
|
|
1817
|
+
/** "2 to push, 1 new on origin/feat/git", or "Up to date with origin/feat/git". */
|
|
1818
|
+
function upstreamSummary(upstream) {
|
|
1819
|
+
if (upstream === void 0) return "No upstream yet";
|
|
1820
|
+
const parts = [...upstream.ahead > 0 ? [`${String(upstream.ahead)} to push`] : [], ...upstream.behind > 0 ? [`${String(upstream.behind)} new on ${upstream.ref}`] : []];
|
|
1821
|
+
return parts.length === 0 ? `Up to date with ${upstream.ref}` : parts.join(", ");
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1414
1824
|
* Hands one deferred follow-up to the session. Returns the reason it failed,
|
|
1415
1825
|
* or undefined once sent. The sender throws when the session is disconnected.
|
|
1416
1826
|
*/
|
|
@@ -1426,485 +1836,302 @@ function deliverToAgent(send, sessionId, message) {
|
|
|
1426
1836
|
}
|
|
1427
1837
|
}
|
|
1428
1838
|
//#endregion
|
|
1429
|
-
//#region src/extensions/workspaces/sessions/(frontend)/_lib/gitReviewTree.ts
|
|
1430
|
-
function buildTree(files) {
|
|
1431
|
-
const root = {
|
|
1432
|
-
path: "",
|
|
1433
|
-
dirs: /* @__PURE__ */ new Map(),
|
|
1434
|
-
files: []
|
|
1435
|
-
};
|
|
1436
|
-
for (const file of files) {
|
|
1437
|
-
const parts = file.path.split("/");
|
|
1438
|
-
parts.pop();
|
|
1439
|
-
let dir = root;
|
|
1440
|
-
for (const part of parts) {
|
|
1441
|
-
let next = dir.dirs.get(part);
|
|
1442
|
-
if (next === void 0) {
|
|
1443
|
-
next = {
|
|
1444
|
-
path: dir.path === "" ? part : `${dir.path}/${part}`,
|
|
1445
|
-
dirs: /* @__PURE__ */ new Map(),
|
|
1446
|
-
files: []
|
|
1447
|
-
};
|
|
1448
|
-
dir.dirs.set(part, next);
|
|
1449
|
-
}
|
|
1450
|
-
dir = next;
|
|
1451
|
-
}
|
|
1452
|
-
dir.files.push(file);
|
|
1453
|
-
}
|
|
1454
|
-
return root;
|
|
1455
|
-
}
|
|
1456
|
-
/** Folders first, then files, each alphabetical; single-child folder chains render as one row. */
|
|
1457
|
-
function flattenTree(files, isOpen) {
|
|
1458
|
-
const rows = [];
|
|
1459
|
-
const walk = (dir, depth) => {
|
|
1460
|
-
for (const [segment, child] of [...dir.dirs].sort(([left], [right]) => left.localeCompare(right))) {
|
|
1461
|
-
let name = segment;
|
|
1462
|
-
let node = child;
|
|
1463
|
-
while (node.files.length === 0 && node.dirs.size === 1) {
|
|
1464
|
-
const [[nextName, next]] = [...node.dirs];
|
|
1465
|
-
name = `${name}/${nextName}`;
|
|
1466
|
-
node = next;
|
|
1467
|
-
}
|
|
1468
|
-
const open = isOpen(node.path);
|
|
1469
|
-
rows.push({
|
|
1470
|
-
kind: "dir",
|
|
1471
|
-
path: node.path,
|
|
1472
|
-
name,
|
|
1473
|
-
depth,
|
|
1474
|
-
open
|
|
1475
|
-
});
|
|
1476
|
-
if (open) walk(node, depth + 1);
|
|
1477
|
-
}
|
|
1478
|
-
for (const file of [...dir.files].sort((left, right) => left.path.localeCompare(right.path))) rows.push({
|
|
1479
|
-
kind: "file",
|
|
1480
|
-
file,
|
|
1481
|
-
depth
|
|
1482
|
-
});
|
|
1483
|
-
};
|
|
1484
|
-
walk(buildTree(files), 0);
|
|
1485
|
-
return rows;
|
|
1486
|
-
}
|
|
1487
|
-
//#endregion
|
|
1488
1839
|
//#region src/extensions/workspaces/sessions/(frontend)/_components/GitReviewBrowser.tsx
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
tone: "text-po-yellow",
|
|
1516
|
-
label: "modified"
|
|
1517
|
-
},
|
|
1518
|
-
deleted: {
|
|
1519
|
-
letter: "D",
|
|
1520
|
-
tone: "text-po-red",
|
|
1521
|
-
label: "deleted"
|
|
1522
|
-
},
|
|
1523
|
-
untracked: {
|
|
1524
|
-
letter: "U",
|
|
1525
|
-
tone: "text-po-blue",
|
|
1526
|
-
label: "untracked"
|
|
1527
|
-
},
|
|
1528
|
-
conflicted: {
|
|
1529
|
-
letter: "C",
|
|
1530
|
-
tone: "text-po-red",
|
|
1531
|
-
label: "conflicted"
|
|
1532
|
-
}
|
|
1840
|
+
const status = {
|
|
1841
|
+
added: [
|
|
1842
|
+
"A",
|
|
1843
|
+
"text-po-green",
|
|
1844
|
+
"Added"
|
|
1845
|
+
],
|
|
1846
|
+
modified: [
|
|
1847
|
+
"M",
|
|
1848
|
+
"text-po-yellow",
|
|
1849
|
+
"Modified"
|
|
1850
|
+
],
|
|
1851
|
+
deleted: [
|
|
1852
|
+
"D",
|
|
1853
|
+
"text-po-red",
|
|
1854
|
+
"Deleted"
|
|
1855
|
+
],
|
|
1856
|
+
untracked: [
|
|
1857
|
+
"U",
|
|
1858
|
+
"text-po-blue",
|
|
1859
|
+
"Untracked"
|
|
1860
|
+
],
|
|
1861
|
+
conflicted: [
|
|
1862
|
+
"C",
|
|
1863
|
+
"text-po-red",
|
|
1864
|
+
"Conflicted"
|
|
1865
|
+
]
|
|
1533
1866
|
};
|
|
1534
|
-
const baseName = (path) => path.slice(path.lastIndexOf("/") + 1);
|
|
1535
|
-
/** Left padding per depth level; tight so deep files keep room for their names. */
|
|
1536
|
-
const indent = (depth) => ({ paddingLeft: `${String(.75 + depth * .5)}rem` });
|
|
1537
1867
|
function GitReviewBrowser({ files, activePath, commentCounts, onPick }) {
|
|
1538
1868
|
const [query, setQuery] = useState("");
|
|
1539
|
-
const
|
|
1540
|
-
const needle = query.trim().toLocaleLowerCase();
|
|
1541
|
-
const shown = needle === "" ? files : files.filter((file) => file.path.toLocaleLowerCase().includes(needle));
|
|
1542
|
-
const rows = flattenTree(shown, (dirPath) => needle !== "" || !collapsed.has(dirPath));
|
|
1543
|
-
const ordered = rows.flatMap((row) => row.kind === "file" ? [row.file] : []);
|
|
1869
|
+
const shown = files.filter((f) => f.path.toLowerCase().includes(query.trim().toLowerCase()));
|
|
1544
1870
|
const listRef = useRef(null);
|
|
1545
1871
|
useEffect(() => {
|
|
1546
1872
|
if (activePath === void 0) return;
|
|
1547
|
-
|
|
1873
|
+
listRef.current?.querySelector(`[data-path="${CSS.escape(activePath)}"]`)?.scrollIntoView({ block: "nearest" });
|
|
1548
1874
|
}, [activePath]);
|
|
1549
|
-
const toggle = (dirPath) => {
|
|
1550
|
-
setCollapsed((prev) => {
|
|
1551
|
-
const next = new Set(prev);
|
|
1552
|
-
if (!next.delete(dirPath)) next.add(dirPath);
|
|
1553
|
-
return next;
|
|
1554
|
-
});
|
|
1555
|
-
};
|
|
1556
|
-
const allDirs = () => new Set(flattenTree(files, () => true).flatMap((row) => row.kind === "dir" ? [row.path] : []));
|
|
1557
1875
|
/** Arrow keys pick the neighbouring row and move focus with it, so the diff follows the cursor. */
|
|
1558
1876
|
const onKeyDown = (event) => {
|
|
1559
1877
|
if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
|
|
1560
|
-
if (
|
|
1878
|
+
if (shown.length === 0) return;
|
|
1561
1879
|
event.preventDefault();
|
|
1562
|
-
const current =
|
|
1880
|
+
const current = shown.findIndex((file) => file.path === activePath);
|
|
1563
1881
|
const step = event.key === "ArrowDown" ? 1 : -1;
|
|
1564
|
-
const next = current === -1 ? 0 : (current + step +
|
|
1565
|
-
const file =
|
|
1882
|
+
const next = current === -1 ? 0 : (current + step + shown.length) % shown.length;
|
|
1883
|
+
const file = shown[next];
|
|
1566
1884
|
if (file === void 0) return;
|
|
1567
1885
|
onPick(file.path);
|
|
1568
1886
|
event.currentTarget.closest("[data-git-review-list]")?.querySelectorAll("[data-git-review-row]")[next]?.focus();
|
|
1569
1887
|
};
|
|
1570
1888
|
return /* @__PURE__ */ jsxs("nav", {
|
|
1889
|
+
"aria-label": "Changed files",
|
|
1571
1890
|
"data-testid": "git-review-browser",
|
|
1572
|
-
|
|
1573
|
-
className: "flex min-h-0 w-64 shrink-0 flex-col border-r border-po-border-soft",
|
|
1891
|
+
className: "flex min-h-0 w-80 shrink-0 flex-col border-r border-po-border-soft",
|
|
1574
1892
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
1575
|
-
className: "flex
|
|
1893
|
+
className: "flex flex-col gap-3 border-b border-po-border-soft p-3",
|
|
1576
1894
|
children: [/* @__PURE__ */ jsx(Input, {
|
|
1577
1895
|
"data-testid": "git-review-search",
|
|
1578
|
-
"aria-label": "search changed file paths",
|
|
1579
1896
|
size: "sm",
|
|
1897
|
+
"aria-label": "Search paths",
|
|
1898
|
+
placeholder: "Search paths",
|
|
1580
1899
|
value: query,
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
1586
|
-
className: "text-sm text-po-dim",
|
|
1587
|
-
children: needle === "" ? `${String(files.length)} changed` : `${String(shown.length)} of ${String(files.length)} match`
|
|
1588
|
-
}), needle === "" ? /* @__PURE__ */ jsx(Button, {
|
|
1589
|
-
variant: "link",
|
|
1590
|
-
size: "xs",
|
|
1591
|
-
"data-testid": "git-review-fold-all",
|
|
1592
|
-
className: "h-auto px-0",
|
|
1593
|
-
onClick: () => setCollapsed(collapsed.size === 0 ? allDirs() : /* @__PURE__ */ new Set()),
|
|
1594
|
-
children: collapsed.size === 0 ? "collapse all" : "expand all"
|
|
1595
|
-
}) : null]
|
|
1900
|
+
onChange: (e) => setQuery(e.target.value)
|
|
1901
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
1902
|
+
className: "text-sm text-po-dim",
|
|
1903
|
+
children: [shown.length, " changed"]
|
|
1596
1904
|
})]
|
|
1597
1905
|
}), /* @__PURE__ */ jsxs("div", {
|
|
1598
1906
|
ref: listRef,
|
|
1599
1907
|
"data-git-review-list": true,
|
|
1600
|
-
className: "min-h-0 flex-1 overflow-y-auto
|
|
1601
|
-
children: [
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
variant: "ghost",
|
|
1606
|
-
size: "card",
|
|
1607
|
-
"aria-expanded": row.open,
|
|
1608
|
-
"data-testid": `git-review-browser-dir-${row.path}`,
|
|
1609
|
-
title: row.path,
|
|
1610
|
-
onClick: () => toggle(row.path),
|
|
1611
|
-
style: indent(row.depth),
|
|
1612
|
-
className: "min-w-0 flex-row items-center gap-1 rounded-none border-l-2 border-transparent py-1 pr-3 hover:bg-po-panel/60",
|
|
1613
|
-
children: [/* @__PURE__ */ jsx(Chevron, {
|
|
1614
|
-
"aria-hidden": true,
|
|
1615
|
-
className: "h-3 w-3 shrink-0 text-po-faint"
|
|
1616
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
1617
|
-
className: "min-w-0 flex-1 truncate text-base text-po-dim",
|
|
1618
|
-
children: row.name
|
|
1619
|
-
})]
|
|
1620
|
-
}, `dir:${row.path}`);
|
|
1621
|
-
}
|
|
1622
|
-
const { file } = row;
|
|
1623
|
-
const mark = STATUS_MARK[file.status];
|
|
1624
|
-
const comments = commentCounts[file.path] ?? 0;
|
|
1625
|
-
const active = file.path === activePath;
|
|
1626
|
-
return /* @__PURE__ */ jsxs(Button, {
|
|
1627
|
-
variant: "ghost",
|
|
1628
|
-
size: "card",
|
|
1908
|
+
className: "min-h-0 flex-1 overflow-y-auto",
|
|
1909
|
+
children: [shown.map((f) => {
|
|
1910
|
+
const slash = f.path.lastIndexOf("/");
|
|
1911
|
+
const count = commentCounts[f.path] ?? 0;
|
|
1912
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
1629
1913
|
"data-git-review-row": true,
|
|
1630
|
-
"data-path":
|
|
1631
|
-
"
|
|
1632
|
-
"data-testid": `git-review-browser-file-${file.path}`,
|
|
1633
|
-
title: `${file.path} (${mark.label})`,
|
|
1634
|
-
onClick: () => onPick(file.path),
|
|
1914
|
+
"data-path": f.path,
|
|
1915
|
+
"data-testid": `git-review-browser-file-${f.path}`,
|
|
1635
1916
|
onKeyDown,
|
|
1636
|
-
|
|
1637
|
-
|
|
1917
|
+
title: f.path,
|
|
1918
|
+
"aria-current": activePath === f.path ? "true" : void 0,
|
|
1919
|
+
onClick: () => onPick(f.path),
|
|
1920
|
+
className: `flex w-full min-w-0 items-start gap-2 border-l-2 px-3 py-3 text-left ${activePath === f.path ? "border-po-green bg-po-tint-green" : "border-transparent hover:bg-po-panel"}`,
|
|
1638
1921
|
children: [
|
|
1639
1922
|
/* @__PURE__ */ jsx("span", {
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
children: mark.letter
|
|
1923
|
+
className: `pt-0.5 text-sm font-semibold ${status[f.status][1]}`,
|
|
1924
|
+
children: status[f.status][0]
|
|
1643
1925
|
}),
|
|
1644
|
-
/* @__PURE__ */
|
|
1645
|
-
className:
|
|
1646
|
-
children:
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
className: "h-2.5 w-2.5"
|
|
1654
|
-
}), comments]
|
|
1926
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1927
|
+
className: "min-w-0 flex-1",
|
|
1928
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1929
|
+
className: "block truncate text-base text-po-text",
|
|
1930
|
+
children: f.path.slice(slash + 1)
|
|
1931
|
+
}), slash < 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
1932
|
+
className: "block truncate font-mono text-sm text-po-dim",
|
|
1933
|
+
children: f.path.slice(0, slash)
|
|
1934
|
+
})]
|
|
1655
1935
|
}),
|
|
1656
|
-
|
|
1657
|
-
className: "shrink-0
|
|
1658
|
-
children: "
|
|
1659
|
-
}) : /* @__PURE__ */ jsxs("span", {
|
|
1660
|
-
className: "shrink-0 text-2xs",
|
|
1661
|
-
children: [
|
|
1936
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1937
|
+
className: "flex shrink-0 flex-col items-end gap-1 text-sm",
|
|
1938
|
+
children: [/* @__PURE__ */ jsxs("span", { children: [
|
|
1662
1939
|
/* @__PURE__ */ jsxs("span", {
|
|
1663
1940
|
className: "text-po-green",
|
|
1664
|
-
children: ["+",
|
|
1941
|
+
children: ["+", f.added]
|
|
1665
1942
|
}),
|
|
1666
1943
|
" ",
|
|
1667
1944
|
/* @__PURE__ */ jsxs("span", {
|
|
1668
1945
|
className: "text-po-red",
|
|
1669
|
-
children: ["-",
|
|
1946
|
+
children: ["-", f.removed]
|
|
1670
1947
|
})
|
|
1671
|
-
]
|
|
1948
|
+
] }), count > 0 && /* @__PURE__ */ jsxs("span", {
|
|
1949
|
+
className: "flex items-center gap-1 text-po-blue",
|
|
1950
|
+
children: [/* @__PURE__ */ jsx(MessageIcon, { className: "h-3 w-3" }), count]
|
|
1951
|
+
})]
|
|
1672
1952
|
})
|
|
1673
1953
|
]
|
|
1674
|
-
},
|
|
1675
|
-
}),
|
|
1676
|
-
className: "
|
|
1677
|
-
children: "
|
|
1678
|
-
})
|
|
1954
|
+
}, f.path);
|
|
1955
|
+
}), shown.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
1956
|
+
className: "p-3 text-sm text-po-dim",
|
|
1957
|
+
children: "No paths match."
|
|
1958
|
+
})]
|
|
1679
1959
|
})]
|
|
1680
1960
|
});
|
|
1681
1961
|
}
|
|
1682
1962
|
//#endregion
|
|
1683
1963
|
//#region src/extensions/workspaces/sessions/(frontend)/_components/GitSyncBar.tsx
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
* an error target when it fails, a paused rebase when it stops on conflicts.
|
|
1692
|
-
* - A push the remote rejects is not an error to shrug at: the strip asks
|
|
1693
|
-
* before forcing, and the force it sends is a lease, which git refuses if
|
|
1694
|
-
* the remote holds commits this branch never fetched and integrated.
|
|
1695
|
-
* - A paused rebase blocks the sync buttons. The two ways out are named on the
|
|
1696
|
-
* banner, and abort says what it throws away.
|
|
1697
|
-
*
|
|
1698
|
-
* AVOID:
|
|
1699
|
-
* - Colour classes outside the token set.
|
|
1700
|
-
*/
|
|
1701
|
-
function GitSyncBar({ changes, mergeBase, pending, error, errorTarget, onSync, onForcePush, onAskAgent, onDismissError }) {
|
|
1702
|
-
const busy = pending !== void 0;
|
|
1703
|
-
const paused = changes?.rebase !== void 0;
|
|
1704
|
-
const forceRequired = errorTarget?.action === "push" && "forceRequired" in errorTarget && errorTarget.forceRequired === true;
|
|
1705
|
-
const syncError = error !== void 0 && !forceRequired ? error : void 0;
|
|
1706
|
-
const upstream = changes?.upstream;
|
|
1964
|
+
function GitSyncBar({ secondary = false, ...props }) {
|
|
1965
|
+
const c = props.changes;
|
|
1966
|
+
const paused = c?.rebase !== void 0;
|
|
1967
|
+
const busy = props.pending !== void 0;
|
|
1968
|
+
const rejected = props.errorTarget?.action === "push" && props.errorTarget.forceRequired;
|
|
1969
|
+
const suggested = suggestedSyncAction(c);
|
|
1970
|
+
const upstream = upstreamSummary(c?.upstream);
|
|
1707
1971
|
return /* @__PURE__ */ jsxs("div", {
|
|
1708
1972
|
"data-testid": "git-sync-bar",
|
|
1709
|
-
className: "flex
|
|
1973
|
+
className: "flex flex-col gap-3 border-b border-po-border-soft p-4",
|
|
1710
1974
|
children: [
|
|
1711
1975
|
/* @__PURE__ */ jsxs("div", {
|
|
1712
|
-
className: "flex
|
|
1976
|
+
className: "flex flex-wrap items-center gap-3 text-sm",
|
|
1713
1977
|
children: [
|
|
1714
|
-
/* @__PURE__ */
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
className: "h-3.5 w-3.5 shrink-0 text-po-faint"
|
|
1720
|
-
}),
|
|
1721
|
-
/* @__PURE__ */ jsx("span", {
|
|
1722
|
-
"data-testid": "git-review-branch",
|
|
1723
|
-
className: "truncate text-base font-semibold text-po-hi",
|
|
1724
|
-
children: changes?.branch ?? "detached HEAD"
|
|
1725
|
-
}),
|
|
1726
|
-
/* @__PURE__ */ jsx("span", {
|
|
1727
|
-
className: "shrink-0 text-sm text-po-dim",
|
|
1728
|
-
children: changes?.base === void 0 ? "uncommitted changes" : `vs ${changes.base}`
|
|
1729
|
-
}),
|
|
1730
|
-
mergeBase === void 0 ? null : /* @__PURE__ */ jsxs("span", {
|
|
1731
|
-
className: "shrink-0 text-sm text-po-dim",
|
|
1732
|
-
children: ["from ", mergeBase]
|
|
1733
|
-
})
|
|
1734
|
-
]
|
|
1978
|
+
/* @__PURE__ */ jsx(BranchIcon, { className: "h-4 w-4 text-po-dim" }),
|
|
1979
|
+
/* @__PURE__ */ jsx("span", {
|
|
1980
|
+
"data-testid": "git-review-branch",
|
|
1981
|
+
className: "font-mono text-base font-semibold text-po-hi",
|
|
1982
|
+
children: c?.branch ?? "Detached HEAD"
|
|
1735
1983
|
}),
|
|
1736
|
-
|
|
1984
|
+
/* @__PURE__ */ jsx("span", {
|
|
1985
|
+
className: "text-po-dim",
|
|
1986
|
+
children: c?.base === void 0 ? "Uncommitted changes" : /* @__PURE__ */ jsxs(Fragment, { children: ["vs ", /* @__PURE__ */ jsx("span", {
|
|
1987
|
+
className: "font-mono",
|
|
1988
|
+
children: c.base
|
|
1989
|
+
})] })
|
|
1990
|
+
}),
|
|
1991
|
+
props.mergeBase && /* @__PURE__ */ jsxs("span", {
|
|
1992
|
+
className: "text-po-dim",
|
|
1993
|
+
children: ["from ", /* @__PURE__ */ jsx("span", {
|
|
1994
|
+
className: "font-mono",
|
|
1995
|
+
children: props.mergeBase
|
|
1996
|
+
})]
|
|
1997
|
+
}),
|
|
1998
|
+
c === void 0 ? null : /* @__PURE__ */ jsxs("span", {
|
|
1737
1999
|
"data-testid": "git-review-totals",
|
|
1738
|
-
className: "
|
|
2000
|
+
className: "contents",
|
|
1739
2001
|
children: [
|
|
1740
2002
|
/* @__PURE__ */ jsxs("span", {
|
|
1741
|
-
className: "
|
|
1742
|
-
children: ["+",
|
|
2003
|
+
className: "text-po-green",
|
|
2004
|
+
children: ["+", c?.added]
|
|
1743
2005
|
}),
|
|
1744
2006
|
/* @__PURE__ */ jsxs("span", {
|
|
1745
|
-
className: "
|
|
1746
|
-
children: ["-",
|
|
2007
|
+
className: "text-po-red",
|
|
2008
|
+
children: ["-", c?.removed]
|
|
1747
2009
|
}),
|
|
1748
2010
|
/* @__PURE__ */ jsxs("span", {
|
|
1749
2011
|
className: "text-po-dim",
|
|
1750
2012
|
children: [
|
|
1751
|
-
|
|
1752
|
-
|
|
2013
|
+
c?.files,
|
|
2014
|
+
c?.truncated ? "+" : "",
|
|
1753
2015
|
" ",
|
|
1754
|
-
|
|
2016
|
+
c?.files === 1 ? "file" : "files"
|
|
1755
2017
|
]
|
|
1756
2018
|
})
|
|
1757
2019
|
]
|
|
1758
2020
|
}),
|
|
1759
|
-
/* @__PURE__ */ jsx("span", { className: "
|
|
1760
|
-
|
|
1761
|
-
className: "
|
|
1762
|
-
children: "
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
children: [
|
|
1767
|
-
"↑",
|
|
1768
|
-
upstream.ahead,
|
|
1769
|
-
" ↓",
|
|
1770
|
-
upstream.behind,
|
|
1771
|
-
" ",
|
|
1772
|
-
upstream.ref
|
|
1773
|
-
]
|
|
2021
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
2022
|
+
/* @__PURE__ */ jsx("span", {
|
|
2023
|
+
className: "text-po-dim",
|
|
2024
|
+
children: c?.branch === void 0 ? "No branch checked out" : c.upstream === void 0 || !upstream.endsWith(c.upstream.ref) ? upstream : /* @__PURE__ */ jsxs(Fragment, { children: [upstream.slice(0, -c.upstream.ref.length), /* @__PURE__ */ jsx("span", {
|
|
2025
|
+
className: "font-mono",
|
|
2026
|
+
children: c.upstream.ref
|
|
2027
|
+
})] })
|
|
1774
2028
|
}),
|
|
1775
|
-
/* @__PURE__ */
|
|
1776
|
-
className: "flex
|
|
2029
|
+
/* @__PURE__ */ jsx("div", {
|
|
2030
|
+
className: "flex gap-1.5",
|
|
1777
2031
|
children: [
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
disabled: busy || paused || changes?.branch === void 0,
|
|
1790
|
-
title: upstream === void 0 ? "push and set the upstream" : `push to ${upstream.ref}`,
|
|
1791
|
-
onClick: () => onSync("push"),
|
|
1792
|
-
children: "push"
|
|
1793
|
-
}),
|
|
1794
|
-
/* @__PURE__ */ jsx(Button, {
|
|
1795
|
-
size: "xs",
|
|
1796
|
-
"data-testid": "git-sync-rebase",
|
|
1797
|
-
disabled: busy || paused || changes?.base === void 0,
|
|
1798
|
-
title: changes?.base === void 0 ? "no base branch to rebase onto" : `fetch, then rebase onto ${changes.base}`,
|
|
1799
|
-
onClick: () => onSync("rebase"),
|
|
1800
|
-
children: "rebase"
|
|
1801
|
-
})
|
|
1802
|
-
]
|
|
2032
|
+
"pull",
|
|
2033
|
+
"push",
|
|
2034
|
+
"rebase"
|
|
2035
|
+
].map((action) => /* @__PURE__ */ jsx(Button, {
|
|
2036
|
+
"data-testid": `git-sync-${action}`,
|
|
2037
|
+
size: "sm",
|
|
2038
|
+
variant: !busy && !paused && !rejected && !props.error && c?.branch !== void 0 && suggested === action ? secondary ? "outline" : "primary" : "ghost",
|
|
2039
|
+
disabled: busy || paused || action !== "rebase" && c?.branch === void 0 || action === "pull" && c?.upstream === void 0 || action === "rebase" && c?.base === void 0,
|
|
2040
|
+
onClick: () => props.onSync(action),
|
|
2041
|
+
children: action === "pull" ? "Pull" : action === "push" ? "Push" : "Rebase"
|
|
2042
|
+
}, action))
|
|
1803
2043
|
})
|
|
1804
2044
|
]
|
|
1805
2045
|
}),
|
|
1806
|
-
|
|
2046
|
+
busy && /* @__PURE__ */ jsxs("div", {
|
|
1807
2047
|
"data-testid": "git-sync-pending",
|
|
1808
|
-
className: "flex
|
|
2048
|
+
className: "flex items-center gap-2 text-sm text-po-dim",
|
|
1809
2049
|
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
1810
|
-
className: "h-
|
|
1811
|
-
label: pending
|
|
1812
|
-
}),
|
|
1813
|
-
className: "min-w-0 truncate text-sm text-po-dim",
|
|
1814
|
-
children: pending
|
|
1815
|
-
})]
|
|
1816
|
-
}),
|
|
1817
|
-
syncError === void 0 ? null : /* @__PURE__ */ jsxs("div", {
|
|
1818
|
-
role: "alert",
|
|
1819
|
-
"data-testid": "git-sync-error",
|
|
1820
|
-
className: "flex min-w-0 items-start gap-1.5 text-sm text-po-red",
|
|
1821
|
-
children: [
|
|
1822
|
-
/* @__PURE__ */ jsx(AlertIcon, {
|
|
1823
|
-
"aria-hidden": true,
|
|
1824
|
-
className: "mt-px h-3 w-3 shrink-0"
|
|
1825
|
-
}),
|
|
1826
|
-
/* @__PURE__ */ jsx("span", {
|
|
1827
|
-
className: "min-w-0 flex-1",
|
|
1828
|
-
children: syncError
|
|
1829
|
-
}),
|
|
1830
|
-
/* @__PURE__ */ jsx(Button, {
|
|
1831
|
-
variant: "ghost",
|
|
1832
|
-
size: "xs",
|
|
1833
|
-
className: "h-auto min-h-0 px-1 py-0 ",
|
|
1834
|
-
onClick: onDismissError,
|
|
1835
|
-
children: "dismiss"
|
|
1836
|
-
})
|
|
1837
|
-
]
|
|
2050
|
+
className: "h-4 w-4 text-po-dim",
|
|
2051
|
+
label: props.pending
|
|
2052
|
+
}), props.pending]
|
|
1838
2053
|
}),
|
|
1839
|
-
|
|
2054
|
+
rejected ? /* @__PURE__ */ jsxs("div", {
|
|
1840
2055
|
"data-testid": "git-push-force-confirm",
|
|
1841
|
-
className: "flex
|
|
2056
|
+
className: "flex flex-wrap items-center gap-3 rounded-md border border-po-edge-yellow bg-po-tint-yellow p-3",
|
|
1842
2057
|
children: [
|
|
1843
2058
|
/* @__PURE__ */ jsx("span", {
|
|
1844
|
-
className: "
|
|
1845
|
-
children: "
|
|
2059
|
+
className: "flex-1 text-base text-po-text",
|
|
2060
|
+
children: "origin has commits you have not pulled yet."
|
|
1846
2061
|
}),
|
|
1847
2062
|
/* @__PURE__ */ jsx(Button, {
|
|
2063
|
+
size: "sm",
|
|
1848
2064
|
variant: "danger-outline",
|
|
1849
|
-
size: "xs",
|
|
1850
2065
|
"data-testid": "git-push-force",
|
|
1851
2066
|
disabled: busy,
|
|
1852
|
-
onClick: onForcePush,
|
|
1853
|
-
children: "
|
|
2067
|
+
onClick: props.onForcePush,
|
|
2068
|
+
children: "Force push with lease"
|
|
1854
2069
|
}),
|
|
1855
2070
|
/* @__PURE__ */ jsx(Button, {
|
|
2071
|
+
size: "sm",
|
|
1856
2072
|
variant: "ghost",
|
|
1857
|
-
size: "xs",
|
|
1858
2073
|
"data-testid": "git-push-force-cancel",
|
|
1859
|
-
onClick: onDismissError,
|
|
1860
|
-
children: "
|
|
2074
|
+
onClick: props.onDismissError,
|
|
2075
|
+
children: "Not now"
|
|
1861
2076
|
})
|
|
1862
2077
|
]
|
|
1863
|
-
}) :
|
|
1864
|
-
|
|
2078
|
+
}) : props.error && /* @__PURE__ */ jsxs("div", {
|
|
2079
|
+
role: "alert",
|
|
2080
|
+
"data-testid": "git-sync-error",
|
|
2081
|
+
className: "flex items-center gap-3 rounded-md border border-po-edge-red bg-po-tint-red p-3 text-base text-po-red",
|
|
2082
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
2083
|
+
className: "flex-1",
|
|
2084
|
+
children: props.error
|
|
2085
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
2086
|
+
size: "sm",
|
|
2087
|
+
variant: "ghost",
|
|
2088
|
+
onClick: props.onDismissError,
|
|
2089
|
+
children: "Dismiss"
|
|
2090
|
+
})]
|
|
2091
|
+
}),
|
|
2092
|
+
paused && /* @__PURE__ */ jsxs("div", {
|
|
1865
2093
|
"data-testid": "git-rebase-banner",
|
|
1866
|
-
className: "flex
|
|
2094
|
+
className: "flex flex-col gap-2 rounded-md border border-po-edge-yellow bg-po-tint-yellow p-3",
|
|
1867
2095
|
children: [
|
|
1868
|
-
/* @__PURE__ */ jsxs("
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
className: "flex flex-wrap items-center gap-1.5",
|
|
2096
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(StatusBadge, {
|
|
2097
|
+
tone: "running",
|
|
2098
|
+
size: "xs",
|
|
2099
|
+
children: "Rebase paused"
|
|
2100
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
2101
|
+
className: "ml-2 text-base text-po-text",
|
|
2102
|
+
children: [
|
|
2103
|
+
"Conflicts in ",
|
|
2104
|
+
c.rebase?.conflicts.length,
|
|
2105
|
+
" ",
|
|
2106
|
+
c.rebase?.conflicts.length === 1 ? "file" : "files"
|
|
2107
|
+
]
|
|
2108
|
+
})] }),
|
|
2109
|
+
c.rebase?.conflicts.map((path) => /* @__PURE__ */ jsx("p", {
|
|
2110
|
+
className: "font-mono text-sm text-po-dim",
|
|
2111
|
+
children: path
|
|
2112
|
+
}, path)),
|
|
2113
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2114
|
+
className: "flex items-center gap-2",
|
|
1888
2115
|
children: [
|
|
1889
2116
|
/* @__PURE__ */ jsx(Button, {
|
|
1890
|
-
variant: "primary",
|
|
1891
|
-
size: "
|
|
2117
|
+
variant: secondary ? "outline" : "primary",
|
|
2118
|
+
size: "sm",
|
|
1892
2119
|
"data-testid": "git-rebase-ask-agent",
|
|
1893
2120
|
disabled: busy,
|
|
1894
|
-
onClick: onAskAgent,
|
|
1895
|
-
children: "
|
|
2121
|
+
onClick: props.onAskAgent,
|
|
2122
|
+
children: "Ask Po to resolve"
|
|
1896
2123
|
}),
|
|
1897
2124
|
/* @__PURE__ */ jsx(Button, {
|
|
1898
2125
|
variant: "danger-outline",
|
|
1899
|
-
size: "
|
|
2126
|
+
size: "sm",
|
|
1900
2127
|
"data-testid": "git-rebase-abort",
|
|
1901
2128
|
disabled: busy,
|
|
1902
|
-
onClick: () => onSync("abort-rebase"),
|
|
1903
|
-
children: "
|
|
2129
|
+
onClick: () => props.onSync("abort-rebase"),
|
|
2130
|
+
children: "Abort rebase"
|
|
1904
2131
|
}),
|
|
1905
2132
|
/* @__PURE__ */ jsx("span", {
|
|
1906
2133
|
className: "text-sm text-po-dim",
|
|
1907
|
-
children: "
|
|
2134
|
+
children: "Abort discards conflict resolution in progress."
|
|
1908
2135
|
})
|
|
1909
2136
|
]
|
|
1910
2137
|
})
|
|
@@ -1915,29 +2142,7 @@ function GitSyncBar({ changes, mergeBase, pending, error, errorTarget, onSync, o
|
|
|
1915
2142
|
}
|
|
1916
2143
|
//#endregion
|
|
1917
2144
|
//#region src/extensions/workspaces/sessions/(frontend)/_components/GitReviewView.tsx
|
|
1918
|
-
/**
|
|
1919
|
-
* The review tab, drawn from what it is handed: the sync header, the changed
|
|
1920
|
-
* files on the left, every diff stacked on the right, and one footer that
|
|
1921
|
-
* sends every comment as a single message.
|
|
1922
|
-
*
|
|
1923
|
-
* DESIGN PATTERNS:
|
|
1924
|
-
* - Like a pull request's file view. Picking a file in the list scrolls to it;
|
|
1925
|
-
* each file's header stays pinned while its diff scrolls past.
|
|
1926
|
-
* - Only nearby file bodies mount and load; distant files retain their headers
|
|
1927
|
-
* and measured-height placeholders.
|
|
1928
|
-
* - A comment is drawn under the line it is about, on the side it is about.
|
|
1929
|
-
* The open draft sits in the same place, so writing happens in context.
|
|
1930
|
-
* - Only UI state lives here (the open draft, which files are collapsed, which
|
|
1931
|
-
* is active). Data and comments come in as props; the panel owns fetching.
|
|
1932
|
-
*
|
|
1933
|
-
* AVOID:
|
|
1934
|
-
* - Clearing comments here. The owner clears them once the host took the send.
|
|
1935
|
-
*/
|
|
1936
|
-
const PICK_ALIGNMENT_MS = 300;
|
|
1937
|
-
const ESTIMATED_ROW_HEIGHT = 21;
|
|
1938
|
-
const MAX_DIFF_ROWS = 5e3;
|
|
1939
2145
|
const NO_FILES = [];
|
|
1940
|
-
const NO_PATHS = /* @__PURE__ */ new Set();
|
|
1941
2146
|
/** Stable, URL-safe element id for one file's section. */
|
|
1942
2147
|
function sectionId(index) {
|
|
1943
2148
|
return `git-review-file-${String(index)}`;
|
|
@@ -1945,122 +2150,30 @@ function sectionId(index) {
|
|
|
1945
2150
|
function sideOf(row) {
|
|
1946
2151
|
return row.marker === "-" ? "old" : "new";
|
|
1947
2152
|
}
|
|
1948
|
-
function GitReviewView({ sync, summary, files, comments, onLoadFile, onAddComment, onRemoveComment, onSendReview, onDiscard, onDraftChange, sendError, initialDraft }) {
|
|
2153
|
+
function GitReviewView({ sync, summary, files, comments, onLoadFile, onAddComment, onRemoveComment, onSendReview, onDiscard, onDraftChange, sendError, initialDraft, initialPath }) {
|
|
1949
2154
|
const entries = summary.state === "ready" ? summary.summary.files : NO_FILES;
|
|
1950
|
-
const [
|
|
1951
|
-
const [picked, setPicked] = useState();
|
|
1952
|
-
const [navigation, setNavigation] = useState();
|
|
1953
|
-
const [proximity, setProximity] = useState({
|
|
1954
|
-
entries: NO_FILES,
|
|
1955
|
-
paths: NO_PATHS
|
|
1956
|
-
});
|
|
1957
|
-
const near = proximity.entries === entries ? proximity.paths : NO_PATHS;
|
|
1958
|
-
const paneRef = useRef(null);
|
|
1959
|
-
const [heights, setHeights] = useState(/* @__PURE__ */ new Map());
|
|
1960
|
-
const pickedAt = useRef(0);
|
|
2155
|
+
const [picked, setPicked] = useState(initialPath ?? initialDraft?.path);
|
|
1961
2156
|
const activePath = entries.some((entry) => entry.path === picked) ? picked : entries[0]?.path;
|
|
2157
|
+
const index = entries.findIndex((entry) => entry.path === activePath);
|
|
2158
|
+
const file = entries[index];
|
|
1962
2159
|
const [draft, setDraft] = useState(initialDraft);
|
|
1963
2160
|
useEffect(() => {
|
|
1964
|
-
|
|
1965
|
-
if (pane === null || typeof IntersectionObserver === "undefined") return;
|
|
1966
|
-
setHeights(/* @__PURE__ */ new Map());
|
|
1967
|
-
let observer;
|
|
1968
|
-
const observe = () => {
|
|
1969
|
-
observer?.disconnect();
|
|
1970
|
-
observer = new IntersectionObserver((changes) => {
|
|
1971
|
-
for (const change of changes) {
|
|
1972
|
-
if (change.isIntersecting) continue;
|
|
1973
|
-
const section = change.target;
|
|
1974
|
-
const path = section.dataset.reviewPath;
|
|
1975
|
-
const body = section.querySelector("[data-review-body=\"measured\"]");
|
|
1976
|
-
if (path !== void 0 && body !== null) {
|
|
1977
|
-
const height = body.offsetHeight;
|
|
1978
|
-
setHeights((measured) => new Map(measured).set(path, height));
|
|
1979
|
-
}
|
|
1980
|
-
}
|
|
1981
|
-
setProximity((current) => {
|
|
1982
|
-
const next = new Set(current.entries === entries ? current.paths : NO_PATHS);
|
|
1983
|
-
for (const change of changes) {
|
|
1984
|
-
const path = change.target.dataset.reviewPath;
|
|
1985
|
-
if (path === void 0) continue;
|
|
1986
|
-
if (change.isIntersecting) next.add(path);
|
|
1987
|
-
else next.delete(path);
|
|
1988
|
-
}
|
|
1989
|
-
return {
|
|
1990
|
-
entries,
|
|
1991
|
-
paths: next
|
|
1992
|
-
};
|
|
1993
|
-
});
|
|
1994
|
-
}, {
|
|
1995
|
-
root: pane,
|
|
1996
|
-
rootMargin: `${String(pane.clientHeight)}px 0px`
|
|
1997
|
-
});
|
|
1998
|
-
for (const section of pane.querySelectorAll("[data-review-path]")) observer.observe(section);
|
|
1999
|
-
};
|
|
2000
|
-
observe();
|
|
2001
|
-
const resize = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(observe);
|
|
2002
|
-
resize?.observe(pane);
|
|
2003
|
-
let frame = 0;
|
|
2004
|
-
const scroll = () => {
|
|
2005
|
-
cancelAnimationFrame(frame);
|
|
2006
|
-
frame = requestAnimationFrame(() => {
|
|
2007
|
-
if (Date.now() - pickedAt.current < PICK_ALIGNMENT_MS) return;
|
|
2008
|
-
const rect = pane.getBoundingClientRect();
|
|
2009
|
-
const section = document.elementFromPoint(rect.left + 8, rect.top + 1)?.closest("[data-review-path]");
|
|
2010
|
-
if (section !== null && section !== void 0 && pane.contains(section)) setPicked(section.dataset.reviewPath);
|
|
2011
|
-
});
|
|
2012
|
-
};
|
|
2013
|
-
pane.addEventListener("scroll", scroll, { passive: true });
|
|
2014
|
-
return () => {
|
|
2015
|
-
observer.disconnect();
|
|
2016
|
-
resize?.disconnect();
|
|
2017
|
-
pane.removeEventListener("scroll", scroll);
|
|
2018
|
-
cancelAnimationFrame(frame);
|
|
2019
|
-
};
|
|
2020
|
-
}, [entries]);
|
|
2021
|
-
useLayoutEffect(() => {
|
|
2022
|
-
if (navigation === void 0) return;
|
|
2023
|
-
[...paneRef.current?.querySelectorAll("[data-review-path]") ?? []].find((element) => element.dataset.reviewPath === navigation)?.scrollIntoView({ block: "start" });
|
|
2024
|
-
}, [navigation, openOverride]);
|
|
2025
|
-
useEffect(() => {
|
|
2026
|
-
if (navigation === void 0) return;
|
|
2027
|
-
const timeout = setTimeout(() => setNavigation(void 0), PICK_ALIGNMENT_MS);
|
|
2028
|
-
return () => clearTimeout(timeout);
|
|
2029
|
-
}, [navigation]);
|
|
2030
|
-
useEffect(() => {
|
|
2031
|
-
for (const file of entries) if ((openOverride[file.path] ?? true) && (near.has(file.path) || navigation === file.path || draft?.path === file.path || comments.some((comment) => comment.path === file.path)) && files[file.path] === void 0) onLoadFile(file.path);
|
|
2161
|
+
if (activePath !== void 0 && files[activePath] === void 0) onLoadFile(activePath);
|
|
2032
2162
|
}, [
|
|
2033
|
-
|
|
2163
|
+
activePath,
|
|
2034
2164
|
files,
|
|
2035
|
-
near,
|
|
2036
|
-
navigation,
|
|
2037
|
-
draft,
|
|
2038
|
-
comments,
|
|
2039
|
-
openOverride,
|
|
2040
2165
|
onLoadFile
|
|
2041
2166
|
]);
|
|
2042
2167
|
const commentCounts = {};
|
|
2043
2168
|
for (const comment of comments) commentCounts[comment.path] = (commentCounts[comment.path] ?? 0) + 1;
|
|
2044
|
-
const isOpen = (path) => openOverride[path] ?? true;
|
|
2045
|
-
const open = (path) => {
|
|
2046
|
-
setOpenOverride((current) => current[path] === true ? current : {
|
|
2047
|
-
...current,
|
|
2048
|
-
[path]: true
|
|
2049
|
-
});
|
|
2050
|
-
};
|
|
2051
2169
|
const pick = (path) => {
|
|
2052
2170
|
if (!entries.some((entry) => entry.path === path)) return;
|
|
2053
|
-
|
|
2171
|
+
if (path !== activePath && draft !== void 0) {
|
|
2172
|
+
if (!window.confirm("Discard the unsent comment and open another file?")) return;
|
|
2173
|
+
setDraft(void 0);
|
|
2174
|
+
onDraftChange?.(false);
|
|
2175
|
+
}
|
|
2054
2176
|
setPicked(path);
|
|
2055
|
-
setNavigation(path);
|
|
2056
|
-
open(path);
|
|
2057
|
-
};
|
|
2058
|
-
const toggle = (path) => {
|
|
2059
|
-
if (isOpen(path)) setOpenOverride((current) => ({
|
|
2060
|
-
...current,
|
|
2061
|
-
[path]: false
|
|
2062
|
-
}));
|
|
2063
|
-
else open(path);
|
|
2064
2177
|
};
|
|
2065
2178
|
const submitDraft = (body) => {
|
|
2066
2179
|
if (draft === void 0) return;
|
|
@@ -2082,87 +2195,116 @@ function GitReviewView({ sync, summary, files, comments, onLoadFile, onAddCommen
|
|
|
2082
2195
|
children: [
|
|
2083
2196
|
/* @__PURE__ */ jsx(GitSyncBar, {
|
|
2084
2197
|
...sync,
|
|
2198
|
+
secondary: comments.length > 0,
|
|
2085
2199
|
...summary.state === "ready" && summary.summary.mergeBase !== void 0 ? { mergeBase: summary.summary.mergeBase } : {}
|
|
2086
2200
|
}),
|
|
2087
2201
|
summary.state === "loading" ? /* @__PURE__ */ jsxs("div", {
|
|
2088
2202
|
"data-testid": "git-review-loading",
|
|
2089
|
-
className: "flex flex-1 items-
|
|
2203
|
+
className: "flex min-h-48 flex-1 items-center justify-center gap-2 text-sm text-po-dim",
|
|
2090
2204
|
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
2091
|
-
className: "h-
|
|
2092
|
-
label: "
|
|
2205
|
+
className: "h-4 w-4 text-po-dim",
|
|
2206
|
+
label: "Reading changes"
|
|
2093
2207
|
}), /* @__PURE__ */ jsx("span", {
|
|
2094
2208
|
className: "text-sm text-po-dim",
|
|
2095
|
-
children: "
|
|
2209
|
+
children: "Reading changes..."
|
|
2096
2210
|
})]
|
|
2097
|
-
}) : summary.state === "error" ? /* @__PURE__ */ jsx(
|
|
2098
|
-
role: "alert",
|
|
2211
|
+
}) : summary.state === "error" ? /* @__PURE__ */ jsx(EmptyState, {
|
|
2099
2212
|
"data-testid": "git-review-error",
|
|
2100
|
-
className: "
|
|
2101
|
-
|
|
2213
|
+
className: "min-h-48 text-po-red",
|
|
2214
|
+
title: /* @__PURE__ */ jsx("span", {
|
|
2215
|
+
className: "text-po-red",
|
|
2216
|
+
children: "Changes could not be read"
|
|
2217
|
+
}),
|
|
2218
|
+
description: summary.error
|
|
2102
2219
|
}) : !summary.summary.repository ? /* @__PURE__ */ jsx(EmptyState, {
|
|
2103
2220
|
"data-testid": "git-review-no-repository",
|
|
2104
|
-
className: "
|
|
2105
|
-
title: "
|
|
2106
|
-
description: "
|
|
2221
|
+
className: "min-h-48",
|
|
2222
|
+
title: "Not a Git checkout",
|
|
2223
|
+
description: "This session's folder is not inside a Git repository."
|
|
2107
2224
|
}) : entries.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
|
|
2108
2225
|
"data-testid": "git-review-empty",
|
|
2109
|
-
className: "
|
|
2110
|
-
title: "
|
|
2111
|
-
description: summary.summary.base === void 0 ? "
|
|
2226
|
+
className: "min-h-48",
|
|
2227
|
+
title: "No changes yet",
|
|
2228
|
+
description: summary.summary.base === void 0 ? "Nothing uncommitted in this checkout." : `Nothing differs from ${summary.summary.base}, committed or not.`
|
|
2112
2229
|
}) : /* @__PURE__ */ jsxs("div", {
|
|
2230
|
+
"data-testid": "git-review-content",
|
|
2113
2231
|
className: "flex min-h-0 flex-1",
|
|
2114
2232
|
children: [/* @__PURE__ */ jsx(GitReviewBrowser, {
|
|
2115
2233
|
files: entries,
|
|
2116
2234
|
commentCounts,
|
|
2117
2235
|
onPick: pick,
|
|
2118
2236
|
...activePath === void 0 ? {} : { activePath }
|
|
2119
|
-
}), /* @__PURE__ */ jsxs("
|
|
2120
|
-
ref: paneRef,
|
|
2237
|
+
}), /* @__PURE__ */ jsxs("main", {
|
|
2121
2238
|
"data-testid": "git-review-diffs",
|
|
2122
2239
|
className: "min-h-0 min-w-0 flex-1 overflow-y-auto",
|
|
2123
|
-
children: [
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2240
|
+
children: [
|
|
2241
|
+
summary.summary.truncated ? /* @__PURE__ */ jsx("p", {
|
|
2242
|
+
className: "px-4 pt-2 text-sm text-po-dim",
|
|
2243
|
+
children: "More files changed than the review lists; the rest are left out."
|
|
2244
|
+
}) : null,
|
|
2245
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2246
|
+
className: "flex items-center justify-between border-b border-po-border-soft px-4 py-2",
|
|
2247
|
+
children: [
|
|
2248
|
+
/* @__PURE__ */ jsx(Button, {
|
|
2249
|
+
variant: "ghost",
|
|
2250
|
+
size: "sm",
|
|
2251
|
+
disabled: index <= 0,
|
|
2252
|
+
onClick: () => pick(entries[index - 1].path),
|
|
2253
|
+
children: "Previous file"
|
|
2254
|
+
}),
|
|
2255
|
+
/* @__PURE__ */ jsxs("span", {
|
|
2256
|
+
className: "text-sm text-po-dim",
|
|
2257
|
+
children: [
|
|
2258
|
+
index + 1,
|
|
2259
|
+
" of ",
|
|
2260
|
+
entries.length
|
|
2261
|
+
]
|
|
2262
|
+
}),
|
|
2263
|
+
/* @__PURE__ */ jsx(Button, {
|
|
2264
|
+
variant: "ghost",
|
|
2265
|
+
size: "sm",
|
|
2266
|
+
disabled: index >= entries.length - 1,
|
|
2267
|
+
onClick: () => pick(entries[index + 1].path),
|
|
2268
|
+
children: "Next file"
|
|
2269
|
+
})
|
|
2270
|
+
]
|
|
2271
|
+
}),
|
|
2272
|
+
file === void 0 ? null : /* @__PURE__ */ jsx(FileSection, {
|
|
2273
|
+
id: sectionId(index),
|
|
2274
|
+
entry: file,
|
|
2275
|
+
state: files[file.path],
|
|
2276
|
+
comments: comments.filter((comment) => comment.path === file.path),
|
|
2277
|
+
draft: draft?.path === file.path ? draft.selection : void 0,
|
|
2278
|
+
onSelect: (selection) => {
|
|
2279
|
+
setDraft({
|
|
2280
|
+
path: file.path,
|
|
2281
|
+
selection
|
|
2282
|
+
});
|
|
2283
|
+
onDraftChange?.(true);
|
|
2284
|
+
},
|
|
2285
|
+
onSubmitDraft: submitDraft,
|
|
2286
|
+
onCancelDraft: () => {
|
|
2287
|
+
setDraft(void 0);
|
|
2288
|
+
onDraftChange?.(false);
|
|
2289
|
+
},
|
|
2290
|
+
onRemoveComment,
|
|
2291
|
+
onRetry: () => onLoadFile(file.path)
|
|
2292
|
+
}, file.path)
|
|
2293
|
+
]
|
|
2152
2294
|
})]
|
|
2153
2295
|
}),
|
|
2154
2296
|
/* @__PURE__ */ jsxs("div", {
|
|
2155
2297
|
"data-testid": "git-review-footer",
|
|
2156
|
-
className: "flex shrink-0 flex-wrap items-center gap-
|
|
2298
|
+
className: "flex shrink-0 flex-wrap items-center gap-3 border-t border-po-border-soft bg-po-panel px-4 py-3",
|
|
2157
2299
|
children: [
|
|
2158
2300
|
/* @__PURE__ */ jsx(MessageIcon, {
|
|
2159
2301
|
"aria-hidden": true,
|
|
2160
|
-
className: "h-
|
|
2302
|
+
className: "h-4 w-4 text-po-blue"
|
|
2161
2303
|
}),
|
|
2162
2304
|
/* @__PURE__ */ jsx("span", {
|
|
2163
2305
|
"data-testid": "git-review-comment-count",
|
|
2164
2306
|
className: "text-sm text-po-dim",
|
|
2165
|
-
children: comments.length === 0 ? "
|
|
2307
|
+
children: comments.length === 0 ? "No comments yet: select lines or click a line number" : `${String(comments.length)} ${comments.length === 1 ? "comment" : "comments"} queued`
|
|
2166
2308
|
}),
|
|
2167
2309
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
|
|
2168
2310
|
sendError === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
@@ -2173,11 +2315,11 @@ function GitReviewView({ sync, summary, files, comments, onLoadFile, onAddCommen
|
|
|
2173
2315
|
}),
|
|
2174
2316
|
/* @__PURE__ */ jsx(Button, {
|
|
2175
2317
|
variant: "ghost",
|
|
2176
|
-
size: "
|
|
2318
|
+
size: "sm",
|
|
2177
2319
|
"data-testid": "git-review-discard",
|
|
2178
2320
|
disabled: comments.length === 0,
|
|
2179
2321
|
onClick: onDiscard,
|
|
2180
|
-
children: "
|
|
2322
|
+
children: "Discard"
|
|
2181
2323
|
}),
|
|
2182
2324
|
/* @__PURE__ */ jsx(Button, {
|
|
2183
2325
|
variant: "primary",
|
|
@@ -2185,7 +2327,7 @@ function GitReviewView({ sync, summary, files, comments, onLoadFile, onAddCommen
|
|
|
2185
2327
|
"data-testid": "git-review-send",
|
|
2186
2328
|
disabled: comments.length === 0,
|
|
2187
2329
|
onClick: onSendReview,
|
|
2188
|
-
children: "
|
|
2330
|
+
children: "Send review"
|
|
2189
2331
|
})
|
|
2190
2332
|
]
|
|
2191
2333
|
})
|
|
@@ -2193,34 +2335,34 @@ function GitReviewView({ sync, summary, files, comments, onLoadFile, onAddCommen
|
|
|
2193
2335
|
});
|
|
2194
2336
|
}
|
|
2195
2337
|
const STATUS_WORD = {
|
|
2196
|
-
added: "
|
|
2197
|
-
modified: "
|
|
2198
|
-
deleted: "
|
|
2199
|
-
untracked: "
|
|
2200
|
-
conflicted: "
|
|
2338
|
+
added: "Added",
|
|
2339
|
+
modified: "Modified",
|
|
2340
|
+
deleted: "Deleted",
|
|
2341
|
+
untracked: "Untracked",
|
|
2342
|
+
conflicted: "Conflicted"
|
|
2201
2343
|
};
|
|
2202
2344
|
/** One file: a pinned header, then its diff with comments drawn under the lines they are about. */
|
|
2203
|
-
function FileSection({ id, entry, state,
|
|
2345
|
+
function FileSection({ id, entry, state, comments, draft, onSelect, onSubmitDraft, onCancelDraft, onRemoveComment, onRetry }) {
|
|
2204
2346
|
const renderAfterRow = (row) => {
|
|
2205
2347
|
const side = sideOf(row);
|
|
2206
2348
|
const here = comments.filter((comment) => (comment.side ?? "new") === side && (comment.endLine ?? comment.startLine) === row.line);
|
|
2207
2349
|
const drafting = draft !== void 0 && draft.side === side && draft.endLine === row.line;
|
|
2208
2350
|
if (here.length === 0 && !drafting) return null;
|
|
2209
2351
|
return /* @__PURE__ */ jsxs("div", {
|
|
2210
|
-
className: "
|
|
2352
|
+
className: "font-sans whitespace-normal",
|
|
2211
2353
|
children: [here.map((comment) => /* @__PURE__ */ jsxs("div", {
|
|
2212
2354
|
"data-testid": `git-review-comment-${comment.id}`,
|
|
2213
|
-
className: "flex
|
|
2355
|
+
className: "m-3 flex items-start gap-3 rounded-md border border-po-border-soft bg-po-panel p-3 font-sans whitespace-normal",
|
|
2214
2356
|
children: [
|
|
2215
2357
|
/* @__PURE__ */ jsx(MessageIcon, {
|
|
2216
2358
|
"aria-hidden": true,
|
|
2217
|
-
className: "mt-
|
|
2359
|
+
className: "mt-1 h-4 w-4 shrink-0 text-po-blue"
|
|
2218
2360
|
}),
|
|
2219
2361
|
/* @__PURE__ */ jsxs("span", {
|
|
2220
|
-
className: "flex min-w-0 flex-1 flex-col
|
|
2221
|
-
children: [/* @__PURE__ */
|
|
2222
|
-
className: "
|
|
2223
|
-
children:
|
|
2362
|
+
className: "flex min-w-0 flex-1 flex-col",
|
|
2363
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
2364
|
+
className: "mb-1 text-sm text-po-dim",
|
|
2365
|
+
children: ["You · line ", comment.endLine ?? comment.startLine]
|
|
2224
2366
|
}), /* @__PURE__ */ jsx("span", {
|
|
2225
2367
|
className: "text-base whitespace-pre-wrap text-po-text",
|
|
2226
2368
|
children: comment.body
|
|
@@ -2228,15 +2370,15 @@ function FileSection({ id, entry, state, collapsed, mounted, placeholderHeight,
|
|
|
2228
2370
|
}),
|
|
2229
2371
|
/* @__PURE__ */ jsx(Button, {
|
|
2230
2372
|
variant: "ghost",
|
|
2231
|
-
size: "
|
|
2232
|
-
"aria-label": "
|
|
2233
|
-
title: "
|
|
2373
|
+
size: "sm",
|
|
2374
|
+
"aria-label": "Remove comment",
|
|
2375
|
+
title: "Remove comment",
|
|
2234
2376
|
onClick: () => onRemoveComment(comment.id),
|
|
2235
|
-
children:
|
|
2377
|
+
children: "Remove"
|
|
2236
2378
|
})
|
|
2237
2379
|
]
|
|
2238
2380
|
}, comment.id)), drafting ? /* @__PURE__ */ jsx("div", {
|
|
2239
|
-
className: "rounded-md border border-po-border bg-po-panel",
|
|
2381
|
+
className: "m-3 rounded-md border border-po-border-soft bg-po-panel font-sans whitespace-normal [&_pre]:text-sm [&_p]:first-letter:uppercase",
|
|
2240
2382
|
children: /* @__PURE__ */ jsx(ReviewCommentDraft, {
|
|
2241
2383
|
snippet: draft.snippet,
|
|
2242
2384
|
startLine: draft.startLine,
|
|
@@ -2253,90 +2395,73 @@ function FileSection({ id, entry, state, collapsed, mounted, placeholderHeight,
|
|
|
2253
2395
|
id,
|
|
2254
2396
|
"data-review-path": entry.path,
|
|
2255
2397
|
"data-testid": `git-review-file-${entry.path}`,
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
variant: "ghost",
|
|
2259
|
-
size: "card",
|
|
2260
|
-
"aria-expanded": !collapsed,
|
|
2261
|
-
onClick: onToggle,
|
|
2262
|
-
className: "sticky top-0 z-10 flex-row items-center gap-1.5 rounded-none border-b border-po-border-soft bg-po-rail px-3 py-1.5 hover:bg-po-panel sm:px-4",
|
|
2398
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2399
|
+
className: "flex flex-wrap items-center gap-3 border-b border-po-border-soft bg-po-panel px-4 py-3",
|
|
2263
2400
|
children: [
|
|
2264
|
-
collapsed ? /* @__PURE__ */ jsx(ChevronRightIcon, {
|
|
2265
|
-
"aria-hidden": true,
|
|
2266
|
-
className: "h-3 w-3 shrink-0 text-po-faint"
|
|
2267
|
-
}) : /* @__PURE__ */ jsx(ChevronDownIcon, {
|
|
2268
|
-
"aria-hidden": true,
|
|
2269
|
-
className: "h-3 w-3 shrink-0 text-po-faint"
|
|
2270
|
-
}),
|
|
2271
2401
|
/* @__PURE__ */ jsx("span", {
|
|
2272
|
-
className: "min-w-0 flex-1 truncate font-mono text-base
|
|
2402
|
+
className: "min-w-0 flex-1 truncate font-mono text-base text-po-hi",
|
|
2273
2403
|
children: entry.path
|
|
2274
2404
|
}),
|
|
2275
2405
|
/* @__PURE__ */ jsx("span", {
|
|
2276
|
-
className: "shrink-0 text-sm
|
|
2406
|
+
className: "shrink-0 text-sm text-po-dim",
|
|
2277
2407
|
children: STATUS_WORD[entry.status]
|
|
2278
2408
|
}),
|
|
2409
|
+
entry.binary ? null : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("span", {
|
|
2410
|
+
className: "text-sm text-po-green",
|
|
2411
|
+
children: ["+", entry.added]
|
|
2412
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
2413
|
+
className: "text-sm text-po-red",
|
|
2414
|
+
children: ["-", entry.removed]
|
|
2415
|
+
})] }),
|
|
2279
2416
|
comments.length === 0 ? null : /* @__PURE__ */ jsxs("span", {
|
|
2280
|
-
className: "flex shrink-0 items-center gap-
|
|
2281
|
-
children: [/* @__PURE__ */ jsx(MessageIcon, {
|
|
2282
|
-
"aria-hidden": true,
|
|
2283
|
-
className: "h-2.5 w-2.5"
|
|
2284
|
-
}), comments.length]
|
|
2285
|
-
}),
|
|
2286
|
-
entry.binary ? null : /* @__PURE__ */ jsxs("span", {
|
|
2287
|
-
className: "shrink-0 text-2xs",
|
|
2417
|
+
className: "flex shrink-0 items-center gap-1 text-sm text-po-blue",
|
|
2288
2418
|
children: [
|
|
2289
|
-
/* @__PURE__ */
|
|
2290
|
-
|
|
2291
|
-
|
|
2419
|
+
/* @__PURE__ */ jsx(MessageIcon, {
|
|
2420
|
+
"aria-hidden": true,
|
|
2421
|
+
className: "h-3 w-3"
|
|
2292
2422
|
}),
|
|
2423
|
+
comments.length,
|
|
2293
2424
|
" ",
|
|
2294
|
-
|
|
2295
|
-
className: "text-po-red",
|
|
2296
|
-
children: ["-", entry.removed]
|
|
2297
|
-
})
|
|
2425
|
+
comments.length === 1 ? "comment" : "comments"
|
|
2298
2426
|
]
|
|
2299
2427
|
})
|
|
2300
2428
|
]
|
|
2301
|
-
}),
|
|
2302
|
-
"aria-hidden": true,
|
|
2303
|
-
style: { height: placeholderHeight }
|
|
2304
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
2429
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2305
2430
|
"data-review-body": state === void 0 || state.state === "loading" ? "loading" : "measured",
|
|
2306
|
-
style: state === void 0 || state.state === "loading" ? { minHeight: placeholderHeight } : void 0,
|
|
2307
2431
|
children: state === void 0 || state.state === "loading" ? /* @__PURE__ */ jsxs("div", {
|
|
2308
2432
|
className: "flex items-center gap-2 px-4 py-3",
|
|
2309
2433
|
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
2310
2434
|
className: "h-3 w-3 text-po-faint",
|
|
2311
|
-
label: `
|
|
2435
|
+
label: `Loading ${entry.path}`
|
|
2312
2436
|
}), /* @__PURE__ */ jsx("span", {
|
|
2313
2437
|
className: "text-sm text-po-dim",
|
|
2314
|
-
children: "
|
|
2438
|
+
children: "Loading diff..."
|
|
2315
2439
|
})]
|
|
2316
2440
|
}) : state.state === "error" ? /* @__PURE__ */ jsxs("div", {
|
|
2317
|
-
className: "flex items-center
|
|
2441
|
+
className: "flex min-h-48 flex-col items-center justify-center gap-3",
|
|
2318
2442
|
children: [/* @__PURE__ */ jsx("span", {
|
|
2319
2443
|
role: "alert",
|
|
2320
|
-
className: "text-
|
|
2444
|
+
className: "text-base text-po-red",
|
|
2321
2445
|
children: state.error
|
|
2322
2446
|
}), /* @__PURE__ */ jsx(Button, {
|
|
2323
|
-
variant: "
|
|
2324
|
-
size: "
|
|
2447
|
+
variant: "outline",
|
|
2448
|
+
size: "sm",
|
|
2325
2449
|
onClick: onRetry,
|
|
2326
|
-
children: "
|
|
2450
|
+
children: "Retry"
|
|
2327
2451
|
})]
|
|
2328
|
-
}) : state.diff.binary ? /* @__PURE__ */ jsx(
|
|
2329
|
-
className: "
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2452
|
+
}) : state.diff.binary ? /* @__PURE__ */ jsx(EmptyState, {
|
|
2453
|
+
className: "min-h-48",
|
|
2454
|
+
title: "Binary file",
|
|
2455
|
+
description: `${entry.path} cannot be shown as a text diff.`
|
|
2456
|
+
}) : state.diff.tooLarge ? /* @__PURE__ */ jsx(EmptyState, {
|
|
2457
|
+
className: "min-h-48",
|
|
2458
|
+
title: "This diff is too large to show",
|
|
2459
|
+
description: "Other changed files are still available to review."
|
|
2334
2460
|
}) : /* @__PURE__ */ jsx(DiffView, {
|
|
2335
2461
|
hunks: state.diff.hunks,
|
|
2336
2462
|
testId: `git-review-diff-${id}`,
|
|
2337
2463
|
onSelect,
|
|
2338
|
-
renderAfterRow
|
|
2339
|
-
className: "py-1"
|
|
2464
|
+
renderAfterRow
|
|
2340
2465
|
})
|
|
2341
2466
|
})]
|
|
2342
2467
|
});
|
|
@@ -2371,6 +2496,7 @@ function reviewTab() {
|
|
|
2371
2496
|
function GitReviewPanel({ sessionId, sendSessionFrame }) {
|
|
2372
2497
|
const activity = useStore(gitChanges.store, (state) => gitChanges.select(state, sessionId));
|
|
2373
2498
|
const comments = useStore(gitReview.store, (state) => gitReview.select(state, sessionId).comments);
|
|
2499
|
+
const focus = useStore(gitReview.store, (state) => gitReview.select(state, sessionId).focus);
|
|
2374
2500
|
const [summary, setSummary] = useState({ state: "loading" });
|
|
2375
2501
|
const [files, setFiles] = useState({});
|
|
2376
2502
|
const [sendError, setSendError] = useState();
|
|
@@ -2465,6 +2591,7 @@ function GitReviewPanel({ sessionId, sendSessionFrame }) {
|
|
|
2465
2591
|
if (failure === void 0) onSent?.();
|
|
2466
2592
|
};
|
|
2467
2593
|
return /* @__PURE__ */ jsx(GitReviewView, {
|
|
2594
|
+
...focus === void 0 ? {} : { initialPath: focus.path },
|
|
2468
2595
|
onDraftChange: (open) => setDraftSnapshot((current) => open ? current?.sessionId === sessionId ? current : {
|
|
2469
2596
|
sessionId,
|
|
2470
2597
|
fingerprint
|
|
@@ -2474,7 +2601,10 @@ function GitReviewPanel({ sessionId, sendSessionFrame }) {
|
|
|
2474
2601
|
...activity.pending === void 0 ? {} : { pending: activity.pending },
|
|
2475
2602
|
...error === void 0 ? {} : { error },
|
|
2476
2603
|
...activity.errorTarget === void 0 || error === void 0 ? {} : { errorTarget: activity.errorTarget },
|
|
2477
|
-
onSync: (action) =>
|
|
2604
|
+
onSync: (action) => {
|
|
2605
|
+
if (action === "abort-rebase" && !window.confirm("Abort rebase? This discards conflict resolution in progress.")) return;
|
|
2606
|
+
requestGitSync(sessionId, action);
|
|
2607
|
+
},
|
|
2478
2608
|
onForcePush: () => requestGitSync(sessionId, "push", true),
|
|
2479
2609
|
onAskAgent: () => send(conflictPrompt(branch, changes?.rebase?.conflicts ?? [])),
|
|
2480
2610
|
onDismissError: () => setDismissedError(activity.error)
|
|
@@ -2488,16 +2618,40 @@ function GitReviewPanel({ sessionId, sendSessionFrame }) {
|
|
|
2488
2618
|
onSendReview: () => send(buildReviewPrompt(comments, reviewHeading(comments.length, branch, base)), () => clearReviewComments(sessionId)),
|
|
2489
2619
|
onDiscard: () => clearReviewComments(sessionId),
|
|
2490
2620
|
...sendError === void 0 ? {} : { sendError }
|
|
2491
|
-
}, sessionId);
|
|
2621
|
+
}, `${sessionId}:${String(focus?.at ?? 0)}`);
|
|
2492
2622
|
}
|
|
2493
2623
|
//#endregion
|
|
2624
|
+
//#region src/extensions/workspaces/sessions/(frontend)/fill/GitReview.vibe-lint.finding-review.web.ts
|
|
2625
|
+
/** Lets code review findings open this checkout's diff review on their file. */
|
|
2626
|
+
var GitReview_vibe_lint_finding_review_web_default = defineFill({
|
|
2627
|
+
slot: "vibe-lint.finding-review",
|
|
2628
|
+
id: "git-review",
|
|
2629
|
+
data: {
|
|
2630
|
+
version: 1,
|
|
2631
|
+
label: "Open in review",
|
|
2632
|
+
open: (props, path) => {
|
|
2633
|
+
if (props.sessionId === null) return;
|
|
2634
|
+
focusReviewFile(props.sessionId, path);
|
|
2635
|
+
props.openTransientTab(reviewTab());
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
});
|
|
2639
|
+
//#endregion
|
|
2640
|
+
//#region src/extensions/workspaces/sessions/(frontend)/fill/Worktree.session-menu.web.tsx
|
|
2641
|
+
var Worktree_session_menu_web_default = defineFill({ component: WorktreeSessionMenu });
|
|
2642
|
+
//#endregion
|
|
2643
|
+
//#region src/extensions/workspaces/sessions/(frontend)/fill/WorktreeCreate.overlay.web.tsx
|
|
2644
|
+
var WorktreeCreate_overlay_web_default = defineFill({ component: WorktreeCreateOverlay });
|
|
2645
|
+
//#endregion
|
|
2494
2646
|
//#region src/extensions/workspaces/sessions/(frontend)/fill/_components/DiffActivitySection.tsx
|
|
2495
2647
|
/**
|
|
2496
2648
|
* The '# git' group's branch row: this session's code change against its base.
|
|
2497
2649
|
*
|
|
2498
2650
|
* DESIGN PATTERNS:
|
|
2499
|
-
* - One lane, branch and `+A -R
|
|
2500
|
-
*
|
|
2651
|
+
* - One lane, branch and `+A -R`, straight on the group body like every other
|
|
2652
|
+
* activity group: the dock already draws the card, so no inner card or heading.
|
|
2653
|
+
* Files, base, upstream and a paused rebase live in the review tab's header,
|
|
2654
|
+
* which this lane opens.
|
|
2501
2655
|
* - Stays clickable at zero, because the review tab is also where pull, push
|
|
2502
2656
|
* and rebase live.
|
|
2503
2657
|
* - Reads the focused session's checkout, whether it is a worktree or not.
|
|
@@ -2509,128 +2663,112 @@ function DiffActivitySection({ sessionId, openTransientTab }) {
|
|
|
2509
2663
|
const session = useStore(gitChanges.store, (state) => gitChanges.select(state, sessionId));
|
|
2510
2664
|
const changes = session.changes;
|
|
2511
2665
|
if (sessionId === null || changes === void 0) return null;
|
|
2512
|
-
|
|
2513
|
-
|
|
2666
|
+
return /* @__PURE__ */ jsx(DiffActivityView, {
|
|
2667
|
+
changes,
|
|
2668
|
+
pending: session.pending,
|
|
2669
|
+
onReview: () => openTransientTab(reviewTab())
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2672
|
+
function DiffActivityView({ changes, pending, onReview, branch }) {
|
|
2673
|
+
const zero = changes?.added === 0 && changes?.removed === 0;
|
|
2674
|
+
const against = changes?.base === void 0 ? "uncommitted" : `vs ${changes.base}`;
|
|
2514
2675
|
return /* @__PURE__ */ jsxs("div", {
|
|
2515
|
-
className: "flex min-w-0
|
|
2676
|
+
className: "flex min-w-0 flex-col gap-1",
|
|
2516
2677
|
children: [/* @__PURE__ */ jsxs(Button, {
|
|
2517
2678
|
variant: "ghost",
|
|
2518
|
-
size: "
|
|
2679
|
+
size: "card",
|
|
2519
2680
|
"data-testid": "activity-diff-changes",
|
|
2520
|
-
"
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
onClick:
|
|
2524
|
-
children: [
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2681
|
+
className: "min-w-0 flex-row items-center gap-2 rounded-md px-1 py-1 font-normal hover:bg-po-panel",
|
|
2682
|
+
"aria-label": changes ? `review ${changes.branch ?? "detached HEAD"} changes: ${changes.added} added, ${changes.removed} removed ${against}` : "Changes not reported yet",
|
|
2683
|
+
title: changes ? `${changes.files} ${changes.files === 1 ? "file" : "files"} ${against}` : void 0,
|
|
2684
|
+
onClick: onReview,
|
|
2685
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
2686
|
+
className: "min-w-0 flex-1 truncate text-left font-mono text-sm text-po-hi",
|
|
2687
|
+
children: changes?.branch ?? branch ?? "detached HEAD"
|
|
2688
|
+
}), !changes || zero ? /* @__PURE__ */ jsx("span", {
|
|
2689
|
+
className: "shrink-0 text-sm text-po-dim",
|
|
2690
|
+
children: zero ? "No changes yet" : "Not reported yet"
|
|
2691
|
+
}) : /* @__PURE__ */ jsxs("span", {
|
|
2692
|
+
className: "flex shrink-0 gap-2 text-sm",
|
|
2693
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
2694
|
+
className: "text-po-green",
|
|
2531
2695
|
children: ["+", changes.added]
|
|
2532
|
-
}),
|
|
2533
|
-
|
|
2534
|
-
className: zero ? "" : "text-po-red",
|
|
2696
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
2697
|
+
className: "text-po-red",
|
|
2535
2698
|
children: ["-", changes.removed]
|
|
2536
|
-
})
|
|
2537
|
-
]
|
|
2538
|
-
}),
|
|
2539
|
-
className: "flex
|
|
2699
|
+
})]
|
|
2700
|
+
})]
|
|
2701
|
+
}), pending && /* @__PURE__ */ jsxs("p", {
|
|
2702
|
+
className: "flex items-center gap-2 px-1 text-sm text-po-dim",
|
|
2540
2703
|
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
2541
|
-
className: "
|
|
2542
|
-
label:
|
|
2704
|
+
className: "size-3",
|
|
2705
|
+
label: pending
|
|
2543
2706
|
}), /* @__PURE__ */ jsx("span", {
|
|
2544
|
-
className: "
|
|
2545
|
-
children:
|
|
2707
|
+
className: "inline-block first-letter:uppercase",
|
|
2708
|
+
children: pending
|
|
2546
2709
|
})]
|
|
2547
2710
|
})]
|
|
2548
2711
|
});
|
|
2549
2712
|
}
|
|
2550
2713
|
//#endregion
|
|
2551
2714
|
//#region src/extensions/workspaces/sessions/(frontend)/fill/_components/WorktreesActivitySection.tsx
|
|
2552
|
-
/**
|
|
2553
|
-
* The '# git' group's body in the activity dock.
|
|
2554
|
-
*
|
|
2555
|
-
* DESIGN PATTERNS:
|
|
2556
|
-
* - The focused checkout's branch and diff stay visible, even with no child worktrees.
|
|
2557
|
-
* - One line per worktree, the same rule the runners rail follows: enough to
|
|
2558
|
-
* notice it, not enough to work with it. The group's own name opens the tab
|
|
2559
|
-
* that has room.
|
|
2560
|
-
* - Idle still shows the launcher. A repository with no worktrees is the state
|
|
2561
|
-
* where someone most wants to make one.
|
|
2562
|
-
*
|
|
2563
|
-
* AVOID:
|
|
2564
|
-
* - Doing work here. This section reports and launches; the panel and the tool
|
|
2565
|
-
* own everything else.
|
|
2566
|
-
*/
|
|
2567
2715
|
function WorktreesActivitySection(props) {
|
|
2716
|
+
const session = useStore(worktreeActivity.store, (state) => worktreeActivity.select(state, props.sessionId));
|
|
2568
2717
|
return /* @__PURE__ */ jsxs("div", {
|
|
2569
|
-
className: "flex min-w-0 flex-col gap-
|
|
2570
|
-
children: [/* @__PURE__ */ jsx(DiffActivitySection, { ...props }), /* @__PURE__ */ jsx(
|
|
2718
|
+
className: "flex min-w-0 flex-col gap-1",
|
|
2719
|
+
children: [/* @__PURE__ */ jsx(DiffActivitySection, { ...props }), /* @__PURE__ */ jsx(WorktreesActivityView, {
|
|
2720
|
+
worktrees: session.worktrees.filter((w) => w.sessionId !== props.sessionId),
|
|
2721
|
+
pending: session.pending,
|
|
2722
|
+
onOpen: () => props.openTransientTab(worktreesTab()),
|
|
2723
|
+
onCreate: props.sessionId === null ? void 0 : () => worktreeCreateDialog.update(() => ({
|
|
2724
|
+
sessionId: props.sessionId,
|
|
2725
|
+
...props.sessionReservationId === void 0 ? {} : { reservationId: props.sessionReservationId }
|
|
2726
|
+
}))
|
|
2727
|
+
})]
|
|
2571
2728
|
});
|
|
2572
2729
|
}
|
|
2573
|
-
function
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2730
|
+
function WorktreesActivityView({ worktrees, pending, onOpen, onCreate, startingIds = [] }) {
|
|
2731
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2732
|
+
className: "flex min-w-0 flex-col gap-1",
|
|
2733
|
+
children: [pending && /* @__PURE__ */ jsxs("p", {
|
|
2734
|
+
className: "flex items-center gap-2 px-1 text-sm text-po-dim",
|
|
2735
|
+
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
2736
|
+
className: "size-3",
|
|
2737
|
+
label: pending
|
|
2738
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2739
|
+
className: "inline-block first-letter:uppercase",
|
|
2740
|
+
children: pending
|
|
2741
|
+
})]
|
|
2742
|
+
}), worktrees.length === 0 ? onCreate ? /* @__PURE__ */ jsxs(Button, {
|
|
2583
2743
|
variant: "link",
|
|
2584
2744
|
size: "sm",
|
|
2585
2745
|
"data-testid": "activity-git-create",
|
|
2586
|
-
className: "h-auto gap-1 px-
|
|
2587
|
-
onClick:
|
|
2746
|
+
className: "h-auto self-start gap-1 px-1 text-po-green",
|
|
2747
|
+
onClick: onCreate,
|
|
2588
2748
|
children: ["Create a worktree", /* @__PURE__ */ jsx(ArrowRightIcon, {
|
|
2589
2749
|
"aria-hidden": true,
|
|
2590
2750
|
className: "size-3.5"
|
|
2591
2751
|
})]
|
|
2752
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
2753
|
+
"data-testid": "activity-summary-git",
|
|
2754
|
+
className: "px-1 text-sm text-po-dim",
|
|
2755
|
+
children: "Idle"
|
|
2756
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
2757
|
+
"data-testid": "activity-git-worktrees",
|
|
2758
|
+
children: worktrees.map((w) => /* @__PURE__ */ jsx(Button, {
|
|
2759
|
+
variant: "ghost",
|
|
2760
|
+
size: "card",
|
|
2761
|
+
className: "block w-full min-w-0 p-0 text-left font-normal",
|
|
2762
|
+
"data-testid": `activity-git-worktree-${w.id}`,
|
|
2763
|
+
onClick: onOpen,
|
|
2764
|
+
children: /* @__PURE__ */ jsx(WorktreeRow, {
|
|
2765
|
+
compact: true,
|
|
2766
|
+
worktree: w,
|
|
2767
|
+
starting: startingIds.includes(w.id) || !!pending?.includes("starting") && w.sessionId === null && !w.orphaned && !w.unowned
|
|
2768
|
+
})
|
|
2769
|
+
}, w.id))
|
|
2592
2770
|
})]
|
|
2593
2771
|
});
|
|
2594
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
2595
|
-
"data-testid": "activity-git-worktrees",
|
|
2596
|
-
className: "flex flex-col gap-0.5",
|
|
2597
|
-
children: [session.pending === void 0 ? null : /* @__PURE__ */ jsxs("span", {
|
|
2598
|
-
className: "flex min-w-0 items-center gap-1.5 px-1",
|
|
2599
|
-
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
2600
|
-
className: "h-3 w-3 shrink-0 text-po-faint",
|
|
2601
|
-
label: session.pending
|
|
2602
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
2603
|
-
className: "min-w-0 truncate text-sm text-po-dim",
|
|
2604
|
-
children: session.pending
|
|
2605
|
-
})]
|
|
2606
|
-
}), worktrees.map((worktree) => /* @__PURE__ */ jsx(Button, {
|
|
2607
|
-
variant: "ghost",
|
|
2608
|
-
size: "card",
|
|
2609
|
-
"data-testid": `activity-git-worktree-${worktree.id}`,
|
|
2610
|
-
className: "min-w-0 gap-1.5 px-1",
|
|
2611
|
-
onClick: () => openTransientTab(worktreesTab()),
|
|
2612
|
-
children: /* @__PURE__ */ jsxs("span", {
|
|
2613
|
-
className: "flex w-full min-w-0 items-center gap-1.5",
|
|
2614
|
-
children: [
|
|
2615
|
-
/* @__PURE__ */ jsx(Dot, { tone: worktree.orphaned ? "red" : worktree.sessionId === null ? "muted" : "blue" }),
|
|
2616
|
-
/* @__PURE__ */ jsx("span", {
|
|
2617
|
-
className: "min-w-0 flex-1 truncate text-left text-base font-semibold text-po-hi",
|
|
2618
|
-
children: worktree.branch
|
|
2619
|
-
}),
|
|
2620
|
-
worktree.orphaned ? /* @__PURE__ */ jsx(StatusBadge, {
|
|
2621
|
-
tone: "error",
|
|
2622
|
-
size: "xs",
|
|
2623
|
-
children: "orphaned"
|
|
2624
|
-
}) : null,
|
|
2625
|
-
worktree.unowned ? /* @__PURE__ */ jsx(StatusBadge, {
|
|
2626
|
-
tone: "info",
|
|
2627
|
-
size: "xs",
|
|
2628
|
-
children: "unowned"
|
|
2629
|
-
}) : null
|
|
2630
|
-
]
|
|
2631
|
-
})
|
|
2632
|
-
}, worktree.id))]
|
|
2633
|
-
});
|
|
2634
2772
|
}
|
|
2635
2773
|
//#endregion
|
|
2636
2774
|
//#region src/extensions/workspaces/sessions/(frontend)/fill/Worktrees.activity.git.web.tsx
|
|
@@ -2664,49 +2802,162 @@ function startGitRuntime(runtime) {
|
|
|
2664
2802
|
var start_web_default = defineWebLifecycle(startGitRuntime);
|
|
2665
2803
|
//#endregion
|
|
2666
2804
|
//#region src/extensions/workspaces/sessions/(frontend)/tool/_components/RunWorktreeToolMessage.tsx
|
|
2667
|
-
const
|
|
2668
|
-
spawn_worktree:
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2805
|
+
const VERBS = {
|
|
2806
|
+
spawn_worktree: [
|
|
2807
|
+
"is creating",
|
|
2808
|
+
"created",
|
|
2809
|
+
"create"
|
|
2810
|
+
],
|
|
2811
|
+
close_worktree: [
|
|
2812
|
+
"is closing",
|
|
2813
|
+
"closed",
|
|
2814
|
+
"close"
|
|
2815
|
+
],
|
|
2816
|
+
list: [
|
|
2817
|
+
"is listing",
|
|
2818
|
+
"listed",
|
|
2819
|
+
"list"
|
|
2820
|
+
],
|
|
2821
|
+
status: [
|
|
2822
|
+
"is checking",
|
|
2823
|
+
"checked",
|
|
2824
|
+
"check"
|
|
2825
|
+
],
|
|
2826
|
+
merge: [
|
|
2827
|
+
"is merging",
|
|
2828
|
+
"merged",
|
|
2829
|
+
"merge"
|
|
2830
|
+
],
|
|
2831
|
+
prune: [
|
|
2832
|
+
"is pruning",
|
|
2833
|
+
"pruned",
|
|
2834
|
+
"prune"
|
|
2835
|
+
],
|
|
2836
|
+
send: [
|
|
2837
|
+
"is messaging",
|
|
2838
|
+
"messaged",
|
|
2839
|
+
"message"
|
|
2840
|
+
],
|
|
2841
|
+
messages: [
|
|
2842
|
+
"is reading messages from",
|
|
2843
|
+
"read messages from",
|
|
2844
|
+
"read messages from"
|
|
2845
|
+
]
|
|
2676
2846
|
};
|
|
2677
|
-
/**
|
|
2678
|
-
* The timeline item for `run_worktree`.
|
|
2679
|
-
*
|
|
2680
|
-
* DESIGN PATTERNS:
|
|
2681
|
-
* - Built on MessageItem, like every other tool renderer in the cockpit. That
|
|
2682
|
-
* is what gives it the shared collapse toggle and the RUNNING/OK/ERROR badge
|
|
2683
|
-
* rather than a hand-rolled ellipsis that never resolves.
|
|
2684
|
-
* - The action and its subject go in the heading because that is what a reader
|
|
2685
|
-
* scanning a transcript needs: "close_worktree a1b2c3d4" answers the question
|
|
2686
|
-
* the raw JSON arguments make them decode.
|
|
2687
|
-
* - A create takes as long as the cockpit needs to start a session, and the
|
|
2688
|
-
* tool streams each phase into `output`, so the body is where the wait is
|
|
2689
|
-
* visible.
|
|
2690
|
-
*/
|
|
2691
2847
|
function RunWorktreeToolMessage({ args, output, running, isError }) {
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2848
|
+
return /* @__PURE__ */ jsx(RunWorktreeToolMessageView, {
|
|
2849
|
+
action: typeof args.action === "string" ? args.action : "",
|
|
2850
|
+
subject: typeof args.branch === "string" ? args.branch : typeof args.id === "string" ? args.id : "",
|
|
2851
|
+
output,
|
|
2852
|
+
running,
|
|
2853
|
+
isError
|
|
2854
|
+
});
|
|
2855
|
+
}
|
|
2856
|
+
function RunWorktreeToolMessageView({ action, subject = "", output = "", running = false, isError = false, defaultExpanded = false }) {
|
|
2857
|
+
const tone = running ? "neutral" : isError ? "error" : "ok";
|
|
2858
|
+
const verbs = VERBS[action] ?? [
|
|
2859
|
+
"is running",
|
|
2860
|
+
"finished",
|
|
2861
|
+
"run"
|
|
2862
|
+
];
|
|
2863
|
+
const rows = action === "list" ? output.split("\n").map((line) => line.split(/\s{2,}/)).filter((parts) => parts.length >= 3) : [];
|
|
2864
|
+
const listingCount = output.match(/^(\d+) worktree/);
|
|
2865
|
+
return /* @__PURE__ */ jsx(MessageItem, {
|
|
2866
|
+
tone,
|
|
2867
|
+
expandable: true,
|
|
2868
|
+
defaultExpanded,
|
|
2869
|
+
className: "bg-po-panel",
|
|
2870
|
+
children: ({ expanded, toggle }) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2871
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2872
|
+
className: "flex items-start gap-3 p-4",
|
|
2873
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2874
|
+
className: "min-w-0 flex-1",
|
|
2875
|
+
children: /* @__PURE__ */ jsxs("p", {
|
|
2876
|
+
className: "text-base font-semibold leading-relaxed text-po-hi",
|
|
2877
|
+
children: [
|
|
2878
|
+
"Po ",
|
|
2879
|
+
isError && !running ? `could not ${verbs[2]}` : verbs[running ? 0 : 1],
|
|
2880
|
+
" ",
|
|
2881
|
+
action === "list" && !running && !isError ? `${listingCount?.[1] ?? rows.length} ${Number(listingCount?.[1] ?? rows.length) === 1 ? "worktree" : "worktrees"}` : subject ? /* @__PURE__ */ jsx("span", {
|
|
2882
|
+
className: "font-mono",
|
|
2883
|
+
children: subject
|
|
2884
|
+
}) : "worktrees",
|
|
2885
|
+
running ? "..." : isError && output ? `: ${output.split("\n")[0]}` : ""
|
|
2886
|
+
]
|
|
2887
|
+
})
|
|
2888
|
+
}), /* @__PURE__ */ jsxs(StatusBadge, {
|
|
2889
|
+
tone,
|
|
2890
|
+
size: "xs",
|
|
2891
|
+
className: running ? "gap-1 bg-po-rail text-po-dim" : "",
|
|
2892
|
+
children: [running && /* @__PURE__ */ jsx(Spinner, {
|
|
2893
|
+
className: "size-3",
|
|
2894
|
+
label: "Running"
|
|
2895
|
+
}), running ? "Running" : isError ? "Failed" : "Completed"]
|
|
2896
|
+
})]
|
|
2897
|
+
}),
|
|
2898
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
2899
|
+
variant: "ghost",
|
|
2900
|
+
size: "sm",
|
|
2901
|
+
onClick: toggle,
|
|
2902
|
+
"aria-expanded": expanded,
|
|
2903
|
+
className: "mb-2 ml-3 text-po-dim",
|
|
2904
|
+
children: ["Details ", expanded ? /* @__PURE__ */ jsx(ChevronUpIcon, { className: "size-3" }) : /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3" })]
|
|
2905
|
+
}),
|
|
2906
|
+
expanded && /* @__PURE__ */ jsx(MessageItemBody, { children: rows.length > 0 && !isError ? /* @__PURE__ */ jsxs("table", {
|
|
2907
|
+
className: "w-full text-left text-sm",
|
|
2908
|
+
children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
|
|
2909
|
+
className: "border-b border-po-border-soft",
|
|
2910
|
+
children: [
|
|
2911
|
+
/* @__PURE__ */ jsx("th", {
|
|
2912
|
+
className: "pb-2 font-medium",
|
|
2913
|
+
children: "Branch"
|
|
2914
|
+
}),
|
|
2915
|
+
/* @__PURE__ */ jsx("th", {
|
|
2916
|
+
className: "pb-2 font-medium",
|
|
2917
|
+
children: "Path"
|
|
2918
|
+
}),
|
|
2919
|
+
/* @__PURE__ */ jsx("th", {
|
|
2920
|
+
className: "pb-2 font-medium",
|
|
2921
|
+
children: "Status"
|
|
2922
|
+
})
|
|
2923
|
+
]
|
|
2924
|
+
}) }), /* @__PURE__ */ jsx("tbody", { children: rows.map((parts, i) => {
|
|
2925
|
+
const real = parts.length >= 5;
|
|
2926
|
+
const status = parts[2];
|
|
2927
|
+
return /* @__PURE__ */ jsxs("tr", {
|
|
2928
|
+
className: "border-b border-po-border-soft",
|
|
2929
|
+
children: [
|
|
2930
|
+
/* @__PURE__ */ jsxs("td", {
|
|
2931
|
+
className: "py-3 pr-3 font-mono text-po-text",
|
|
2932
|
+
children: [real ? parts[1] : parts[0], real && /* @__PURE__ */ jsx("span", {
|
|
2933
|
+
className: "mt-1 block text-po-dim",
|
|
2934
|
+
children: parts[0]
|
|
2935
|
+
})]
|
|
2936
|
+
}),
|
|
2937
|
+
/* @__PURE__ */ jsx("td", {
|
|
2938
|
+
className: "py-3 pr-3 font-mono text-po-dim",
|
|
2939
|
+
children: real ? parts.slice(4).join(" ") : parts[1]
|
|
2940
|
+
}),
|
|
2941
|
+
/* @__PURE__ */ jsxs("td", { children: [/* @__PURE__ */ jsx(StatusBadge, {
|
|
2942
|
+
tone: status === "orphaned" ? "neutral" : status === "unowned" ? "info" : "ok",
|
|
2943
|
+
size: "xs",
|
|
2944
|
+
className: status === "orphaned" ? "bg-po-tint-yellow text-po-yellow" : "",
|
|
2945
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
2946
|
+
className: "inline-block first-letter:uppercase",
|
|
2947
|
+
children: status
|
|
2948
|
+
})
|
|
2949
|
+
}), real && /* @__PURE__ */ jsx("span", {
|
|
2950
|
+
className: "ml-2 text-po-dim",
|
|
2951
|
+
children: parts[3]
|
|
2952
|
+
})] })
|
|
2953
|
+
]
|
|
2954
|
+
}, i);
|
|
2955
|
+
}) })]
|
|
2956
|
+
}) : /* @__PURE__ */ jsx("pre", {
|
|
2957
|
+
className: `whitespace-pre-wrap font-mono text-sm ${isError ? "text-po-red" : "text-po-dim"}`,
|
|
2958
|
+
children: output
|
|
2959
|
+
}) })
|
|
2960
|
+
] })
|
|
2710
2961
|
});
|
|
2711
2962
|
}
|
|
2712
2963
|
//#endregion
|
|
@@ -2740,6 +2991,10 @@ const webPlugin = defineWebPlugin({
|
|
|
2740
2991
|
},
|
|
2741
2992
|
get fills() {
|
|
2742
2993
|
return [
|
|
2994
|
+
via({
|
|
2995
|
+
slot: "vibe-lint.finding-review",
|
|
2996
|
+
id: "git-review"
|
|
2997
|
+
}, GitReview_vibe_lint_finding_review_web_default),
|
|
2743
2998
|
via({
|
|
2744
2999
|
slot: "session-menu",
|
|
2745
3000
|
id: "worktree"
|