@agimon-ai/po-git 0.0.0-alpha.6 → 0.0.0-alpha.60
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/extensions/native.mjs +2160 -534
- package/dist/extensions/native.mjs.map +1 -1
- package/dist/extensions/server.cjs +286 -114
- 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 +283 -111
- package/dist/extensions/server.mjs.map +1 -1
- package/dist/extensions/web.mjs +1412 -1291
- package/dist/extensions/web.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{worktreeOperations-DGUbUnYU.cjs → worktreeOperations-C1ygq-Na.cjs} +148 -65
- package/dist/worktreeOperations-C1ygq-Na.cjs.map +1 -0
- package/dist/{worktreeOperations-BSeyxUai.mjs → worktreeOperations-CP2lYxrI.mjs} +117 -64
- package/dist/worktreeOperations-CP2lYxrI.mjs.map +1 -0
- package/llms.txt +1 -1
- package/package.json +20 -18
- 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, Dialog, DialogContent, DialogTitle, DiffView, Dot, DropdownMenuItem, EmptyState, Input, 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, 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({
|
|
@@ -51,7 +51,7 @@ const api = createApiClient({
|
|
|
51
51
|
"session"
|
|
52
52
|
],
|
|
53
53
|
basePath: "git",
|
|
54
|
-
transport: (input, init) =>
|
|
54
|
+
transport: (input, init) => browserTransport.fetch(input, init),
|
|
55
55
|
sessionAddress: sessionApiAddress
|
|
56
56
|
});
|
|
57
57
|
//#endregion
|
|
@@ -113,20 +113,241 @@ function createRequest(state) {
|
|
|
113
113
|
}
|
|
114
114
|
//#endregion
|
|
115
115
|
//#region src/extensions/workspaces/(frontend)/fill/_components/GitNewSessionDialog.tsx
|
|
116
|
-
|
|
117
|
-
{
|
|
116
|
+
function Field$1({ label, children }) {
|
|
117
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
118
|
+
className: "flex flex-col gap-2 text-base text-po-text",
|
|
119
|
+
children: [/* @__PURE__ */ jsx("span", { children: label }), children]
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/** Inline presentation also lets story capture inspect content, rather than a portal-only root. */
|
|
123
|
+
function GitNewSessionDialogView({ workspaceRoot, branches, loadError = "", initialForm, busy = false, error = "", onClose, onCreate }) {
|
|
124
|
+
const [form, setForm] = useState({
|
|
118
125
|
mode: "existing",
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
newBranch: "",
|
|
127
|
+
baseRef: branches?.defaultBase ?? "",
|
|
128
|
+
name: "",
|
|
129
|
+
...initialForm
|
|
130
|
+
});
|
|
131
|
+
const [query, setQuery] = useState("");
|
|
132
|
+
const ready = branches?.repository === true && loadError === "";
|
|
133
|
+
const loading = branches === null && loadError === "";
|
|
134
|
+
const mode = ready || loading ? form.mode : "plain";
|
|
135
|
+
const state = {
|
|
136
|
+
...form,
|
|
137
|
+
mode,
|
|
138
|
+
baseRef: form.baseRef || branches?.defaultBase || ""
|
|
139
|
+
};
|
|
140
|
+
const problem = formProblem$1(state);
|
|
141
|
+
const update = (patch) => setForm((current) => ({
|
|
142
|
+
...current,
|
|
143
|
+
...patch
|
|
144
|
+
}));
|
|
145
|
+
const options = branches === null ? [] : branchOptions(branches, query);
|
|
146
|
+
const bases = branches === null ? [] : baseOptions(branches);
|
|
147
|
+
const submit = () => {
|
|
148
|
+
if (!busy && !loading && problem === void 0) onCreate(state);
|
|
149
|
+
};
|
|
150
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
151
|
+
"data-testid": "new-session-dialog",
|
|
152
|
+
"aria-label": "New session",
|
|
153
|
+
className: "shrink-0 overflow-hidden rounded-lg border border-po-border bg-po-panel shadow-sm",
|
|
154
|
+
children: [
|
|
155
|
+
/* @__PURE__ */ jsxs("header", {
|
|
156
|
+
className: "border-b border-po-border-soft px-6 py-4",
|
|
157
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
158
|
+
className: "text-xl font-semibold text-po-hi",
|
|
159
|
+
children: "New session"
|
|
160
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
161
|
+
className: "mt-1 font-mono text-sm text-po-dim",
|
|
162
|
+
children: workspaceRoot
|
|
163
|
+
})]
|
|
164
|
+
}),
|
|
165
|
+
/* @__PURE__ */ jsxs("div", {
|
|
166
|
+
className: "flex flex-col gap-5 p-6",
|
|
167
|
+
children: [
|
|
168
|
+
!ready ? /* @__PURE__ */ jsx("p", {
|
|
169
|
+
"data-testid": "git-new-session-plain-note",
|
|
170
|
+
className: "text-base text-po-dim",
|
|
171
|
+
children: loading ? "Reading branches..." : loadError || "This workspace is not a git checkout. Start a plain session in this folder."
|
|
172
|
+
}) : /* @__PURE__ */ jsxs(Tabs, {
|
|
173
|
+
value: mode,
|
|
174
|
+
onValueChange: (value) => update({ mode: value }),
|
|
175
|
+
className: "flex flex-col gap-5",
|
|
176
|
+
children: [
|
|
177
|
+
/* @__PURE__ */ jsxs(TabsList, {
|
|
178
|
+
"aria-label": "Session kind",
|
|
179
|
+
className: "rounded-md border border-po-border-soft bg-po-bg p-1",
|
|
180
|
+
children: [
|
|
181
|
+
/* @__PURE__ */ jsx(TabsTrigger, {
|
|
182
|
+
"data-testid": "git-new-session-mode-existing",
|
|
183
|
+
value: "existing",
|
|
184
|
+
disabled: busy,
|
|
185
|
+
className: "flex-1 justify-center",
|
|
186
|
+
children: "Existing branch"
|
|
187
|
+
}),
|
|
188
|
+
/* @__PURE__ */ jsx(TabsTrigger, {
|
|
189
|
+
"data-testid": "git-new-session-mode-new",
|
|
190
|
+
value: "new",
|
|
191
|
+
disabled: busy,
|
|
192
|
+
className: "flex-1 justify-center",
|
|
193
|
+
children: "New branch"
|
|
194
|
+
}),
|
|
195
|
+
/* @__PURE__ */ jsx(TabsTrigger, {
|
|
196
|
+
"data-testid": "git-new-session-mode-plain",
|
|
197
|
+
value: "plain",
|
|
198
|
+
disabled: busy,
|
|
199
|
+
className: "flex-1 justify-center",
|
|
200
|
+
children: "No branch"
|
|
201
|
+
})
|
|
202
|
+
]
|
|
203
|
+
}),
|
|
204
|
+
/* @__PURE__ */ jsxs(TabsContent, {
|
|
205
|
+
value: "existing",
|
|
206
|
+
className: "flex flex-col gap-2",
|
|
207
|
+
children: [
|
|
208
|
+
/* @__PURE__ */ jsx("p", {
|
|
209
|
+
className: "text-base font-medium",
|
|
210
|
+
children: "Branch"
|
|
211
|
+
}),
|
|
212
|
+
/* @__PURE__ */ jsx(Input, {
|
|
213
|
+
"data-testid": "git-new-session-branch-search",
|
|
214
|
+
"aria-label": "Find a branch",
|
|
215
|
+
placeholder: "Find a branch",
|
|
216
|
+
value: query,
|
|
217
|
+
onChange: (event) => setQuery(event.target.value),
|
|
218
|
+
disabled: busy
|
|
219
|
+
}),
|
|
220
|
+
/* @__PURE__ */ jsxs("fieldset", {
|
|
221
|
+
"aria-label": "Branches",
|
|
222
|
+
className: "max-h-64 min-w-0 overflow-y-auto rounded-md border border-po-border-soft bg-po-bg p-1",
|
|
223
|
+
children: [options.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
224
|
+
className: "px-3 py-2 text-sm text-po-dim",
|
|
225
|
+
children: "No matching branches"
|
|
226
|
+
}) : null, options.map((branch, index) => /* @__PURE__ */ jsxs("div", { children: [branch.remote !== void 0 && (index === 0 || options[index - 1]?.remote === void 0) ? /* @__PURE__ */ jsx("p", {
|
|
227
|
+
className: "mt-1 border-t border-po-border-soft px-3 pt-2 text-sm text-po-dim",
|
|
228
|
+
children: "Remote branches"
|
|
229
|
+
}) : null, /* @__PURE__ */ jsxs("button", {
|
|
230
|
+
"data-testid": "git-new-session-branch-option",
|
|
231
|
+
"data-branch": branch.name,
|
|
232
|
+
"data-remote": branch.remote,
|
|
233
|
+
"aria-pressed": form.selected?.key === branch.key,
|
|
234
|
+
disabled: branch.busyAt !== void 0 || busy,
|
|
235
|
+
onClick: () => update({ selected: branch }),
|
|
236
|
+
className: `flex w-full 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"}`,
|
|
237
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
238
|
+
className: "flex items-center justify-between gap-2",
|
|
239
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
240
|
+
className: "font-mono",
|
|
241
|
+
children: branch.remote === void 0 ? branch.name : `${branch.remote}/${branch.name}`
|
|
242
|
+
}), form.selected?.key === branch.key ? /* @__PURE__ */ jsx(CheckIcon, { className: "size-4" }) : null]
|
|
243
|
+
}), branch.busyAt !== void 0 ? /* @__PURE__ */ jsxs("span", {
|
|
244
|
+
className: "text-sm text-po-dim",
|
|
245
|
+
children: ["Checked out in ", /* @__PURE__ */ jsx("span", {
|
|
246
|
+
className: "font-mono break-all",
|
|
247
|
+
children: branch.busyAt
|
|
248
|
+
})]
|
|
249
|
+
}) : branch.current ? /* @__PURE__ */ jsx("span", {
|
|
250
|
+
className: "text-sm text-po-dim",
|
|
251
|
+
children: "Current"
|
|
252
|
+
}) : null]
|
|
253
|
+
})] }, branch.key))]
|
|
254
|
+
})
|
|
255
|
+
]
|
|
256
|
+
}),
|
|
257
|
+
/* @__PURE__ */ jsxs(TabsContent, {
|
|
258
|
+
value: "new",
|
|
259
|
+
className: "flex flex-col gap-4",
|
|
260
|
+
children: [/* @__PURE__ */ jsx(Field$1, {
|
|
261
|
+
label: "Branch name",
|
|
262
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
263
|
+
"data-testid": "git-new-session-new-branch",
|
|
264
|
+
"aria-label": "Branch name",
|
|
265
|
+
placeholder: "feature/my-change",
|
|
266
|
+
className: "font-mono",
|
|
267
|
+
value: form.newBranch,
|
|
268
|
+
onChange: (event) => update({ newBranch: event.target.value }),
|
|
269
|
+
disabled: busy
|
|
270
|
+
})
|
|
271
|
+
}), /* @__PURE__ */ jsx(Field$1, {
|
|
272
|
+
label: "From",
|
|
273
|
+
children: /* @__PURE__ */ jsxs(Select, {
|
|
274
|
+
value: form.baseRef || branches?.defaultBase || "",
|
|
275
|
+
onValueChange: (baseRef) => update({ baseRef }),
|
|
276
|
+
disabled: busy,
|
|
277
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
278
|
+
"data-testid": "git-new-session-base",
|
|
279
|
+
"aria-label": "From",
|
|
280
|
+
className: "font-mono",
|
|
281
|
+
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
282
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: bases.map((base) => /* @__PURE__ */ jsx(SelectItem, {
|
|
283
|
+
value: base,
|
|
284
|
+
children: base
|
|
285
|
+
}, base)) })]
|
|
286
|
+
})
|
|
287
|
+
})]
|
|
288
|
+
}),
|
|
289
|
+
/* @__PURE__ */ jsx(TabsContent, {
|
|
290
|
+
value: "plain",
|
|
291
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
292
|
+
className: "text-base text-po-dim",
|
|
293
|
+
children: "Start in this workspace folder, without creating a worktree."
|
|
294
|
+
})
|
|
295
|
+
})
|
|
296
|
+
]
|
|
297
|
+
}),
|
|
298
|
+
/* @__PURE__ */ jsx(Field$1, {
|
|
299
|
+
label: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
300
|
+
"Name",
|
|
301
|
+
" ",
|
|
302
|
+
/* @__PURE__ */ jsx("span", {
|
|
303
|
+
className: "text-sm text-po-dim",
|
|
304
|
+
children: mode === "plain" ? "(optional)" : "(optional, defaults to the branch)"
|
|
305
|
+
})
|
|
306
|
+
] }),
|
|
307
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
308
|
+
"data-testid": "new-session-name",
|
|
309
|
+
"aria-label": "Session name",
|
|
310
|
+
placeholder: "Session name",
|
|
311
|
+
value: form.name,
|
|
312
|
+
onChange: (event) => update({ name: event.target.value }),
|
|
313
|
+
onKeyDown: (event) => {
|
|
314
|
+
if (event.key === "Enter") submit();
|
|
315
|
+
},
|
|
316
|
+
disabled: busy
|
|
317
|
+
})
|
|
318
|
+
}),
|
|
319
|
+
mode !== "plain" ? /* @__PURE__ */ jsx("p", {
|
|
320
|
+
className: "text-sm leading-relaxed text-po-dim",
|
|
321
|
+
children: "Runs in its own worktree, so this checkout stays as it is."
|
|
322
|
+
}) : null,
|
|
323
|
+
error ? /* @__PURE__ */ jsx("p", {
|
|
324
|
+
"data-testid": "new-session-error",
|
|
325
|
+
role: "alert",
|
|
326
|
+
className: "rounded-md border border-po-edge-red bg-po-tint-red p-3 text-sm text-po-red",
|
|
327
|
+
children: error
|
|
328
|
+
}) : null
|
|
329
|
+
]
|
|
330
|
+
}),
|
|
331
|
+
/* @__PURE__ */ jsxs("footer", {
|
|
332
|
+
className: "flex items-center justify-between border-t border-po-border-soft px-6 py-4",
|
|
333
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
334
|
+
"data-testid": "new-session-cancel",
|
|
335
|
+
variant: "ghost",
|
|
336
|
+
disabled: busy,
|
|
337
|
+
onClick: onClose,
|
|
338
|
+
children: "Cancel"
|
|
339
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
340
|
+
"data-testid": "new-session-create",
|
|
341
|
+
variant: "primary",
|
|
342
|
+
title: problem,
|
|
343
|
+
disabled: busy || loading || problem !== void 0,
|
|
344
|
+
onClick: submit,
|
|
345
|
+
children: busy ? mode === "plain" ? "Creating session..." : "Creating worktree..." : "Create session"
|
|
346
|
+
})]
|
|
347
|
+
})
|
|
348
|
+
]
|
|
349
|
+
});
|
|
350
|
+
}
|
|
130
351
|
function errorOf$1(body, status) {
|
|
131
352
|
return typeof body === "object" && body !== null && typeof body.error === "string" ? body.error : `The cockpit answered ${String(status)}.`;
|
|
132
353
|
}
|
|
@@ -138,70 +359,47 @@ function errorOf$1(body, status) {
|
|
|
138
359
|
function GitNewSessionDialog({ newSession }) {
|
|
139
360
|
const [branches, setBranches] = useState(null);
|
|
140
361
|
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
362
|
const [error, setError] = useState("");
|
|
149
363
|
const [busy, setBusy] = useState(false);
|
|
150
364
|
const workspaceId = newSession?.workspaceId;
|
|
151
|
-
const request = newSession?.requestWithStepUp;
|
|
365
|
+
const request = useEffectEvent((input) => newSession?.requestWithStepUp(input));
|
|
152
366
|
useEffect(() => {
|
|
153
|
-
if (workspaceId === void 0
|
|
367
|
+
if (workspaceId === void 0) return;
|
|
154
368
|
let stale = false;
|
|
155
369
|
const url = api.workspace(workspaceId).branches.url();
|
|
156
|
-
request(url)
|
|
370
|
+
request(url)?.then(async (response) => {
|
|
157
371
|
const body = await response.json().catch(() => void 0);
|
|
158
372
|
if (stale) return;
|
|
159
373
|
if (!response.ok) {
|
|
160
374
|
setLoadError(errorOf$1(body, response.status));
|
|
161
375
|
return;
|
|
162
376
|
}
|
|
163
|
-
|
|
164
|
-
setBranches(loaded);
|
|
165
|
-
setForm((current) => ({
|
|
166
|
-
...current,
|
|
167
|
-
baseRef: loaded.defaultBase ?? ""
|
|
168
|
-
}));
|
|
377
|
+
setBranches(body);
|
|
169
378
|
}).catch(() => {
|
|
170
379
|
if (!stale) setLoadError("The branches could not be loaded.");
|
|
171
380
|
});
|
|
172
381
|
return () => {
|
|
173
382
|
stale = true;
|
|
174
383
|
};
|
|
175
|
-
}, [workspaceId
|
|
384
|
+
}, [workspaceId]);
|
|
176
385
|
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;
|
|
386
|
+
const submit = async (state) => {
|
|
387
|
+
if (busy || formProblem$1(state) !== void 0) return;
|
|
192
388
|
setBusy(true);
|
|
193
389
|
setError("");
|
|
194
390
|
const body = createRequest(state);
|
|
195
391
|
let failure;
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
392
|
+
try {
|
|
393
|
+
if (body === void 0) failure = await newSession.createPlain(state.name);
|
|
394
|
+
else {
|
|
395
|
+
const response = await newSession.requestWithStepUp(api.workspace(newSession.workspaceId).createSession.url(), {
|
|
396
|
+
method: "POST",
|
|
397
|
+
headers: { "Content-Type": "application/json" },
|
|
398
|
+
body: JSON.stringify(body)
|
|
399
|
+
});
|
|
400
|
+
const answer = await response.json().catch(() => void 0);
|
|
401
|
+
failure = response.ok ? await newSession.openCreated(answer.sessionId) : errorOf$1(answer, response.status);
|
|
402
|
+
}
|
|
205
403
|
} catch {
|
|
206
404
|
failure = "The cockpit is unreachable.";
|
|
207
405
|
}
|
|
@@ -211,159 +409,25 @@ function GitNewSessionDialog({ newSession }) {
|
|
|
211
409
|
};
|
|
212
410
|
return /* @__PURE__ */ jsx(Dialog, {
|
|
213
411
|
open: true,
|
|
214
|
-
onOpenChange: (next) =>
|
|
412
|
+
onOpenChange: (next) => {
|
|
413
|
+
if (!next && !busy) newSession.close();
|
|
414
|
+
},
|
|
215
415
|
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
216
416
|
width: "md",
|
|
217
|
-
|
|
417
|
+
className: "overflow-y-auto p-0",
|
|
218
418
|
"aria-describedby": void 0,
|
|
219
|
-
children: [/* @__PURE__ */ jsx(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
"aria-checked": mode === entry.mode,
|
|
232
|
-
variant: mode === entry.mode ? "outline" : "ghost",
|
|
233
|
-
size: "sm",
|
|
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
|
-
] })]
|
|
419
|
+
children: [/* @__PURE__ */ jsx(DialogTitle, {
|
|
420
|
+
className: "sr-only",
|
|
421
|
+
children: "New session"
|
|
422
|
+
}), /* @__PURE__ */ jsx(GitNewSessionDialogView, {
|
|
423
|
+
workspaceRoot: newSession.workspaceRoot,
|
|
424
|
+
branches,
|
|
425
|
+
loadError,
|
|
426
|
+
busy,
|
|
427
|
+
error,
|
|
428
|
+
onClose: newSession.close,
|
|
429
|
+
onCreate: (state) => void submit(state)
|
|
430
|
+
})]
|
|
367
431
|
})
|
|
368
432
|
});
|
|
369
433
|
}
|
|
@@ -446,11 +510,17 @@ function saveRequestOf(form) {
|
|
|
446
510
|
* AVOID:
|
|
447
511
|
* - Echoing a token back. The view the server sends has none to echo.
|
|
448
512
|
*/
|
|
513
|
+
function Field({ label, children }) {
|
|
514
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
515
|
+
className: "flex flex-col gap-2 text-base text-po-text",
|
|
516
|
+
children: [/* @__PURE__ */ jsx("span", { children: label }), children]
|
|
517
|
+
});
|
|
518
|
+
}
|
|
449
519
|
const METHODS = [
|
|
450
520
|
{
|
|
451
521
|
method: "none",
|
|
452
522
|
title: "This machine's git",
|
|
453
|
-
detail: "
|
|
523
|
+
detail: "Your credential helper, SSH config and agent, as they are."
|
|
454
524
|
},
|
|
455
525
|
{
|
|
456
526
|
method: "ssh",
|
|
@@ -465,10 +535,14 @@ const METHODS = [
|
|
|
465
535
|
];
|
|
466
536
|
function GitAuthForm({ saved, saving, saveError, savedNotice = false, onSave }) {
|
|
467
537
|
const [form, setForm] = useState(() => formFromView(saved));
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
538
|
+
const [edited, setEdited] = useState(false);
|
|
539
|
+
const update = (patch) => {
|
|
540
|
+
setEdited(true);
|
|
541
|
+
setForm((current) => ({
|
|
542
|
+
...current,
|
|
543
|
+
...patch
|
|
544
|
+
}));
|
|
545
|
+
};
|
|
472
546
|
const problem = formProblem(form, saved);
|
|
473
547
|
const tokenSaved = saved.https?.hasToken === true && saved.https.host === form.host.trim().toLowerCase();
|
|
474
548
|
const host = form.host.trim() === "" ? "this host" : form.host.trim();
|
|
@@ -476,133 +550,156 @@ function GitAuthForm({ saved, saving, saveError, savedNotice = false, onSave })
|
|
|
476
550
|
"data-testid": "git-auth-form",
|
|
477
551
|
children: [
|
|
478
552
|
/* @__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(
|
|
553
|
+
/* @__PURE__ */ jsx("div", {
|
|
480
554
|
"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
|
-
|
|
555
|
+
className: "flex flex-col gap-3",
|
|
556
|
+
children: METHODS.map((choice) => /* @__PURE__ */ jsxs("div", {
|
|
557
|
+
className: `overflow-hidden rounded-lg border ${form.method === choice.method ? "border-po-green" : "border-po-border-soft"}`,
|
|
558
|
+
children: [
|
|
559
|
+
/* @__PURE__ */ jsxs("button", {
|
|
560
|
+
type: "button",
|
|
561
|
+
"aria-pressed": form.method === choice.method,
|
|
562
|
+
disabled: saving,
|
|
563
|
+
"data-testid": `git-auth-method-${choice.method}`,
|
|
564
|
+
onClick: () => update({ method: choice.method }),
|
|
565
|
+
className: `flex w-full items-start gap-3 p-4 text-left ${form.method === choice.method ? "bg-po-tint-green" : "bg-po-bg"}`,
|
|
566
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
567
|
+
className: "flex flex-1 flex-col gap-1",
|
|
568
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
569
|
+
className: "text-base font-semibold text-po-hi",
|
|
570
|
+
children: choice.title
|
|
571
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
572
|
+
className: "text-sm leading-relaxed text-po-dim",
|
|
573
|
+
children: choice.detail
|
|
574
|
+
})]
|
|
575
|
+
}), form.method === choice.method ? /* @__PURE__ */ jsx(CheckIcon, { className: "mt-0.5 size-4 shrink-0 text-po-green" }) : null]
|
|
576
|
+
}),
|
|
577
|
+
form.method === choice.method && form.method === "ssh" ? /* @__PURE__ */ jsxs("div", {
|
|
578
|
+
className: "flex flex-col gap-3 border-t border-po-border-soft p-4",
|
|
579
|
+
children: [/* @__PURE__ */ jsx(Field, {
|
|
580
|
+
label: "Key path",
|
|
581
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
582
|
+
"data-testid": "git-auth-key-path",
|
|
583
|
+
value: form.keyPath,
|
|
584
|
+
placeholder: "~/.ssh/id_ed25519",
|
|
585
|
+
spellCheck: false,
|
|
586
|
+
disabled: saving,
|
|
587
|
+
onChange: (event) => update({ keyPath: event.target.value }),
|
|
588
|
+
className: "font-mono"
|
|
589
|
+
})
|
|
590
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
591
|
+
className: "text-sm leading-relaxed text-po-dim",
|
|
592
|
+
children: "Leave blank to use SSH config and ssh-agent. Passphrases need ssh-agent. Trust an unknown host in a terminal first."
|
|
593
|
+
})]
|
|
594
|
+
}) : null,
|
|
595
|
+
form.method === choice.method && form.method === "https" ? /* @__PURE__ */ jsxs("div", {
|
|
596
|
+
className: "flex flex-col gap-4 border-t border-po-border-soft p-4",
|
|
597
|
+
children: [
|
|
598
|
+
/* @__PURE__ */ jsxs("div", {
|
|
599
|
+
className: "grid grid-cols-2 gap-3",
|
|
600
|
+
children: [/* @__PURE__ */ jsx(Field, {
|
|
601
|
+
label: "Host",
|
|
602
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
603
|
+
"data-testid": "git-auth-host",
|
|
604
|
+
value: form.host,
|
|
605
|
+
spellCheck: false,
|
|
606
|
+
disabled: saving,
|
|
607
|
+
onChange: (event) => update({ host: event.target.value }),
|
|
608
|
+
className: "font-mono"
|
|
609
|
+
})
|
|
610
|
+
}), /* @__PURE__ */ jsx(Field, {
|
|
611
|
+
label: "Username",
|
|
612
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
613
|
+
"data-testid": "git-auth-username",
|
|
614
|
+
value: form.username,
|
|
615
|
+
autoComplete: "username",
|
|
616
|
+
spellCheck: false,
|
|
617
|
+
disabled: saving,
|
|
618
|
+
onChange: (event) => update({ username: event.target.value })
|
|
619
|
+
})
|
|
620
|
+
})]
|
|
621
|
+
}),
|
|
622
|
+
/* @__PURE__ */ jsx(Field, {
|
|
623
|
+
label: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
624
|
+
"Token",
|
|
625
|
+
" ",
|
|
626
|
+
tokenSaved ? /* @__PURE__ */ jsx("span", {
|
|
627
|
+
"data-testid": "git-auth-token-saved",
|
|
628
|
+
className: "ml-2 text-sm text-po-green",
|
|
629
|
+
children: "A token is saved"
|
|
630
|
+
}) : null
|
|
631
|
+
] }),
|
|
632
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
633
|
+
"data-testid": "git-auth-token",
|
|
634
|
+
type: "password",
|
|
635
|
+
value: form.token,
|
|
636
|
+
autoComplete: "new-password",
|
|
637
|
+
placeholder: tokenSaved ? "Leave blank to keep it" : "Personal access token",
|
|
638
|
+
disabled: saving,
|
|
639
|
+
onChange: (event) => update({ token: event.target.value })
|
|
640
|
+
})
|
|
641
|
+
}),
|
|
642
|
+
/* @__PURE__ */ jsxs("p", {
|
|
643
|
+
className: "text-sm text-po-dim",
|
|
644
|
+
children: [
|
|
645
|
+
"Sent only to ",
|
|
646
|
+
/* @__PURE__ */ jsx("span", {
|
|
647
|
+
className: "font-mono",
|
|
648
|
+
children: host
|
|
649
|
+
}),
|
|
650
|
+
". Other hosts use this machine's git."
|
|
651
|
+
]
|
|
652
|
+
})
|
|
653
|
+
]
|
|
654
|
+
}) : null
|
|
655
|
+
]
|
|
498
656
|
}, choice.method))
|
|
499
657
|
}),
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
"
|
|
505
|
-
|
|
658
|
+
/* @__PURE__ */ jsxs(SettingsNote, {
|
|
659
|
+
className: "flex items-start gap-2 leading-relaxed",
|
|
660
|
+
children: [/* @__PURE__ */ jsx(ShieldIcon, {
|
|
661
|
+
"aria-hidden": true,
|
|
662
|
+
className: "mt-0.5 size-4 shrink-0"
|
|
663
|
+
}), /* @__PURE__ */ jsxs("span", { children: [
|
|
664
|
+
"Only cockpit pull, push and rebase use these credentials, not the agent.",
|
|
665
|
+
/* @__PURE__ */ jsx("br", {}),
|
|
666
|
+
"Stored in ",
|
|
667
|
+
/* @__PURE__ */ jsx("span", {
|
|
506
668
|
className: "font-mono",
|
|
507
|
-
children: "
|
|
669
|
+
children: "~/.po/git/credentials.json"
|
|
508
670
|
}),
|
|
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,
|
|
671
|
+
", readable only by you."
|
|
672
|
+
] })]
|
|
673
|
+
}),
|
|
573
674
|
/* @__PURE__ */ jsxs(SettingsActions, {
|
|
574
675
|
className: "gap-4",
|
|
575
676
|
children: [/* @__PURE__ */ jsx(Button, {
|
|
576
677
|
variant: "primary",
|
|
577
|
-
size: "sm",
|
|
578
678
|
"data-testid": "git-auth-save",
|
|
579
679
|
disabled: problem !== void 0 || saving,
|
|
580
680
|
loading: saving,
|
|
581
|
-
loadingLabel: "Saving",
|
|
582
|
-
onClick: () =>
|
|
681
|
+
loadingLabel: "Saving...",
|
|
682
|
+
onClick: () => {
|
|
683
|
+
if (!saving && problem === void 0) onSave(saveRequestOf(form));
|
|
684
|
+
},
|
|
583
685
|
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, {
|
|
686
|
+
}), savedNotice && !edited && problem === void 0 && saveError === void 0 ? /* @__PURE__ */ jsx("span", {
|
|
594
687
|
"data-testid": "git-auth-saved",
|
|
595
|
-
className: "text-po-green",
|
|
688
|
+
className: "text-sm text-po-green",
|
|
596
689
|
children: "Saved."
|
|
597
690
|
}) : null]
|
|
598
691
|
}),
|
|
599
|
-
/* @__PURE__ */
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
692
|
+
problem !== void 0 ? /* @__PURE__ */ jsx("p", {
|
|
693
|
+
"data-testid": "git-auth-problem",
|
|
694
|
+
className: "text-sm text-po-red",
|
|
695
|
+
children: problem
|
|
696
|
+
}) : null,
|
|
697
|
+
saveError !== void 0 ? /* @__PURE__ */ jsx("p", {
|
|
698
|
+
role: "alert",
|
|
699
|
+
"data-testid": "git-auth-error",
|
|
700
|
+
className: "text-sm text-po-red",
|
|
701
|
+
children: saveError
|
|
702
|
+
}) : null
|
|
606
703
|
]
|
|
607
704
|
});
|
|
608
705
|
}
|
|
@@ -893,200 +990,394 @@ function requestWorktreeClose(sessionId, id) {
|
|
|
893
990
|
});
|
|
894
991
|
}
|
|
895
992
|
//#endregion
|
|
993
|
+
//#region src/extensions/workspaces/sessions/(frontend)/fill/_components/WorktreeCreateDialog.tsx
|
|
994
|
+
function WorktreeSessionMenu({ sessionId, sessionReservationId }) {
|
|
995
|
+
return /* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
996
|
+
disabled: sessionId === null,
|
|
997
|
+
"data-testid": `session-worktree-${sessionReservationId ?? sessionId}`,
|
|
998
|
+
onSelect: () => {
|
|
999
|
+
if (sessionId !== null) worktreeCreateDialog.update(() => ({
|
|
1000
|
+
sessionId,
|
|
1001
|
+
...sessionReservationId === void 0 ? {} : { reservationId: sessionReservationId }
|
|
1002
|
+
}));
|
|
1003
|
+
},
|
|
1004
|
+
children: "Create Worktree Session..."
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
function WorktreeCreateOverlay(props) {
|
|
1008
|
+
const target = useStore(worktreeCreateDialog.store);
|
|
1009
|
+
const { holdSessionChannels } = props;
|
|
1010
|
+
useEffect(() => {
|
|
1011
|
+
if (target === null) return;
|
|
1012
|
+
return holdSessionChannels?.(target.sessionId, (frame) => {
|
|
1013
|
+
if (frame.type !== worktreesChannel.channel) return;
|
|
1014
|
+
const payload = worktreesChannel.parse(frame.payload);
|
|
1015
|
+
if (payload !== null) worktreesChannel.apply(target.sessionId, payload);
|
|
1016
|
+
});
|
|
1017
|
+
}, [target, holdSessionChannels]);
|
|
1018
|
+
if (target === null) return null;
|
|
1019
|
+
return /* @__PURE__ */ jsx(Dialog, {
|
|
1020
|
+
open: true,
|
|
1021
|
+
onOpenChange: (open) => {
|
|
1022
|
+
if (!open) worktreeCreateDialog.update(() => null);
|
|
1023
|
+
},
|
|
1024
|
+
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
1025
|
+
width: "md",
|
|
1026
|
+
className: "w-96 overflow-y-auto border-0",
|
|
1027
|
+
"data-testid": "session-worktree-dialog",
|
|
1028
|
+
"aria-describedby": void 0,
|
|
1029
|
+
children: [/* @__PURE__ */ jsx(DialogTitle, {
|
|
1030
|
+
className: "sr-only",
|
|
1031
|
+
children: "Create a worktree session"
|
|
1032
|
+
}), /* @__PURE__ */ createElement(WorktreeCreateForm, {
|
|
1033
|
+
...props,
|
|
1034
|
+
key: `${target.sessionId}:${target.reservationId ?? ""}`,
|
|
1035
|
+
sessionId: target.sessionId,
|
|
1036
|
+
sessionReservationId: target.reservationId,
|
|
1037
|
+
onCancel: () => worktreeCreateDialog.update(() => null),
|
|
1038
|
+
onCreated: () => worktreeCreateDialog.update(() => null)
|
|
1039
|
+
})]
|
|
1040
|
+
})
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
function WorktreeCreateForm({ sessionId, sessionReservationId, onCancel, onRequested, onCreated }) {
|
|
1044
|
+
const session = useStore(worktreeActivity.store, (state) => worktreeActivity.select(state, sessionId));
|
|
1045
|
+
const [branch, setBranch] = useState("");
|
|
1046
|
+
const [baseRef, setBaseRef] = useState("");
|
|
1047
|
+
const sent = useRef(void 0);
|
|
1048
|
+
useEffect(() => {
|
|
1049
|
+
const request = sent.current;
|
|
1050
|
+
if (request === void 0) return;
|
|
1051
|
+
if (session.pending !== void 0) {
|
|
1052
|
+
request.sawPending = true;
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
if (!session.worktrees.some((worktree) => worktree.branch === request.branch) && !request.sawPending) return;
|
|
1056
|
+
sent.current = void 0;
|
|
1057
|
+
if (session.errorTarget?.action !== "create") onCreated?.();
|
|
1058
|
+
}, [
|
|
1059
|
+
session.pending,
|
|
1060
|
+
session.worktrees,
|
|
1061
|
+
session.errorTarget,
|
|
1062
|
+
onCreated
|
|
1063
|
+
]);
|
|
1064
|
+
return /* @__PURE__ */ jsx(WorktreeCreateView, {
|
|
1065
|
+
branch,
|
|
1066
|
+
baseRef,
|
|
1067
|
+
onBranchChange: setBranch,
|
|
1068
|
+
onBaseChange: setBaseRef,
|
|
1069
|
+
busy: session.pending !== void 0,
|
|
1070
|
+
error: session.errorTarget?.action === "create" ? session.error : void 0,
|
|
1071
|
+
onCancel,
|
|
1072
|
+
onCreate: () => {
|
|
1073
|
+
if (sessionId !== null) {
|
|
1074
|
+
sent.current = {
|
|
1075
|
+
branch: branch.trim(),
|
|
1076
|
+
sawPending: false
|
|
1077
|
+
};
|
|
1078
|
+
onRequested?.(branch.trim());
|
|
1079
|
+
requestWorktreeCreate(sessionId, branch, baseRef, sessionReservationId);
|
|
1080
|
+
}
|
|
1081
|
+
},
|
|
1082
|
+
disabled: sessionId === null
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
function WorktreeCreateView({ branch, baseRef, onBranchChange, onBaseChange, busy = false, error, onCancel, onCreate, disabled = false }) {
|
|
1086
|
+
const id = useId();
|
|
1087
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1088
|
+
className: "rounded-lg border border-po-border-soft bg-po-panel p-5 text-po-text shadow-sm",
|
|
1089
|
+
children: [
|
|
1090
|
+
/* @__PURE__ */ jsx("h2", {
|
|
1091
|
+
id: `${id}-title`,
|
|
1092
|
+
className: "text-xl font-semibold text-po-hi",
|
|
1093
|
+
children: "Create a worktree session"
|
|
1094
|
+
}),
|
|
1095
|
+
/* @__PURE__ */ jsx("p", {
|
|
1096
|
+
className: "mt-2 text-sm leading-relaxed text-po-dim",
|
|
1097
|
+
children: "Create a separate checkout and session without moving your existing sessions."
|
|
1098
|
+
}),
|
|
1099
|
+
/* @__PURE__ */ jsxs("form", {
|
|
1100
|
+
onSubmit: (event) => {
|
|
1101
|
+
event.preventDefault();
|
|
1102
|
+
if (!busy && !disabled && branch.trim()) onCreate?.();
|
|
1103
|
+
},
|
|
1104
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1105
|
+
className: "mt-5 flex flex-col gap-4",
|
|
1106
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1107
|
+
className: "flex flex-col gap-2",
|
|
1108
|
+
children: [
|
|
1109
|
+
/* @__PURE__ */ jsx("label", {
|
|
1110
|
+
htmlFor: `${id}-branch`,
|
|
1111
|
+
className: "text-base font-medium",
|
|
1112
|
+
children: "Branch"
|
|
1113
|
+
}),
|
|
1114
|
+
/* @__PURE__ */ jsx(Input, {
|
|
1115
|
+
id: `${id}-branch`,
|
|
1116
|
+
"data-testid": "git-worktree-branch",
|
|
1117
|
+
className: "font-mono",
|
|
1118
|
+
placeholder: "wt/fix-auth",
|
|
1119
|
+
value: branch,
|
|
1120
|
+
onChange: (e) => onBranchChange?.(e.target.value),
|
|
1121
|
+
disabled: busy,
|
|
1122
|
+
"aria-invalid": !!error,
|
|
1123
|
+
"aria-describedby": error ? `${id}-error` : void 0
|
|
1124
|
+
}),
|
|
1125
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
1126
|
+
id: `${id}-error`,
|
|
1127
|
+
role: "alert",
|
|
1128
|
+
"data-testid": "git-worktree-error",
|
|
1129
|
+
className: "text-sm text-po-red",
|
|
1130
|
+
children: error
|
|
1131
|
+
})
|
|
1132
|
+
]
|
|
1133
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1134
|
+
className: "flex flex-col gap-2",
|
|
1135
|
+
children: [/* @__PURE__ */ jsx("label", {
|
|
1136
|
+
htmlFor: `${id}-base`,
|
|
1137
|
+
className: "text-base font-medium",
|
|
1138
|
+
children: "Start from"
|
|
1139
|
+
}), /* @__PURE__ */ jsx(Input, {
|
|
1140
|
+
size: "sm",
|
|
1141
|
+
id: `${id}-base`,
|
|
1142
|
+
"data-testid": "git-worktree-base",
|
|
1143
|
+
className: "font-mono",
|
|
1144
|
+
placeholder: "Current branch",
|
|
1145
|
+
value: baseRef,
|
|
1146
|
+
onChange: (e) => onBaseChange?.(e.target.value),
|
|
1147
|
+
disabled: busy
|
|
1148
|
+
})]
|
|
1149
|
+
})]
|
|
1150
|
+
}), /* @__PURE__ */ jsxs("footer", {
|
|
1151
|
+
className: "mt-5 flex items-center justify-end gap-2 border-t border-po-border-soft pt-4",
|
|
1152
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
1153
|
+
type: "button",
|
|
1154
|
+
variant: "ghost",
|
|
1155
|
+
disabled: busy,
|
|
1156
|
+
onClick: onCancel,
|
|
1157
|
+
children: "Cancel"
|
|
1158
|
+
}), /* @__PURE__ */ jsxs(Button, {
|
|
1159
|
+
type: "submit",
|
|
1160
|
+
variant: "primary",
|
|
1161
|
+
"data-testid": "git-worktree-create",
|
|
1162
|
+
disabled: busy || disabled || !branch.trim(),
|
|
1163
|
+
children: [busy && /* @__PURE__ */ jsx(Spinner, {
|
|
1164
|
+
className: "size-3",
|
|
1165
|
+
label: "Creating worktree"
|
|
1166
|
+
}), busy ? "Creating worktree..." : "Create worktree"]
|
|
1167
|
+
})]
|
|
1168
|
+
})]
|
|
1169
|
+
})
|
|
1170
|
+
]
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
//#endregion
|
|
896
1174
|
//#region src/extensions/workspaces/sessions/(frontend)/_components/WorktreesPanel.tsx
|
|
897
|
-
/**
|
|
898
|
-
* The '# git' group's tab: this session's worktrees, and the form that makes one.
|
|
899
|
-
*
|
|
900
|
-
* DESIGN PATTERNS:
|
|
901
|
-
* - Lives in the activity dock, opened by the group's name. A worktree is work
|
|
902
|
-
* in progress belonging to the session that started it, so it sits beside
|
|
903
|
-
* agents, runners and workflows rather than in a settings screen.
|
|
904
|
-
* - Header, form, then cards, the same rhythm the runners and workflows panels
|
|
905
|
-
* use. A worktree row is a card because it carries three facts and two
|
|
906
|
-
* actions, which a single line cannot hold without becoming a puzzle.
|
|
907
|
-
* - Local form state is never reset by a data refresh. Direct lifecycle events
|
|
908
|
-
* update the list without discarding a half-typed branch name.
|
|
909
|
-
* - Acts through the plugin's own hub channel, so a worktree made here and one
|
|
910
|
-
* made by the tool take exactly the same path.
|
|
911
|
-
* - Creating waits on the cockpit starting a session, so the hub reports each
|
|
912
|
-
* phase on that channel and the header shows it turning. A button that looks
|
|
913
|
-
* idle for two minutes reads as broken.
|
|
914
|
-
*
|
|
915
|
-
* AVOID:
|
|
916
|
-
* - Deriving form state from refreshed store data instead of the user's input.
|
|
917
|
-
* - Colour classes outside the token set. `po-error`, `po-line` and
|
|
918
|
-
* `po-bright` are not tokens: they compile to nothing and the row silently
|
|
919
|
-
* loses its border.
|
|
920
|
-
*/
|
|
921
|
-
const WORKTREES_TAB_ID = "git-worktrees";
|
|
922
1175
|
function worktreesTab() {
|
|
923
1176
|
return {
|
|
924
|
-
id:
|
|
1177
|
+
id: "git-worktrees",
|
|
925
1178
|
label: "worktrees",
|
|
926
1179
|
panel: WorktreesPanel
|
|
927
1180
|
};
|
|
928
1181
|
}
|
|
929
|
-
function WorktreesPanel(
|
|
930
|
-
const session = useStore(worktreeActivity.store, (state) => worktreeActivity.select(state, sessionId));
|
|
931
|
-
const [
|
|
932
|
-
const [
|
|
933
|
-
const
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1182
|
+
function WorktreesPanel(props) {
|
|
1183
|
+
const session = useStore(worktreeActivity.store, (state) => worktreeActivity.select(state, props.sessionId));
|
|
1184
|
+
const [creating, setCreating] = useState(session.errorTarget?.action === "create");
|
|
1185
|
+
const [requestedBranch, setRequestedBranch] = useState();
|
|
1186
|
+
const sawPending = useRef(false);
|
|
1187
|
+
useEffect(() => {
|
|
1188
|
+
if (!requestedBranch) return;
|
|
1189
|
+
if (session.worktrees.some((w) => w.branch === requestedBranch) || sawPending.current && session.pending === void 0) setRequestedBranch(void 0);
|
|
1190
|
+
else if (session.pending !== void 0) sawPending.current = true;
|
|
1191
|
+
}, [
|
|
1192
|
+
requestedBranch,
|
|
1193
|
+
session.pending,
|
|
1194
|
+
session.worktrees
|
|
1195
|
+
]);
|
|
1196
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1197
|
+
className: "min-h-0 flex-1 overflow-auto",
|
|
1198
|
+
children: /* @__PURE__ */ jsx(WorktreesPanelView, {
|
|
1199
|
+
worktrees: session.worktrees,
|
|
1200
|
+
pending: session.pending,
|
|
1201
|
+
startingBranch: session.pending === void 0 || session.worktrees.some((w) => w.branch === requestedBranch) ? void 0 : requestedBranch,
|
|
1202
|
+
error: session.errorTarget?.action === "create" && creating ? void 0 : session.error,
|
|
1203
|
+
closeErrorId: session.errorTarget?.action === "close" ? session.errorTarget.id : void 0,
|
|
1204
|
+
onNew: () => setCreating(true),
|
|
1205
|
+
onClose: props.sessionId === null ? void 0 : (id) => requestWorktreeClose(props.sessionId, id),
|
|
1206
|
+
form: creating ? /* @__PURE__ */ jsx(WorktreeCreateForm, {
|
|
1207
|
+
...props,
|
|
1208
|
+
onCancel: () => setCreating(false),
|
|
1209
|
+
onCreated: () => setCreating(false),
|
|
1210
|
+
onRequested: (branch) => {
|
|
1211
|
+
sawPending.current = false;
|
|
1212
|
+
setRequestedBranch(branch);
|
|
1213
|
+
}
|
|
1214
|
+
}) : void 0
|
|
1215
|
+
})
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
function WorktreesPanelView({ worktrees, pending, error, closeErrorId, onNew, onClose, form, startingIds = [], startingBranch }) {
|
|
1219
|
+
const groups = [worktrees.filter((w) => !w.orphaned && !w.unowned), worktrees.filter((w) => w.orphaned || w.unowned)];
|
|
946
1220
|
return /* @__PURE__ */ jsxs("div", {
|
|
947
1221
|
"data-testid": "git-worktrees-panel",
|
|
948
|
-
className: "
|
|
1222
|
+
className: "rounded-lg border border-po-border-soft bg-po-panel p-5",
|
|
949
1223
|
children: [
|
|
950
|
-
/* @__PURE__ */ jsxs("
|
|
951
|
-
className: "flex
|
|
1224
|
+
/* @__PURE__ */ jsxs("header", {
|
|
1225
|
+
className: "flex items-center justify-between gap-4 border-b border-po-border-soft pb-4",
|
|
952
1226
|
children: [
|
|
953
|
-
/* @__PURE__ */ jsx("
|
|
1227
|
+
/* @__PURE__ */ jsx("h2", {
|
|
954
1228
|
className: "text-lg font-semibold text-po-hi",
|
|
955
|
-
children: "
|
|
1229
|
+
children: "Worktrees"
|
|
956
1230
|
}),
|
|
957
1231
|
/* @__PURE__ */ jsx("span", {
|
|
958
1232
|
"data-testid": "git-worktrees-count",
|
|
959
|
-
className: "
|
|
960
|
-
children:
|
|
1233
|
+
className: "sr-only",
|
|
1234
|
+
children: worktrees.length === 0 ? "none yet" : `${worktrees.length} open`
|
|
961
1235
|
}),
|
|
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
|
-
})]
|
|
1236
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1237
|
+
variant: "outline",
|
|
1238
|
+
size: "sm",
|
|
1239
|
+
onClick: onNew,
|
|
1240
|
+
children: "New worktree"
|
|
973
1241
|
})
|
|
974
1242
|
]
|
|
975
1243
|
}),
|
|
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
|
|
1244
|
+
form,
|
|
1245
|
+
pending && !startingBranch && /* @__PURE__ */ jsxs("p", {
|
|
1246
|
+
"data-testid": "git-worktree-pending",
|
|
1247
|
+
className: "flex items-center gap-2 pt-3 text-sm text-po-dim",
|
|
1248
|
+
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
1249
|
+
className: "size-3",
|
|
1250
|
+
label: pending
|
|
1251
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1252
|
+
className: "inline-block first-letter:uppercase",
|
|
1253
|
+
children: pending
|
|
1018
1254
|
})]
|
|
1019
1255
|
}),
|
|
1020
|
-
|
|
1256
|
+
error && (!closeErrorId || !worktrees.some((w) => w.id === closeErrorId)) && /* @__PURE__ */ jsx("p", {
|
|
1021
1257
|
role: "alert",
|
|
1022
1258
|
"data-testid": "git-worktree-error",
|
|
1023
|
-
className: "
|
|
1024
|
-
children:
|
|
1025
|
-
})
|
|
1026
|
-
|
|
1259
|
+
className: "text-sm text-po-red",
|
|
1260
|
+
children: error
|
|
1261
|
+
}),
|
|
1262
|
+
worktrees.length === 0 && !startingBranch ? /* @__PURE__ */ jsx(EmptyState, {
|
|
1027
1263
|
"data-testid": "git-worktrees-empty",
|
|
1028
|
-
className: "
|
|
1029
|
-
title: "
|
|
1030
|
-
description: "
|
|
1031
|
-
}) : /* @__PURE__ */ jsx("
|
|
1264
|
+
className: "py-10",
|
|
1265
|
+
title: "No worktrees yet",
|
|
1266
|
+
description: "A worktree gives a branch its own checkout and a session nested under this one."
|
|
1267
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
1032
1268
|
"data-testid": "git-worktrees-list",
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1269
|
+
children: groups.map((group, i) => (group.length > 0 || i === 0 && startingBranch) && /* @__PURE__ */ jsxs("section", { children: [
|
|
1270
|
+
/* @__PURE__ */ jsxs("h3", {
|
|
1271
|
+
className: "flex items-center gap-2 pt-5 text-lg font-semibold text-po-hi",
|
|
1272
|
+
children: [
|
|
1273
|
+
i === 0 ? "Working" : "Needs a look",
|
|
1274
|
+
" ",
|
|
1275
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1276
|
+
className: "text-sm font-normal text-po-dim",
|
|
1277
|
+
children: [group.length + (i === 0 && startingBranch ? 1 : 0), " open"]
|
|
1278
|
+
})
|
|
1279
|
+
]
|
|
1280
|
+
}),
|
|
1281
|
+
i === 1 && /* @__PURE__ */ jsxs("p", {
|
|
1282
|
+
className: "pt-2 text-sm leading-relaxed text-po-dim",
|
|
1283
|
+
children: [
|
|
1284
|
+
group.length === 2 ? "Two worktrees" : group.length === 1 ? "One worktree" : `${group.length} worktrees`,
|
|
1285
|
+
" ",
|
|
1286
|
+
"would like your eye, whenever you are ready."
|
|
1287
|
+
]
|
|
1288
|
+
}),
|
|
1289
|
+
i === 0 && startingBranch && /* @__PURE__ */ jsxs("div", {
|
|
1290
|
+
"data-testid": "git-worktree-starting",
|
|
1291
|
+
className: "flex min-w-0 flex-col gap-2 border-b border-po-border-soft py-4",
|
|
1292
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1293
|
+
className: "flex min-w-0 items-center gap-2",
|
|
1294
|
+
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
1295
|
+
className: "size-3 shrink-0 text-po-dim",
|
|
1296
|
+
label: "Starting session"
|
|
1297
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1298
|
+
className: "min-w-0 flex-1 truncate font-mono text-base font-medium text-po-hi",
|
|
1299
|
+
children: startingBranch
|
|
1300
|
+
})]
|
|
1301
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
1302
|
+
className: "pl-4 text-sm text-po-dim",
|
|
1303
|
+
children: "Starting session..."
|
|
1304
|
+
})]
|
|
1305
|
+
}),
|
|
1306
|
+
group.map((w) => /* @__PURE__ */ jsx(WorktreeRow, {
|
|
1307
|
+
worktree: w,
|
|
1308
|
+
starting: startingIds.includes(w.id) || !!pending?.includes("starting") && w.sessionId === null && !w.orphaned && !w.unowned,
|
|
1309
|
+
disabled: !!pending || !onClose,
|
|
1310
|
+
onClose: () => onClose?.(w.id),
|
|
1311
|
+
error: closeErrorId === w.id ? error : void 0
|
|
1312
|
+
}, w.id))
|
|
1313
|
+
] }, i))
|
|
1040
1314
|
})
|
|
1041
1315
|
]
|
|
1042
1316
|
});
|
|
1043
1317
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1318
|
+
function WorktreeBadge({ worktree }) {
|
|
1319
|
+
if (worktree.orphaned) return /* @__PURE__ */ jsx(StatusBadge, {
|
|
1320
|
+
tone: "neutral",
|
|
1321
|
+
size: "xs",
|
|
1322
|
+
className: "bg-po-tint-yellow text-po-yellow",
|
|
1323
|
+
children: "Orphaned"
|
|
1324
|
+
});
|
|
1325
|
+
if (worktree.unowned) return /* @__PURE__ */ jsx(StatusBadge, {
|
|
1326
|
+
tone: "info",
|
|
1327
|
+
size: "xs",
|
|
1328
|
+
children: "Unowned"
|
|
1329
|
+
});
|
|
1330
|
+
return null;
|
|
1331
|
+
}
|
|
1332
|
+
function WorktreeRow({ worktree, compact = false, starting = false, error, disabled = false, onClose }) {
|
|
1333
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1047
1334
|
"data-testid": `git-worktree-${worktree.id}`,
|
|
1048
|
-
className: "flex min-w-0 flex-col gap-1
|
|
1335
|
+
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
1336
|
children: [
|
|
1050
1337
|
/* @__PURE__ */ jsxs("div", {
|
|
1051
|
-
className: "flex min-w-0 items-center gap-
|
|
1338
|
+
className: "flex min-w-0 items-center gap-2",
|
|
1052
1339
|
children: [
|
|
1053
|
-
/* @__PURE__ */ jsx(
|
|
1340
|
+
starting ? /* @__PURE__ */ jsx(Spinner, {
|
|
1341
|
+
className: "size-3 shrink-0 text-po-dim",
|
|
1342
|
+
label: "Starting session"
|
|
1343
|
+
}) : /* @__PURE__ */ jsx(Dot, {
|
|
1344
|
+
size: "md",
|
|
1345
|
+
tone: worktree.orphaned ? "yellow" : worktree.unowned ? "blue" : "green"
|
|
1346
|
+
}),
|
|
1054
1347
|
/* @__PURE__ */ jsx("span", {
|
|
1055
|
-
|
|
1348
|
+
title: worktree.branch,
|
|
1349
|
+
className: "min-w-0 flex-1 truncate font-mono text-base font-medium text-po-hi",
|
|
1056
1350
|
children: worktree.branch
|
|
1057
1351
|
}),
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
children: "
|
|
1067
|
-
})
|
|
1352
|
+
/* @__PURE__ */ jsx(WorktreeBadge, { worktree }),
|
|
1353
|
+
!compact && /* @__PURE__ */ jsx(Button, {
|
|
1354
|
+
variant: "ghost",
|
|
1355
|
+
size: "sm",
|
|
1356
|
+
onClick: onClose,
|
|
1357
|
+
disabled: starting || disabled,
|
|
1358
|
+
"data-testid": `git-worktree-close-${worktree.id}`,
|
|
1359
|
+
"aria-label": `Close ${worktree.branch}`,
|
|
1360
|
+
children: "Close"
|
|
1361
|
+
})
|
|
1068
1362
|
]
|
|
1069
1363
|
}),
|
|
1070
|
-
/* @__PURE__ */ jsx("
|
|
1071
|
-
className: "
|
|
1364
|
+
!compact && /* @__PURE__ */ jsx("p", {
|
|
1365
|
+
className: "truncate pl-4 font-mono text-sm text-po-dim",
|
|
1366
|
+
title: worktree.path,
|
|
1072
1367
|
children: worktree.path
|
|
1073
1368
|
}),
|
|
1074
|
-
/* @__PURE__ */
|
|
1075
|
-
className: "
|
|
1076
|
-
children:
|
|
1077
|
-
variant: "danger-outline",
|
|
1078
|
-
size: "xs",
|
|
1079
|
-
disabled: busy,
|
|
1080
|
-
"data-testid": `git-worktree-close-${worktree.id}`,
|
|
1081
|
-
className: "px-2 ",
|
|
1082
|
-
onClick: () => requestWorktreeClose(sessionId, worktree.id),
|
|
1083
|
-
children: "close"
|
|
1084
|
-
})]
|
|
1369
|
+
starting && /* @__PURE__ */ jsx("p", {
|
|
1370
|
+
className: "pl-4 text-sm text-po-dim",
|
|
1371
|
+
children: "Starting session..."
|
|
1085
1372
|
}),
|
|
1086
|
-
|
|
1373
|
+
!compact && (worktree.orphaned || worktree.unowned) && /* @__PURE__ */ jsx("p", {
|
|
1374
|
+
className: "pl-4 text-sm text-po-dim first-letter:uppercase",
|
|
1375
|
+
children: worktree.orphaned ? "its folder is missing" : "its session has ended"
|
|
1376
|
+
}),
|
|
1377
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
1087
1378
|
role: "alert",
|
|
1088
1379
|
"data-testid": "git-worktree-error",
|
|
1089
|
-
className: "text-sm text-po-red",
|
|
1380
|
+
className: "pl-4 text-sm text-po-red",
|
|
1090
1381
|
children: error
|
|
1091
1382
|
})
|
|
1092
1383
|
]
|
|
@@ -1264,56 +1555,6 @@ var gitChanges_web_default = defineChannelFile(gitChangesChannel);
|
|
|
1264
1555
|
//#region src/extensions/workspaces/sessions/(frontend)/channel/gitWorktrees.web.ts
|
|
1265
1556
|
var gitWorktrees_web_default = defineChannelFile(worktreesChannel);
|
|
1266
1557
|
//#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
1558
|
//#region src/extensions/workspaces/sessions/(frontend)/_lib/gitReviewApi.ts
|
|
1318
1559
|
/**
|
|
1319
1560
|
* The review tab's half of the session API: the changed-file list and one
|
|
@@ -1370,16 +1611,34 @@ async function fetchReviewFile(sessionId, path, signal) {
|
|
|
1370
1611
|
const gitReview = defineSessionStore({ comments: [] });
|
|
1371
1612
|
function addReviewComment(sessionId, comment) {
|
|
1372
1613
|
const id = globalThis.crypto.randomUUID();
|
|
1373
|
-
gitReview.update(sessionId, (current) => ({
|
|
1374
|
-
...
|
|
1375
|
-
|
|
1376
|
-
|
|
1614
|
+
gitReview.update(sessionId, (current) => ({
|
|
1615
|
+
...current,
|
|
1616
|
+
comments: [...current.comments, {
|
|
1617
|
+
...comment,
|
|
1618
|
+
id
|
|
1619
|
+
}]
|
|
1620
|
+
}));
|
|
1377
1621
|
}
|
|
1378
1622
|
function removeReviewComment(sessionId, id) {
|
|
1379
|
-
gitReview.update(sessionId, (current) => ({
|
|
1623
|
+
gitReview.update(sessionId, (current) => ({
|
|
1624
|
+
...current,
|
|
1625
|
+
comments: current.comments.filter((comment) => comment.id !== id)
|
|
1626
|
+
}));
|
|
1380
1627
|
}
|
|
1381
1628
|
function clearReviewComments(sessionId) {
|
|
1382
|
-
gitReview.update(sessionId, () => ({
|
|
1629
|
+
gitReview.update(sessionId, (current) => ({
|
|
1630
|
+
...current,
|
|
1631
|
+
comments: []
|
|
1632
|
+
}));
|
|
1633
|
+
}
|
|
1634
|
+
function focusReviewFile(sessionId, path) {
|
|
1635
|
+
gitReview.update(sessionId, (current) => ({
|
|
1636
|
+
...current,
|
|
1637
|
+
focus: {
|
|
1638
|
+
path,
|
|
1639
|
+
at: Date.now()
|
|
1640
|
+
}
|
|
1641
|
+
}));
|
|
1383
1642
|
}
|
|
1384
1643
|
//#endregion
|
|
1385
1644
|
//#region src/extensions/workspaces/sessions/(frontend)/_lib/gitReviewView.ts
|
|
@@ -1411,6 +1670,23 @@ function conflictPrompt(branch, conflicts) {
|
|
|
1411
1670
|
].join("\n");
|
|
1412
1671
|
}
|
|
1413
1672
|
/**
|
|
1673
|
+
* The one sync the bar fills in: pull while the branch is behind (a push would
|
|
1674
|
+
* be rejected), push while it is only ahead or has no upstream yet. Nothing
|
|
1675
|
+
* while a rebase is paused, detached, or already level.
|
|
1676
|
+
*/
|
|
1677
|
+
function suggestedSyncAction(changes) {
|
|
1678
|
+
if (changes?.branch === void 0 || changes.rebase !== void 0) return void 0;
|
|
1679
|
+
if (changes.upstream === void 0) return "push";
|
|
1680
|
+
if (changes.upstream.behind > 0) return "pull";
|
|
1681
|
+
return changes.upstream.ahead > 0 ? "push" : void 0;
|
|
1682
|
+
}
|
|
1683
|
+
/** "2 to push, 1 new on origin/feat/git", or "Up to date with origin/feat/git". */
|
|
1684
|
+
function upstreamSummary(upstream) {
|
|
1685
|
+
if (upstream === void 0) return "No upstream yet";
|
|
1686
|
+
const parts = [...upstream.ahead > 0 ? [`${String(upstream.ahead)} to push`] : [], ...upstream.behind > 0 ? [`${String(upstream.behind)} new on ${upstream.ref}`] : []];
|
|
1687
|
+
return parts.length === 0 ? `Up to date with ${upstream.ref}` : parts.join(", ");
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1414
1690
|
* Hands one deferred follow-up to the session. Returns the reason it failed,
|
|
1415
1691
|
* or undefined once sent. The sender throws when the session is disconnected.
|
|
1416
1692
|
*/
|
|
@@ -1426,485 +1702,302 @@ function deliverToAgent(send, sessionId, message) {
|
|
|
1426
1702
|
}
|
|
1427
1703
|
}
|
|
1428
1704
|
//#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
1705
|
//#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
|
-
}
|
|
1706
|
+
const status = {
|
|
1707
|
+
added: [
|
|
1708
|
+
"A",
|
|
1709
|
+
"text-po-green",
|
|
1710
|
+
"Added"
|
|
1711
|
+
],
|
|
1712
|
+
modified: [
|
|
1713
|
+
"M",
|
|
1714
|
+
"text-po-yellow",
|
|
1715
|
+
"Modified"
|
|
1716
|
+
],
|
|
1717
|
+
deleted: [
|
|
1718
|
+
"D",
|
|
1719
|
+
"text-po-red",
|
|
1720
|
+
"Deleted"
|
|
1721
|
+
],
|
|
1722
|
+
untracked: [
|
|
1723
|
+
"U",
|
|
1724
|
+
"text-po-blue",
|
|
1725
|
+
"Untracked"
|
|
1726
|
+
],
|
|
1727
|
+
conflicted: [
|
|
1728
|
+
"C",
|
|
1729
|
+
"text-po-red",
|
|
1730
|
+
"Conflicted"
|
|
1731
|
+
]
|
|
1533
1732
|
};
|
|
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
1733
|
function GitReviewBrowser({ files, activePath, commentCounts, onPick }) {
|
|
1538
1734
|
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] : []);
|
|
1735
|
+
const shown = files.filter((f) => f.path.toLowerCase().includes(query.trim().toLowerCase()));
|
|
1544
1736
|
const listRef = useRef(null);
|
|
1545
1737
|
useEffect(() => {
|
|
1546
1738
|
if (activePath === void 0) return;
|
|
1547
|
-
|
|
1739
|
+
listRef.current?.querySelector(`[data-path="${CSS.escape(activePath)}"]`)?.scrollIntoView({ block: "nearest" });
|
|
1548
1740
|
}, [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
1741
|
/** Arrow keys pick the neighbouring row and move focus with it, so the diff follows the cursor. */
|
|
1558
1742
|
const onKeyDown = (event) => {
|
|
1559
1743
|
if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
|
|
1560
|
-
if (
|
|
1744
|
+
if (shown.length === 0) return;
|
|
1561
1745
|
event.preventDefault();
|
|
1562
|
-
const current =
|
|
1746
|
+
const current = shown.findIndex((file) => file.path === activePath);
|
|
1563
1747
|
const step = event.key === "ArrowDown" ? 1 : -1;
|
|
1564
|
-
const next = current === -1 ? 0 : (current + step +
|
|
1565
|
-
const file =
|
|
1748
|
+
const next = current === -1 ? 0 : (current + step + shown.length) % shown.length;
|
|
1749
|
+
const file = shown[next];
|
|
1566
1750
|
if (file === void 0) return;
|
|
1567
1751
|
onPick(file.path);
|
|
1568
1752
|
event.currentTarget.closest("[data-git-review-list]")?.querySelectorAll("[data-git-review-row]")[next]?.focus();
|
|
1569
1753
|
};
|
|
1570
1754
|
return /* @__PURE__ */ jsxs("nav", {
|
|
1755
|
+
"aria-label": "Changed files",
|
|
1571
1756
|
"data-testid": "git-review-browser",
|
|
1572
|
-
|
|
1573
|
-
className: "flex min-h-0 w-64 shrink-0 flex-col border-r border-po-border-soft",
|
|
1757
|
+
className: "flex min-h-0 w-80 shrink-0 flex-col border-r border-po-border-soft",
|
|
1574
1758
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
1575
|
-
className: "flex
|
|
1759
|
+
className: "flex flex-col gap-3 border-b border-po-border-soft p-3",
|
|
1576
1760
|
children: [/* @__PURE__ */ jsx(Input, {
|
|
1577
1761
|
"data-testid": "git-review-search",
|
|
1578
|
-
"aria-label": "search changed file paths",
|
|
1579
1762
|
size: "sm",
|
|
1580
|
-
|
|
1581
|
-
placeholder: "
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
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]
|
|
1763
|
+
"aria-label": "Search paths",
|
|
1764
|
+
placeholder: "Search paths",
|
|
1765
|
+
value: query,
|
|
1766
|
+
onChange: (e) => setQuery(e.target.value)
|
|
1767
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
1768
|
+
className: "text-sm text-po-dim",
|
|
1769
|
+
children: [shown.length, " changed"]
|
|
1596
1770
|
})]
|
|
1597
1771
|
}), /* @__PURE__ */ jsxs("div", {
|
|
1598
1772
|
ref: listRef,
|
|
1599
1773
|
"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",
|
|
1774
|
+
className: "min-h-0 flex-1 overflow-y-auto",
|
|
1775
|
+
children: [shown.map((f) => {
|
|
1776
|
+
const slash = f.path.lastIndexOf("/");
|
|
1777
|
+
const count = commentCounts[f.path] ?? 0;
|
|
1778
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
1629
1779
|
"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),
|
|
1780
|
+
"data-path": f.path,
|
|
1781
|
+
"data-testid": `git-review-browser-file-${f.path}`,
|
|
1635
1782
|
onKeyDown,
|
|
1636
|
-
|
|
1637
|
-
|
|
1783
|
+
title: f.path,
|
|
1784
|
+
"aria-current": activePath === f.path ? "true" : void 0,
|
|
1785
|
+
onClick: () => onPick(f.path),
|
|
1786
|
+
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
1787
|
children: [
|
|
1639
1788
|
/* @__PURE__ */ jsx("span", {
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
children: mark.letter
|
|
1643
|
-
}),
|
|
1644
|
-
/* @__PURE__ */ jsx("span", {
|
|
1645
|
-
className: `min-w-0 flex-1 truncate text-base text-po-hi ${active ? "font-semibold" : ""} ${file.status === "deleted" ? "line-through opacity-70" : ""}`,
|
|
1646
|
-
children: baseName(file.path)
|
|
1789
|
+
className: `pt-0.5 text-sm font-semibold ${status[f.status][1]}`,
|
|
1790
|
+
children: status[f.status][0]
|
|
1647
1791
|
}),
|
|
1648
|
-
|
|
1649
|
-
className: "
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1792
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1793
|
+
className: "min-w-0 flex-1",
|
|
1794
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1795
|
+
className: "block truncate text-base text-po-text",
|
|
1796
|
+
children: f.path.slice(slash + 1)
|
|
1797
|
+
}), slash < 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
1798
|
+
className: "block truncate font-mono text-sm text-po-dim",
|
|
1799
|
+
children: f.path.slice(0, slash)
|
|
1800
|
+
})]
|
|
1655
1801
|
}),
|
|
1656
|
-
|
|
1657
|
-
className: "shrink-0
|
|
1658
|
-
children: "
|
|
1659
|
-
}) : /* @__PURE__ */ jsxs("span", {
|
|
1660
|
-
className: "shrink-0 text-2xs",
|
|
1661
|
-
children: [
|
|
1802
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1803
|
+
className: "flex shrink-0 flex-col items-end gap-1 text-sm",
|
|
1804
|
+
children: [/* @__PURE__ */ jsxs("span", { children: [
|
|
1662
1805
|
/* @__PURE__ */ jsxs("span", {
|
|
1663
1806
|
className: "text-po-green",
|
|
1664
|
-
children: ["+",
|
|
1807
|
+
children: ["+", f.added]
|
|
1665
1808
|
}),
|
|
1666
1809
|
" ",
|
|
1667
1810
|
/* @__PURE__ */ jsxs("span", {
|
|
1668
1811
|
className: "text-po-red",
|
|
1669
|
-
children: ["-",
|
|
1812
|
+
children: ["-", f.removed]
|
|
1670
1813
|
})
|
|
1671
|
-
]
|
|
1814
|
+
] }), count > 0 && /* @__PURE__ */ jsxs("span", {
|
|
1815
|
+
className: "flex items-center gap-1 text-po-blue",
|
|
1816
|
+
children: [/* @__PURE__ */ jsx(MessageIcon, { className: "h-3 w-3" }), count]
|
|
1817
|
+
})]
|
|
1672
1818
|
})
|
|
1673
1819
|
]
|
|
1674
|
-
},
|
|
1675
|
-
}),
|
|
1676
|
-
className: "
|
|
1677
|
-
children: "
|
|
1678
|
-
})
|
|
1820
|
+
}, f.path);
|
|
1821
|
+
}), shown.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
1822
|
+
className: "p-3 text-sm text-po-dim",
|
|
1823
|
+
children: "No paths match."
|
|
1824
|
+
})]
|
|
1679
1825
|
})]
|
|
1680
1826
|
});
|
|
1681
1827
|
}
|
|
1682
1828
|
//#endregion
|
|
1683
1829
|
//#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;
|
|
1830
|
+
function GitSyncBar({ secondary = false, ...props }) {
|
|
1831
|
+
const c = props.changes;
|
|
1832
|
+
const paused = c?.rebase !== void 0;
|
|
1833
|
+
const busy = props.pending !== void 0;
|
|
1834
|
+
const rejected = props.errorTarget?.action === "push" && props.errorTarget.forceRequired;
|
|
1835
|
+
const suggested = suggestedSyncAction(c);
|
|
1836
|
+
const upstream = upstreamSummary(c?.upstream);
|
|
1707
1837
|
return /* @__PURE__ */ jsxs("div", {
|
|
1708
1838
|
"data-testid": "git-sync-bar",
|
|
1709
|
-
className: "flex
|
|
1839
|
+
className: "flex flex-col gap-3 border-b border-po-border-soft p-4",
|
|
1710
1840
|
children: [
|
|
1711
1841
|
/* @__PURE__ */ jsxs("div", {
|
|
1712
|
-
className: "flex
|
|
1842
|
+
className: "flex flex-wrap items-center gap-3 text-sm",
|
|
1713
1843
|
children: [
|
|
1714
|
-
/* @__PURE__ */
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
})
|
|
1734
|
-
]
|
|
1844
|
+
/* @__PURE__ */ jsx(BranchIcon, { className: "h-4 w-4 text-po-dim" }),
|
|
1845
|
+
/* @__PURE__ */ jsx("span", {
|
|
1846
|
+
"data-testid": "git-review-branch",
|
|
1847
|
+
className: "font-mono text-base font-semibold text-po-hi",
|
|
1848
|
+
children: c?.branch ?? "Detached HEAD"
|
|
1849
|
+
}),
|
|
1850
|
+
/* @__PURE__ */ jsx("span", {
|
|
1851
|
+
className: "text-po-dim",
|
|
1852
|
+
children: c?.base === void 0 ? "Uncommitted changes" : /* @__PURE__ */ jsxs(Fragment, { children: ["vs ", /* @__PURE__ */ jsx("span", {
|
|
1853
|
+
className: "font-mono",
|
|
1854
|
+
children: c.base
|
|
1855
|
+
})] })
|
|
1856
|
+
}),
|
|
1857
|
+
props.mergeBase && /* @__PURE__ */ jsxs("span", {
|
|
1858
|
+
className: "text-po-dim",
|
|
1859
|
+
children: ["from ", /* @__PURE__ */ jsx("span", {
|
|
1860
|
+
className: "font-mono",
|
|
1861
|
+
children: props.mergeBase
|
|
1862
|
+
})]
|
|
1735
1863
|
}),
|
|
1736
|
-
|
|
1864
|
+
c === void 0 ? null : /* @__PURE__ */ jsxs("span", {
|
|
1737
1865
|
"data-testid": "git-review-totals",
|
|
1738
|
-
className: "
|
|
1866
|
+
className: "contents",
|
|
1739
1867
|
children: [
|
|
1740
1868
|
/* @__PURE__ */ jsxs("span", {
|
|
1741
|
-
className: "
|
|
1742
|
-
children: ["+",
|
|
1869
|
+
className: "text-po-green",
|
|
1870
|
+
children: ["+", c?.added]
|
|
1743
1871
|
}),
|
|
1744
1872
|
/* @__PURE__ */ jsxs("span", {
|
|
1745
|
-
className: "
|
|
1746
|
-
children: ["-",
|
|
1873
|
+
className: "text-po-red",
|
|
1874
|
+
children: ["-", c?.removed]
|
|
1747
1875
|
}),
|
|
1748
1876
|
/* @__PURE__ */ jsxs("span", {
|
|
1749
1877
|
className: "text-po-dim",
|
|
1750
1878
|
children: [
|
|
1751
|
-
|
|
1752
|
-
|
|
1879
|
+
c?.files,
|
|
1880
|
+
c?.truncated ? "+" : "",
|
|
1753
1881
|
" ",
|
|
1754
|
-
|
|
1882
|
+
c?.files === 1 ? "file" : "files"
|
|
1755
1883
|
]
|
|
1756
1884
|
})
|
|
1757
1885
|
]
|
|
1758
1886
|
}),
|
|
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
|
-
]
|
|
1887
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
1888
|
+
/* @__PURE__ */ jsx("span", {
|
|
1889
|
+
className: "text-po-dim",
|
|
1890
|
+
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", {
|
|
1891
|
+
className: "font-mono",
|
|
1892
|
+
children: c.upstream.ref
|
|
1893
|
+
})] })
|
|
1774
1894
|
}),
|
|
1775
|
-
/* @__PURE__ */
|
|
1776
|
-
className: "flex
|
|
1895
|
+
/* @__PURE__ */ jsx("div", {
|
|
1896
|
+
className: "flex gap-1.5",
|
|
1777
1897
|
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
|
-
]
|
|
1898
|
+
"pull",
|
|
1899
|
+
"push",
|
|
1900
|
+
"rebase"
|
|
1901
|
+
].map((action) => /* @__PURE__ */ jsx(Button, {
|
|
1902
|
+
"data-testid": `git-sync-${action}`,
|
|
1903
|
+
size: "sm",
|
|
1904
|
+
variant: !busy && !paused && !rejected && !props.error && c?.branch !== void 0 && suggested === action ? secondary ? "outline" : "primary" : "ghost",
|
|
1905
|
+
disabled: busy || paused || action !== "rebase" && c?.branch === void 0 || action === "pull" && c?.upstream === void 0 || action === "rebase" && c?.base === void 0,
|
|
1906
|
+
onClick: () => props.onSync(action),
|
|
1907
|
+
children: action === "pull" ? "Pull" : action === "push" ? "Push" : "Rebase"
|
|
1908
|
+
}, action))
|
|
1803
1909
|
})
|
|
1804
1910
|
]
|
|
1805
1911
|
}),
|
|
1806
|
-
|
|
1912
|
+
busy && /* @__PURE__ */ jsxs("div", {
|
|
1807
1913
|
"data-testid": "git-sync-pending",
|
|
1808
|
-
className: "flex
|
|
1914
|
+
className: "flex items-center gap-2 text-sm text-po-dim",
|
|
1809
1915
|
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
|
-
]
|
|
1916
|
+
className: "h-4 w-4 text-po-dim",
|
|
1917
|
+
label: props.pending
|
|
1918
|
+
}), props.pending]
|
|
1838
1919
|
}),
|
|
1839
|
-
|
|
1920
|
+
rejected ? /* @__PURE__ */ jsxs("div", {
|
|
1840
1921
|
"data-testid": "git-push-force-confirm",
|
|
1841
|
-
className: "flex
|
|
1922
|
+
className: "flex flex-wrap items-center gap-3 rounded-md border border-po-edge-yellow bg-po-tint-yellow p-3",
|
|
1842
1923
|
children: [
|
|
1843
1924
|
/* @__PURE__ */ jsx("span", {
|
|
1844
|
-
className: "
|
|
1845
|
-
children: "
|
|
1925
|
+
className: "flex-1 text-base text-po-text",
|
|
1926
|
+
children: "origin has commits you have not pulled yet."
|
|
1846
1927
|
}),
|
|
1847
1928
|
/* @__PURE__ */ jsx(Button, {
|
|
1929
|
+
size: "sm",
|
|
1848
1930
|
variant: "danger-outline",
|
|
1849
|
-
size: "xs",
|
|
1850
1931
|
"data-testid": "git-push-force",
|
|
1851
1932
|
disabled: busy,
|
|
1852
|
-
onClick: onForcePush,
|
|
1853
|
-
children: "
|
|
1933
|
+
onClick: props.onForcePush,
|
|
1934
|
+
children: "Force push with lease"
|
|
1854
1935
|
}),
|
|
1855
1936
|
/* @__PURE__ */ jsx(Button, {
|
|
1937
|
+
size: "sm",
|
|
1856
1938
|
variant: "ghost",
|
|
1857
|
-
size: "xs",
|
|
1858
1939
|
"data-testid": "git-push-force-cancel",
|
|
1859
|
-
onClick: onDismissError,
|
|
1860
|
-
children: "
|
|
1940
|
+
onClick: props.onDismissError,
|
|
1941
|
+
children: "Not now"
|
|
1861
1942
|
})
|
|
1862
1943
|
]
|
|
1863
|
-
}) :
|
|
1864
|
-
|
|
1944
|
+
}) : props.error && /* @__PURE__ */ jsxs("div", {
|
|
1945
|
+
role: "alert",
|
|
1946
|
+
"data-testid": "git-sync-error",
|
|
1947
|
+
className: "flex items-center gap-3 rounded-md border border-po-edge-red bg-po-tint-red p-3 text-base text-po-red",
|
|
1948
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1949
|
+
className: "flex-1",
|
|
1950
|
+
children: props.error
|
|
1951
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
1952
|
+
size: "sm",
|
|
1953
|
+
variant: "ghost",
|
|
1954
|
+
onClick: props.onDismissError,
|
|
1955
|
+
children: "Dismiss"
|
|
1956
|
+
})]
|
|
1957
|
+
}),
|
|
1958
|
+
paused && /* @__PURE__ */ jsxs("div", {
|
|
1865
1959
|
"data-testid": "git-rebase-banner",
|
|
1866
|
-
className: "flex
|
|
1960
|
+
className: "flex flex-col gap-2 rounded-md border border-po-edge-yellow bg-po-tint-yellow p-3",
|
|
1867
1961
|
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",
|
|
1962
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(StatusBadge, {
|
|
1963
|
+
tone: "running",
|
|
1964
|
+
size: "xs",
|
|
1965
|
+
children: "Rebase paused"
|
|
1966
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
1967
|
+
className: "ml-2 text-base text-po-text",
|
|
1968
|
+
children: [
|
|
1969
|
+
"Conflicts in ",
|
|
1970
|
+
c.rebase?.conflicts.length,
|
|
1971
|
+
" ",
|
|
1972
|
+
c.rebase?.conflicts.length === 1 ? "file" : "files"
|
|
1973
|
+
]
|
|
1974
|
+
})] }),
|
|
1975
|
+
c.rebase?.conflicts.map((path) => /* @__PURE__ */ jsx("p", {
|
|
1976
|
+
className: "font-mono text-sm text-po-dim",
|
|
1977
|
+
children: path
|
|
1978
|
+
}, path)),
|
|
1979
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1980
|
+
className: "flex items-center gap-2",
|
|
1888
1981
|
children: [
|
|
1889
1982
|
/* @__PURE__ */ jsx(Button, {
|
|
1890
|
-
variant: "primary",
|
|
1891
|
-
size: "
|
|
1983
|
+
variant: secondary ? "outline" : "primary",
|
|
1984
|
+
size: "sm",
|
|
1892
1985
|
"data-testid": "git-rebase-ask-agent",
|
|
1893
1986
|
disabled: busy,
|
|
1894
|
-
onClick: onAskAgent,
|
|
1895
|
-
children: "
|
|
1987
|
+
onClick: props.onAskAgent,
|
|
1988
|
+
children: "Ask Po to resolve"
|
|
1896
1989
|
}),
|
|
1897
1990
|
/* @__PURE__ */ jsx(Button, {
|
|
1898
1991
|
variant: "danger-outline",
|
|
1899
|
-
size: "
|
|
1992
|
+
size: "sm",
|
|
1900
1993
|
"data-testid": "git-rebase-abort",
|
|
1901
1994
|
disabled: busy,
|
|
1902
|
-
onClick: () => onSync("abort-rebase"),
|
|
1903
|
-
children: "
|
|
1995
|
+
onClick: () => props.onSync("abort-rebase"),
|
|
1996
|
+
children: "Abort rebase"
|
|
1904
1997
|
}),
|
|
1905
1998
|
/* @__PURE__ */ jsx("span", {
|
|
1906
1999
|
className: "text-sm text-po-dim",
|
|
1907
|
-
children: "
|
|
2000
|
+
children: "Abort discards conflict resolution in progress."
|
|
1908
2001
|
})
|
|
1909
2002
|
]
|
|
1910
2003
|
})
|
|
@@ -1915,29 +2008,7 @@ function GitSyncBar({ changes, mergeBase, pending, error, errorTarget, onSync, o
|
|
|
1915
2008
|
}
|
|
1916
2009
|
//#endregion
|
|
1917
2010
|
//#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
2011
|
const NO_FILES = [];
|
|
1940
|
-
const NO_PATHS = /* @__PURE__ */ new Set();
|
|
1941
2012
|
/** Stable, URL-safe element id for one file's section. */
|
|
1942
2013
|
function sectionId(index) {
|
|
1943
2014
|
return `git-review-file-${String(index)}`;
|
|
@@ -1945,122 +2016,30 @@ function sectionId(index) {
|
|
|
1945
2016
|
function sideOf(row) {
|
|
1946
2017
|
return row.marker === "-" ? "old" : "new";
|
|
1947
2018
|
}
|
|
1948
|
-
function GitReviewView({ sync, summary, files, comments, onLoadFile, onAddComment, onRemoveComment, onSendReview, onDiscard, onDraftChange, sendError, initialDraft }) {
|
|
2019
|
+
function GitReviewView({ sync, summary, files, comments, onLoadFile, onAddComment, onRemoveComment, onSendReview, onDiscard, onDraftChange, sendError, initialDraft, initialPath }) {
|
|
1949
2020
|
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);
|
|
2021
|
+
const [picked, setPicked] = useState(initialPath ?? initialDraft?.path);
|
|
1961
2022
|
const activePath = entries.some((entry) => entry.path === picked) ? picked : entries[0]?.path;
|
|
2023
|
+
const index = entries.findIndex((entry) => entry.path === activePath);
|
|
2024
|
+
const file = entries[index];
|
|
1962
2025
|
const [draft, setDraft] = useState(initialDraft);
|
|
1963
2026
|
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);
|
|
2027
|
+
if (activePath !== void 0 && files[activePath] === void 0) onLoadFile(activePath);
|
|
2032
2028
|
}, [
|
|
2033
|
-
|
|
2029
|
+
activePath,
|
|
2034
2030
|
files,
|
|
2035
|
-
near,
|
|
2036
|
-
navigation,
|
|
2037
|
-
draft,
|
|
2038
|
-
comments,
|
|
2039
|
-
openOverride,
|
|
2040
2031
|
onLoadFile
|
|
2041
2032
|
]);
|
|
2042
2033
|
const commentCounts = {};
|
|
2043
2034
|
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
2035
|
const pick = (path) => {
|
|
2052
2036
|
if (!entries.some((entry) => entry.path === path)) return;
|
|
2053
|
-
|
|
2037
|
+
if (path !== activePath && draft !== void 0) {
|
|
2038
|
+
if (!window.confirm("Discard the unsent comment and open another file?")) return;
|
|
2039
|
+
setDraft(void 0);
|
|
2040
|
+
onDraftChange?.(false);
|
|
2041
|
+
}
|
|
2054
2042
|
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
2043
|
};
|
|
2065
2044
|
const submitDraft = (body) => {
|
|
2066
2045
|
if (draft === void 0) return;
|
|
@@ -2082,87 +2061,116 @@ function GitReviewView({ sync, summary, files, comments, onLoadFile, onAddCommen
|
|
|
2082
2061
|
children: [
|
|
2083
2062
|
/* @__PURE__ */ jsx(GitSyncBar, {
|
|
2084
2063
|
...sync,
|
|
2064
|
+
secondary: comments.length > 0,
|
|
2085
2065
|
...summary.state === "ready" && summary.summary.mergeBase !== void 0 ? { mergeBase: summary.summary.mergeBase } : {}
|
|
2086
2066
|
}),
|
|
2087
2067
|
summary.state === "loading" ? /* @__PURE__ */ jsxs("div", {
|
|
2088
2068
|
"data-testid": "git-review-loading",
|
|
2089
|
-
className: "flex flex-1 items-
|
|
2069
|
+
className: "flex min-h-48 flex-1 items-center justify-center gap-2 text-sm text-po-dim",
|
|
2090
2070
|
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
2091
|
-
className: "h-
|
|
2092
|
-
label: "
|
|
2071
|
+
className: "h-4 w-4 text-po-dim",
|
|
2072
|
+
label: "Reading changes"
|
|
2093
2073
|
}), /* @__PURE__ */ jsx("span", {
|
|
2094
2074
|
className: "text-sm text-po-dim",
|
|
2095
|
-
children: "
|
|
2075
|
+
children: "Reading changes..."
|
|
2096
2076
|
})]
|
|
2097
|
-
}) : summary.state === "error" ? /* @__PURE__ */ jsx(
|
|
2098
|
-
role: "alert",
|
|
2077
|
+
}) : summary.state === "error" ? /* @__PURE__ */ jsx(EmptyState, {
|
|
2099
2078
|
"data-testid": "git-review-error",
|
|
2100
|
-
className: "
|
|
2101
|
-
|
|
2079
|
+
className: "min-h-48 text-po-red",
|
|
2080
|
+
title: /* @__PURE__ */ jsx("span", {
|
|
2081
|
+
className: "text-po-red",
|
|
2082
|
+
children: "Changes could not be read"
|
|
2083
|
+
}),
|
|
2084
|
+
description: summary.error
|
|
2102
2085
|
}) : !summary.summary.repository ? /* @__PURE__ */ jsx(EmptyState, {
|
|
2103
2086
|
"data-testid": "git-review-no-repository",
|
|
2104
|
-
className: "
|
|
2105
|
-
title: "
|
|
2106
|
-
description: "
|
|
2087
|
+
className: "min-h-48",
|
|
2088
|
+
title: "Not a Git checkout",
|
|
2089
|
+
description: "This session's folder is not inside a Git repository."
|
|
2107
2090
|
}) : entries.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
|
|
2108
2091
|
"data-testid": "git-review-empty",
|
|
2109
|
-
className: "
|
|
2110
|
-
title: "
|
|
2111
|
-
description: summary.summary.base === void 0 ? "
|
|
2092
|
+
className: "min-h-48",
|
|
2093
|
+
title: "No changes yet",
|
|
2094
|
+
description: summary.summary.base === void 0 ? "Nothing uncommitted in this checkout." : `Nothing differs from ${summary.summary.base}, committed or not.`
|
|
2112
2095
|
}) : /* @__PURE__ */ jsxs("div", {
|
|
2096
|
+
"data-testid": "git-review-content",
|
|
2113
2097
|
className: "flex min-h-0 flex-1",
|
|
2114
2098
|
children: [/* @__PURE__ */ jsx(GitReviewBrowser, {
|
|
2115
2099
|
files: entries,
|
|
2116
2100
|
commentCounts,
|
|
2117
2101
|
onPick: pick,
|
|
2118
2102
|
...activePath === void 0 ? {} : { activePath }
|
|
2119
|
-
}), /* @__PURE__ */ jsxs("
|
|
2120
|
-
ref: paneRef,
|
|
2103
|
+
}), /* @__PURE__ */ jsxs("main", {
|
|
2121
2104
|
"data-testid": "git-review-diffs",
|
|
2122
2105
|
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
|
-
|
|
2106
|
+
children: [
|
|
2107
|
+
summary.summary.truncated ? /* @__PURE__ */ jsx("p", {
|
|
2108
|
+
className: "px-4 pt-2 text-sm text-po-dim",
|
|
2109
|
+
children: "More files changed than the review lists; the rest are left out."
|
|
2110
|
+
}) : null,
|
|
2111
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2112
|
+
className: "flex items-center justify-between border-b border-po-border-soft px-4 py-2",
|
|
2113
|
+
children: [
|
|
2114
|
+
/* @__PURE__ */ jsx(Button, {
|
|
2115
|
+
variant: "ghost",
|
|
2116
|
+
size: "sm",
|
|
2117
|
+
disabled: index <= 0,
|
|
2118
|
+
onClick: () => pick(entries[index - 1].path),
|
|
2119
|
+
children: "Previous file"
|
|
2120
|
+
}),
|
|
2121
|
+
/* @__PURE__ */ jsxs("span", {
|
|
2122
|
+
className: "text-sm text-po-dim",
|
|
2123
|
+
children: [
|
|
2124
|
+
index + 1,
|
|
2125
|
+
" of ",
|
|
2126
|
+
entries.length
|
|
2127
|
+
]
|
|
2128
|
+
}),
|
|
2129
|
+
/* @__PURE__ */ jsx(Button, {
|
|
2130
|
+
variant: "ghost",
|
|
2131
|
+
size: "sm",
|
|
2132
|
+
disabled: index >= entries.length - 1,
|
|
2133
|
+
onClick: () => pick(entries[index + 1].path),
|
|
2134
|
+
children: "Next file"
|
|
2135
|
+
})
|
|
2136
|
+
]
|
|
2137
|
+
}),
|
|
2138
|
+
file === void 0 ? null : /* @__PURE__ */ jsx(FileSection, {
|
|
2139
|
+
id: sectionId(index),
|
|
2140
|
+
entry: file,
|
|
2141
|
+
state: files[file.path],
|
|
2142
|
+
comments: comments.filter((comment) => comment.path === file.path),
|
|
2143
|
+
draft: draft?.path === file.path ? draft.selection : void 0,
|
|
2144
|
+
onSelect: (selection) => {
|
|
2145
|
+
setDraft({
|
|
2146
|
+
path: file.path,
|
|
2147
|
+
selection
|
|
2148
|
+
});
|
|
2149
|
+
onDraftChange?.(true);
|
|
2150
|
+
},
|
|
2151
|
+
onSubmitDraft: submitDraft,
|
|
2152
|
+
onCancelDraft: () => {
|
|
2153
|
+
setDraft(void 0);
|
|
2154
|
+
onDraftChange?.(false);
|
|
2155
|
+
},
|
|
2156
|
+
onRemoveComment,
|
|
2157
|
+
onRetry: () => onLoadFile(file.path)
|
|
2158
|
+
}, file.path)
|
|
2159
|
+
]
|
|
2152
2160
|
})]
|
|
2153
2161
|
}),
|
|
2154
2162
|
/* @__PURE__ */ jsxs("div", {
|
|
2155
2163
|
"data-testid": "git-review-footer",
|
|
2156
|
-
className: "flex shrink-0 flex-wrap items-center gap-
|
|
2164
|
+
className: "flex shrink-0 flex-wrap items-center gap-3 border-t border-po-border-soft bg-po-panel px-4 py-3",
|
|
2157
2165
|
children: [
|
|
2158
2166
|
/* @__PURE__ */ jsx(MessageIcon, {
|
|
2159
2167
|
"aria-hidden": true,
|
|
2160
|
-
className: "h-
|
|
2168
|
+
className: "h-4 w-4 text-po-blue"
|
|
2161
2169
|
}),
|
|
2162
2170
|
/* @__PURE__ */ jsx("span", {
|
|
2163
2171
|
"data-testid": "git-review-comment-count",
|
|
2164
2172
|
className: "text-sm text-po-dim",
|
|
2165
|
-
children: comments.length === 0 ? "
|
|
2173
|
+
children: comments.length === 0 ? "No comments yet: select lines or click a line number" : `${String(comments.length)} ${comments.length === 1 ? "comment" : "comments"} queued`
|
|
2166
2174
|
}),
|
|
2167
2175
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
|
|
2168
2176
|
sendError === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
@@ -2173,11 +2181,11 @@ function GitReviewView({ sync, summary, files, comments, onLoadFile, onAddCommen
|
|
|
2173
2181
|
}),
|
|
2174
2182
|
/* @__PURE__ */ jsx(Button, {
|
|
2175
2183
|
variant: "ghost",
|
|
2176
|
-
size: "
|
|
2184
|
+
size: "sm",
|
|
2177
2185
|
"data-testid": "git-review-discard",
|
|
2178
2186
|
disabled: comments.length === 0,
|
|
2179
2187
|
onClick: onDiscard,
|
|
2180
|
-
children: "
|
|
2188
|
+
children: "Discard"
|
|
2181
2189
|
}),
|
|
2182
2190
|
/* @__PURE__ */ jsx(Button, {
|
|
2183
2191
|
variant: "primary",
|
|
@@ -2185,7 +2193,7 @@ function GitReviewView({ sync, summary, files, comments, onLoadFile, onAddCommen
|
|
|
2185
2193
|
"data-testid": "git-review-send",
|
|
2186
2194
|
disabled: comments.length === 0,
|
|
2187
2195
|
onClick: onSendReview,
|
|
2188
|
-
children: "
|
|
2196
|
+
children: "Send review"
|
|
2189
2197
|
})
|
|
2190
2198
|
]
|
|
2191
2199
|
})
|
|
@@ -2193,34 +2201,34 @@ function GitReviewView({ sync, summary, files, comments, onLoadFile, onAddCommen
|
|
|
2193
2201
|
});
|
|
2194
2202
|
}
|
|
2195
2203
|
const STATUS_WORD = {
|
|
2196
|
-
added: "
|
|
2197
|
-
modified: "
|
|
2198
|
-
deleted: "
|
|
2199
|
-
untracked: "
|
|
2200
|
-
conflicted: "
|
|
2204
|
+
added: "Added",
|
|
2205
|
+
modified: "Modified",
|
|
2206
|
+
deleted: "Deleted",
|
|
2207
|
+
untracked: "Untracked",
|
|
2208
|
+
conflicted: "Conflicted"
|
|
2201
2209
|
};
|
|
2202
2210
|
/** One file: a pinned header, then its diff with comments drawn under the lines they are about. */
|
|
2203
|
-
function FileSection({ id, entry, state,
|
|
2211
|
+
function FileSection({ id, entry, state, comments, draft, onSelect, onSubmitDraft, onCancelDraft, onRemoveComment, onRetry }) {
|
|
2204
2212
|
const renderAfterRow = (row) => {
|
|
2205
2213
|
const side = sideOf(row);
|
|
2206
2214
|
const here = comments.filter((comment) => (comment.side ?? "new") === side && (comment.endLine ?? comment.startLine) === row.line);
|
|
2207
2215
|
const drafting = draft !== void 0 && draft.side === side && draft.endLine === row.line;
|
|
2208
2216
|
if (here.length === 0 && !drafting) return null;
|
|
2209
2217
|
return /* @__PURE__ */ jsxs("div", {
|
|
2210
|
-
className: "
|
|
2218
|
+
className: "font-sans whitespace-normal",
|
|
2211
2219
|
children: [here.map((comment) => /* @__PURE__ */ jsxs("div", {
|
|
2212
2220
|
"data-testid": `git-review-comment-${comment.id}`,
|
|
2213
|
-
className: "flex
|
|
2221
|
+
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
2222
|
children: [
|
|
2215
2223
|
/* @__PURE__ */ jsx(MessageIcon, {
|
|
2216
2224
|
"aria-hidden": true,
|
|
2217
|
-
className: "mt-
|
|
2225
|
+
className: "mt-1 h-4 w-4 shrink-0 text-po-blue"
|
|
2218
2226
|
}),
|
|
2219
2227
|
/* @__PURE__ */ jsxs("span", {
|
|
2220
|
-
className: "flex min-w-0 flex-1 flex-col
|
|
2221
|
-
children: [/* @__PURE__ */
|
|
2222
|
-
className: "
|
|
2223
|
-
children:
|
|
2228
|
+
className: "flex min-w-0 flex-1 flex-col",
|
|
2229
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
2230
|
+
className: "mb-1 text-sm text-po-dim",
|
|
2231
|
+
children: ["You · line ", comment.endLine ?? comment.startLine]
|
|
2224
2232
|
}), /* @__PURE__ */ jsx("span", {
|
|
2225
2233
|
className: "text-base whitespace-pre-wrap text-po-text",
|
|
2226
2234
|
children: comment.body
|
|
@@ -2228,15 +2236,15 @@ function FileSection({ id, entry, state, collapsed, mounted, placeholderHeight,
|
|
|
2228
2236
|
}),
|
|
2229
2237
|
/* @__PURE__ */ jsx(Button, {
|
|
2230
2238
|
variant: "ghost",
|
|
2231
|
-
size: "
|
|
2232
|
-
"aria-label": "
|
|
2233
|
-
title: "
|
|
2239
|
+
size: "sm",
|
|
2240
|
+
"aria-label": "Remove comment",
|
|
2241
|
+
title: "Remove comment",
|
|
2234
2242
|
onClick: () => onRemoveComment(comment.id),
|
|
2235
|
-
children:
|
|
2243
|
+
children: "Remove"
|
|
2236
2244
|
})
|
|
2237
2245
|
]
|
|
2238
2246
|
}, comment.id)), drafting ? /* @__PURE__ */ jsx("div", {
|
|
2239
|
-
className: "rounded-md border border-po-border bg-po-panel",
|
|
2247
|
+
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
2248
|
children: /* @__PURE__ */ jsx(ReviewCommentDraft, {
|
|
2241
2249
|
snippet: draft.snippet,
|
|
2242
2250
|
startLine: draft.startLine,
|
|
@@ -2253,90 +2261,73 @@ function FileSection({ id, entry, state, collapsed, mounted, placeholderHeight,
|
|
|
2253
2261
|
id,
|
|
2254
2262
|
"data-review-path": entry.path,
|
|
2255
2263
|
"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",
|
|
2264
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2265
|
+
className: "flex flex-wrap items-center gap-3 border-b border-po-border-soft bg-po-panel px-4 py-3",
|
|
2263
2266
|
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
2267
|
/* @__PURE__ */ jsx("span", {
|
|
2272
|
-
className: "min-w-0 flex-1 truncate font-mono text-base
|
|
2268
|
+
className: "min-w-0 flex-1 truncate font-mono text-base text-po-hi",
|
|
2273
2269
|
children: entry.path
|
|
2274
2270
|
}),
|
|
2275
2271
|
/* @__PURE__ */ jsx("span", {
|
|
2276
|
-
className: "shrink-0 text-sm
|
|
2272
|
+
className: "shrink-0 text-sm text-po-dim",
|
|
2277
2273
|
children: STATUS_WORD[entry.status]
|
|
2278
2274
|
}),
|
|
2275
|
+
entry.binary ? null : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("span", {
|
|
2276
|
+
className: "text-sm text-po-green",
|
|
2277
|
+
children: ["+", entry.added]
|
|
2278
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
2279
|
+
className: "text-sm text-po-red",
|
|
2280
|
+
children: ["-", entry.removed]
|
|
2281
|
+
})] }),
|
|
2279
2282
|
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",
|
|
2283
|
+
className: "flex shrink-0 items-center gap-1 text-sm text-po-blue",
|
|
2288
2284
|
children: [
|
|
2289
|
-
/* @__PURE__ */
|
|
2290
|
-
|
|
2291
|
-
|
|
2285
|
+
/* @__PURE__ */ jsx(MessageIcon, {
|
|
2286
|
+
"aria-hidden": true,
|
|
2287
|
+
className: "h-3 w-3"
|
|
2292
2288
|
}),
|
|
2289
|
+
comments.length,
|
|
2293
2290
|
" ",
|
|
2294
|
-
|
|
2295
|
-
className: "text-po-red",
|
|
2296
|
-
children: ["-", entry.removed]
|
|
2297
|
-
})
|
|
2291
|
+
comments.length === 1 ? "comment" : "comments"
|
|
2298
2292
|
]
|
|
2299
2293
|
})
|
|
2300
2294
|
]
|
|
2301
|
-
}),
|
|
2302
|
-
"aria-hidden": true,
|
|
2303
|
-
style: { height: placeholderHeight }
|
|
2304
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
2295
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2305
2296
|
"data-review-body": state === void 0 || state.state === "loading" ? "loading" : "measured",
|
|
2306
|
-
style: state === void 0 || state.state === "loading" ? { minHeight: placeholderHeight } : void 0,
|
|
2307
2297
|
children: state === void 0 || state.state === "loading" ? /* @__PURE__ */ jsxs("div", {
|
|
2308
2298
|
className: "flex items-center gap-2 px-4 py-3",
|
|
2309
2299
|
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
2310
2300
|
className: "h-3 w-3 text-po-faint",
|
|
2311
|
-
label: `
|
|
2301
|
+
label: `Loading ${entry.path}`
|
|
2312
2302
|
}), /* @__PURE__ */ jsx("span", {
|
|
2313
2303
|
className: "text-sm text-po-dim",
|
|
2314
|
-
children: "
|
|
2304
|
+
children: "Loading diff..."
|
|
2315
2305
|
})]
|
|
2316
2306
|
}) : state.state === "error" ? /* @__PURE__ */ jsxs("div", {
|
|
2317
|
-
className: "flex items-center
|
|
2307
|
+
className: "flex min-h-48 flex-col items-center justify-center gap-3",
|
|
2318
2308
|
children: [/* @__PURE__ */ jsx("span", {
|
|
2319
2309
|
role: "alert",
|
|
2320
|
-
className: "text-
|
|
2310
|
+
className: "text-base text-po-red",
|
|
2321
2311
|
children: state.error
|
|
2322
2312
|
}), /* @__PURE__ */ jsx(Button, {
|
|
2323
|
-
variant: "
|
|
2324
|
-
size: "
|
|
2313
|
+
variant: "outline",
|
|
2314
|
+
size: "sm",
|
|
2325
2315
|
onClick: onRetry,
|
|
2326
|
-
children: "
|
|
2316
|
+
children: "Retry"
|
|
2327
2317
|
})]
|
|
2328
|
-
}) : state.diff.binary ? /* @__PURE__ */ jsx(
|
|
2329
|
-
className: "
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2318
|
+
}) : state.diff.binary ? /* @__PURE__ */ jsx(EmptyState, {
|
|
2319
|
+
className: "min-h-48",
|
|
2320
|
+
title: "Binary file",
|
|
2321
|
+
description: `${entry.path} cannot be shown as a text diff.`
|
|
2322
|
+
}) : state.diff.tooLarge ? /* @__PURE__ */ jsx(EmptyState, {
|
|
2323
|
+
className: "min-h-48",
|
|
2324
|
+
title: "This diff is too large to show",
|
|
2325
|
+
description: "Other changed files are still available to review."
|
|
2334
2326
|
}) : /* @__PURE__ */ jsx(DiffView, {
|
|
2335
2327
|
hunks: state.diff.hunks,
|
|
2336
2328
|
testId: `git-review-diff-${id}`,
|
|
2337
2329
|
onSelect,
|
|
2338
|
-
renderAfterRow
|
|
2339
|
-
className: "py-1"
|
|
2330
|
+
renderAfterRow
|
|
2340
2331
|
})
|
|
2341
2332
|
})]
|
|
2342
2333
|
});
|
|
@@ -2371,6 +2362,7 @@ function reviewTab() {
|
|
|
2371
2362
|
function GitReviewPanel({ sessionId, sendSessionFrame }) {
|
|
2372
2363
|
const activity = useStore(gitChanges.store, (state) => gitChanges.select(state, sessionId));
|
|
2373
2364
|
const comments = useStore(gitReview.store, (state) => gitReview.select(state, sessionId).comments);
|
|
2365
|
+
const focus = useStore(gitReview.store, (state) => gitReview.select(state, sessionId).focus);
|
|
2374
2366
|
const [summary, setSummary] = useState({ state: "loading" });
|
|
2375
2367
|
const [files, setFiles] = useState({});
|
|
2376
2368
|
const [sendError, setSendError] = useState();
|
|
@@ -2465,6 +2457,7 @@ function GitReviewPanel({ sessionId, sendSessionFrame }) {
|
|
|
2465
2457
|
if (failure === void 0) onSent?.();
|
|
2466
2458
|
};
|
|
2467
2459
|
return /* @__PURE__ */ jsx(GitReviewView, {
|
|
2460
|
+
...focus === void 0 ? {} : { initialPath: focus.path },
|
|
2468
2461
|
onDraftChange: (open) => setDraftSnapshot((current) => open ? current?.sessionId === sessionId ? current : {
|
|
2469
2462
|
sessionId,
|
|
2470
2463
|
fingerprint
|
|
@@ -2474,7 +2467,10 @@ function GitReviewPanel({ sessionId, sendSessionFrame }) {
|
|
|
2474
2467
|
...activity.pending === void 0 ? {} : { pending: activity.pending },
|
|
2475
2468
|
...error === void 0 ? {} : { error },
|
|
2476
2469
|
...activity.errorTarget === void 0 || error === void 0 ? {} : { errorTarget: activity.errorTarget },
|
|
2477
|
-
onSync: (action) =>
|
|
2470
|
+
onSync: (action) => {
|
|
2471
|
+
if (action === "abort-rebase" && !window.confirm("Abort rebase? This discards conflict resolution in progress.")) return;
|
|
2472
|
+
requestGitSync(sessionId, action);
|
|
2473
|
+
},
|
|
2478
2474
|
onForcePush: () => requestGitSync(sessionId, "push", true),
|
|
2479
2475
|
onAskAgent: () => send(conflictPrompt(branch, changes?.rebase?.conflicts ?? [])),
|
|
2480
2476
|
onDismissError: () => setDismissedError(activity.error)
|
|
@@ -2488,16 +2484,40 @@ function GitReviewPanel({ sessionId, sendSessionFrame }) {
|
|
|
2488
2484
|
onSendReview: () => send(buildReviewPrompt(comments, reviewHeading(comments.length, branch, base)), () => clearReviewComments(sessionId)),
|
|
2489
2485
|
onDiscard: () => clearReviewComments(sessionId),
|
|
2490
2486
|
...sendError === void 0 ? {} : { sendError }
|
|
2491
|
-
}, sessionId);
|
|
2487
|
+
}, `${sessionId}:${String(focus?.at ?? 0)}`);
|
|
2492
2488
|
}
|
|
2493
2489
|
//#endregion
|
|
2490
|
+
//#region src/extensions/workspaces/sessions/(frontend)/fill/GitReview.vibe-lint.finding-review.web.ts
|
|
2491
|
+
/** Lets code review findings open this checkout's diff review on their file. */
|
|
2492
|
+
var GitReview_vibe_lint_finding_review_web_default = defineFill({
|
|
2493
|
+
slot: "vibe-lint.finding-review",
|
|
2494
|
+
id: "git-review",
|
|
2495
|
+
data: {
|
|
2496
|
+
version: 1,
|
|
2497
|
+
label: "Open in review",
|
|
2498
|
+
open: (props, path) => {
|
|
2499
|
+
if (props.sessionId === null) return;
|
|
2500
|
+
focusReviewFile(props.sessionId, path);
|
|
2501
|
+
props.openTransientTab(reviewTab());
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
});
|
|
2505
|
+
//#endregion
|
|
2506
|
+
//#region src/extensions/workspaces/sessions/(frontend)/fill/Worktree.session-menu.web.tsx
|
|
2507
|
+
var Worktree_session_menu_web_default = defineFill({ component: WorktreeSessionMenu });
|
|
2508
|
+
//#endregion
|
|
2509
|
+
//#region src/extensions/workspaces/sessions/(frontend)/fill/WorktreeCreate.overlay.web.tsx
|
|
2510
|
+
var WorktreeCreate_overlay_web_default = defineFill({ component: WorktreeCreateOverlay });
|
|
2511
|
+
//#endregion
|
|
2494
2512
|
//#region src/extensions/workspaces/sessions/(frontend)/fill/_components/DiffActivitySection.tsx
|
|
2495
2513
|
/**
|
|
2496
2514
|
* The '# git' group's branch row: this session's code change against its base.
|
|
2497
2515
|
*
|
|
2498
2516
|
* DESIGN PATTERNS:
|
|
2499
|
-
* - One lane, branch and `+A -R
|
|
2500
|
-
*
|
|
2517
|
+
* - One lane, branch and `+A -R`, straight on the group body like every other
|
|
2518
|
+
* activity group: the dock already draws the card, so no inner card or heading.
|
|
2519
|
+
* Files, base, upstream and a paused rebase live in the review tab's header,
|
|
2520
|
+
* which this lane opens.
|
|
2501
2521
|
* - Stays clickable at zero, because the review tab is also where pull, push
|
|
2502
2522
|
* and rebase live.
|
|
2503
2523
|
* - Reads the focused session's checkout, whether it is a worktree or not.
|
|
@@ -2509,128 +2529,112 @@ function DiffActivitySection({ sessionId, openTransientTab }) {
|
|
|
2509
2529
|
const session = useStore(gitChanges.store, (state) => gitChanges.select(state, sessionId));
|
|
2510
2530
|
const changes = session.changes;
|
|
2511
2531
|
if (sessionId === null || changes === void 0) return null;
|
|
2512
|
-
|
|
2513
|
-
|
|
2532
|
+
return /* @__PURE__ */ jsx(DiffActivityView, {
|
|
2533
|
+
changes,
|
|
2534
|
+
pending: session.pending,
|
|
2535
|
+
onReview: () => openTransientTab(reviewTab())
|
|
2536
|
+
});
|
|
2537
|
+
}
|
|
2538
|
+
function DiffActivityView({ changes, pending, onReview, branch }) {
|
|
2539
|
+
const zero = changes?.added === 0 && changes?.removed === 0;
|
|
2540
|
+
const against = changes?.base === void 0 ? "uncommitted" : `vs ${changes.base}`;
|
|
2514
2541
|
return /* @__PURE__ */ jsxs("div", {
|
|
2515
|
-
className: "flex min-w-0
|
|
2542
|
+
className: "flex min-w-0 flex-col gap-1",
|
|
2516
2543
|
children: [/* @__PURE__ */ jsxs(Button, {
|
|
2517
2544
|
variant: "ghost",
|
|
2518
|
-
size: "
|
|
2545
|
+
size: "card",
|
|
2519
2546
|
"data-testid": "activity-diff-changes",
|
|
2520
|
-
"
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
onClick:
|
|
2524
|
-
children: [
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2547
|
+
className: "min-w-0 flex-row items-center gap-2 rounded-md px-1 py-1 font-normal hover:bg-po-panel",
|
|
2548
|
+
"aria-label": changes ? `review ${changes.branch ?? "detached HEAD"} changes: ${changes.added} added, ${changes.removed} removed ${against}` : "Changes not reported yet",
|
|
2549
|
+
title: changes ? `${changes.files} ${changes.files === 1 ? "file" : "files"} ${against}` : void 0,
|
|
2550
|
+
onClick: onReview,
|
|
2551
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
2552
|
+
className: "min-w-0 flex-1 truncate text-left font-mono text-sm text-po-hi",
|
|
2553
|
+
children: changes?.branch ?? branch ?? "detached HEAD"
|
|
2554
|
+
}), !changes || zero ? /* @__PURE__ */ jsx("span", {
|
|
2555
|
+
className: "shrink-0 text-sm text-po-dim",
|
|
2556
|
+
children: zero ? "No changes yet" : "Not reported yet"
|
|
2557
|
+
}) : /* @__PURE__ */ jsxs("span", {
|
|
2558
|
+
className: "flex shrink-0 gap-2 text-sm",
|
|
2559
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
2560
|
+
className: "text-po-green",
|
|
2531
2561
|
children: ["+", changes.added]
|
|
2532
|
-
}),
|
|
2533
|
-
|
|
2534
|
-
className: zero ? "" : "text-po-red",
|
|
2562
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
2563
|
+
className: "text-po-red",
|
|
2535
2564
|
children: ["-", changes.removed]
|
|
2536
|
-
})
|
|
2537
|
-
]
|
|
2538
|
-
}),
|
|
2539
|
-
className: "flex
|
|
2565
|
+
})]
|
|
2566
|
+
})]
|
|
2567
|
+
}), pending && /* @__PURE__ */ jsxs("p", {
|
|
2568
|
+
className: "flex items-center gap-2 px-1 text-sm text-po-dim",
|
|
2540
2569
|
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
2541
|
-
className: "
|
|
2542
|
-
label:
|
|
2570
|
+
className: "size-3",
|
|
2571
|
+
label: pending
|
|
2543
2572
|
}), /* @__PURE__ */ jsx("span", {
|
|
2544
|
-
className: "
|
|
2545
|
-
children:
|
|
2573
|
+
className: "inline-block first-letter:uppercase",
|
|
2574
|
+
children: pending
|
|
2546
2575
|
})]
|
|
2547
2576
|
})]
|
|
2548
2577
|
});
|
|
2549
2578
|
}
|
|
2550
2579
|
//#endregion
|
|
2551
2580
|
//#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
2581
|
function WorktreesActivitySection(props) {
|
|
2582
|
+
const session = useStore(worktreeActivity.store, (state) => worktreeActivity.select(state, props.sessionId));
|
|
2568
2583
|
return /* @__PURE__ */ jsxs("div", {
|
|
2569
|
-
className: "flex min-w-0 flex-col gap-
|
|
2570
|
-
children: [/* @__PURE__ */ jsx(DiffActivitySection, { ...props }), /* @__PURE__ */ jsx(
|
|
2584
|
+
className: "flex min-w-0 flex-col gap-1",
|
|
2585
|
+
children: [/* @__PURE__ */ jsx(DiffActivitySection, { ...props }), /* @__PURE__ */ jsx(WorktreesActivityView, {
|
|
2586
|
+
worktrees: session.worktrees.filter((w) => w.sessionId !== props.sessionId),
|
|
2587
|
+
pending: session.pending,
|
|
2588
|
+
onOpen: () => props.openTransientTab(worktreesTab()),
|
|
2589
|
+
onCreate: props.sessionId === null ? void 0 : () => worktreeCreateDialog.update(() => ({
|
|
2590
|
+
sessionId: props.sessionId,
|
|
2591
|
+
...props.sessionReservationId === void 0 ? {} : { reservationId: props.sessionReservationId }
|
|
2592
|
+
}))
|
|
2593
|
+
})]
|
|
2571
2594
|
});
|
|
2572
2595
|
}
|
|
2573
|
-
function
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2596
|
+
function WorktreesActivityView({ worktrees, pending, onOpen, onCreate, startingIds = [] }) {
|
|
2597
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2598
|
+
className: "flex min-w-0 flex-col gap-1",
|
|
2599
|
+
children: [pending && /* @__PURE__ */ jsxs("p", {
|
|
2600
|
+
className: "flex items-center gap-2 px-1 text-sm text-po-dim",
|
|
2601
|
+
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
2602
|
+
className: "size-3",
|
|
2603
|
+
label: pending
|
|
2604
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2605
|
+
className: "inline-block first-letter:uppercase",
|
|
2606
|
+
children: pending
|
|
2607
|
+
})]
|
|
2608
|
+
}), worktrees.length === 0 ? onCreate ? /* @__PURE__ */ jsxs(Button, {
|
|
2583
2609
|
variant: "link",
|
|
2584
2610
|
size: "sm",
|
|
2585
2611
|
"data-testid": "activity-git-create",
|
|
2586
|
-
className: "h-auto gap-1 px-
|
|
2587
|
-
onClick:
|
|
2612
|
+
className: "h-auto self-start gap-1 px-1 text-po-green",
|
|
2613
|
+
onClick: onCreate,
|
|
2588
2614
|
children: ["Create a worktree", /* @__PURE__ */ jsx(ArrowRightIcon, {
|
|
2589
2615
|
"aria-hidden": true,
|
|
2590
2616
|
className: "size-3.5"
|
|
2591
2617
|
})]
|
|
2618
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
2619
|
+
"data-testid": "activity-summary-git",
|
|
2620
|
+
className: "px-1 text-sm text-po-dim",
|
|
2621
|
+
children: "Idle"
|
|
2622
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
2623
|
+
"data-testid": "activity-git-worktrees",
|
|
2624
|
+
children: worktrees.map((w) => /* @__PURE__ */ jsx(Button, {
|
|
2625
|
+
variant: "ghost",
|
|
2626
|
+
size: "card",
|
|
2627
|
+
className: "block w-full min-w-0 p-0 text-left font-normal",
|
|
2628
|
+
"data-testid": `activity-git-worktree-${w.id}`,
|
|
2629
|
+
onClick: onOpen,
|
|
2630
|
+
children: /* @__PURE__ */ jsx(WorktreeRow, {
|
|
2631
|
+
compact: true,
|
|
2632
|
+
worktree: w,
|
|
2633
|
+
starting: startingIds.includes(w.id) || !!pending?.includes("starting") && w.sessionId === null && !w.orphaned && !w.unowned
|
|
2634
|
+
})
|
|
2635
|
+
}, w.id))
|
|
2592
2636
|
})]
|
|
2593
2637
|
});
|
|
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
2638
|
}
|
|
2635
2639
|
//#endregion
|
|
2636
2640
|
//#region src/extensions/workspaces/sessions/(frontend)/fill/Worktrees.activity.git.web.tsx
|
|
@@ -2664,49 +2668,162 @@ function startGitRuntime(runtime) {
|
|
|
2664
2668
|
var start_web_default = defineWebLifecycle(startGitRuntime);
|
|
2665
2669
|
//#endregion
|
|
2666
2670
|
//#region src/extensions/workspaces/sessions/(frontend)/tool/_components/RunWorktreeToolMessage.tsx
|
|
2667
|
-
const
|
|
2668
|
-
spawn_worktree:
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2671
|
+
const VERBS = {
|
|
2672
|
+
spawn_worktree: [
|
|
2673
|
+
"is creating",
|
|
2674
|
+
"created",
|
|
2675
|
+
"create"
|
|
2676
|
+
],
|
|
2677
|
+
close_worktree: [
|
|
2678
|
+
"is closing",
|
|
2679
|
+
"closed",
|
|
2680
|
+
"close"
|
|
2681
|
+
],
|
|
2682
|
+
list: [
|
|
2683
|
+
"is listing",
|
|
2684
|
+
"listed",
|
|
2685
|
+
"list"
|
|
2686
|
+
],
|
|
2687
|
+
status: [
|
|
2688
|
+
"is checking",
|
|
2689
|
+
"checked",
|
|
2690
|
+
"check"
|
|
2691
|
+
],
|
|
2692
|
+
merge: [
|
|
2693
|
+
"is merging",
|
|
2694
|
+
"merged",
|
|
2695
|
+
"merge"
|
|
2696
|
+
],
|
|
2697
|
+
prune: [
|
|
2698
|
+
"is pruning",
|
|
2699
|
+
"pruned",
|
|
2700
|
+
"prune"
|
|
2701
|
+
],
|
|
2702
|
+
send: [
|
|
2703
|
+
"is messaging",
|
|
2704
|
+
"messaged",
|
|
2705
|
+
"message"
|
|
2706
|
+
],
|
|
2707
|
+
messages: [
|
|
2708
|
+
"is reading messages from",
|
|
2709
|
+
"read messages from",
|
|
2710
|
+
"read messages from"
|
|
2711
|
+
]
|
|
2676
2712
|
};
|
|
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
2713
|
function RunWorktreeToolMessage({ args, output, running, isError }) {
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2714
|
+
return /* @__PURE__ */ jsx(RunWorktreeToolMessageView, {
|
|
2715
|
+
action: typeof args.action === "string" ? args.action : "",
|
|
2716
|
+
subject: typeof args.branch === "string" ? args.branch : typeof args.id === "string" ? args.id : "",
|
|
2717
|
+
output,
|
|
2718
|
+
running,
|
|
2719
|
+
isError
|
|
2720
|
+
});
|
|
2721
|
+
}
|
|
2722
|
+
function RunWorktreeToolMessageView({ action, subject = "", output = "", running = false, isError = false, defaultExpanded = false }) {
|
|
2723
|
+
const tone = running ? "neutral" : isError ? "error" : "ok";
|
|
2724
|
+
const verbs = VERBS[action] ?? [
|
|
2725
|
+
"is running",
|
|
2726
|
+
"finished",
|
|
2727
|
+
"run"
|
|
2728
|
+
];
|
|
2729
|
+
const rows = action === "list" ? output.split("\n").map((line) => line.split(/\s{2,}/)).filter((parts) => parts.length >= 3) : [];
|
|
2730
|
+
const listingCount = output.match(/^(\d+) worktree/);
|
|
2731
|
+
return /* @__PURE__ */ jsx(MessageItem, {
|
|
2732
|
+
tone,
|
|
2733
|
+
expandable: true,
|
|
2734
|
+
defaultExpanded,
|
|
2735
|
+
className: "bg-po-panel",
|
|
2736
|
+
children: ({ expanded, toggle }) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2737
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2738
|
+
className: "flex items-start gap-3 p-4",
|
|
2739
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2740
|
+
className: "min-w-0 flex-1",
|
|
2741
|
+
children: /* @__PURE__ */ jsxs("p", {
|
|
2742
|
+
className: "text-base font-semibold leading-relaxed text-po-hi",
|
|
2743
|
+
children: [
|
|
2744
|
+
"Po ",
|
|
2745
|
+
isError && !running ? `could not ${verbs[2]}` : verbs[running ? 0 : 1],
|
|
2746
|
+
" ",
|
|
2747
|
+
action === "list" && !running && !isError ? `${listingCount?.[1] ?? rows.length} ${Number(listingCount?.[1] ?? rows.length) === 1 ? "worktree" : "worktrees"}` : subject ? /* @__PURE__ */ jsx("span", {
|
|
2748
|
+
className: "font-mono",
|
|
2749
|
+
children: subject
|
|
2750
|
+
}) : "worktrees",
|
|
2751
|
+
running ? "..." : isError && output ? `: ${output.split("\n")[0]}` : ""
|
|
2752
|
+
]
|
|
2753
|
+
})
|
|
2754
|
+
}), /* @__PURE__ */ jsxs(StatusBadge, {
|
|
2755
|
+
tone,
|
|
2756
|
+
size: "xs",
|
|
2757
|
+
className: running ? "gap-1 bg-po-rail text-po-dim" : "",
|
|
2758
|
+
children: [running && /* @__PURE__ */ jsx(Spinner, {
|
|
2759
|
+
className: "size-3",
|
|
2760
|
+
label: "Running"
|
|
2761
|
+
}), running ? "Running" : isError ? "Failed" : "Completed"]
|
|
2762
|
+
})]
|
|
2763
|
+
}),
|
|
2764
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
2765
|
+
variant: "ghost",
|
|
2766
|
+
size: "sm",
|
|
2767
|
+
onClick: toggle,
|
|
2768
|
+
"aria-expanded": expanded,
|
|
2769
|
+
className: "mb-2 ml-3 text-po-dim",
|
|
2770
|
+
children: ["Details ", expanded ? /* @__PURE__ */ jsx(ChevronUpIcon, { className: "size-3" }) : /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3" })]
|
|
2771
|
+
}),
|
|
2772
|
+
expanded && /* @__PURE__ */ jsx(MessageItemBody, { children: rows.length > 0 && !isError ? /* @__PURE__ */ jsxs("table", {
|
|
2773
|
+
className: "w-full text-left text-sm",
|
|
2774
|
+
children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
|
|
2775
|
+
className: "border-b border-po-border-soft",
|
|
2776
|
+
children: [
|
|
2777
|
+
/* @__PURE__ */ jsx("th", {
|
|
2778
|
+
className: "pb-2 font-medium",
|
|
2779
|
+
children: "Branch"
|
|
2780
|
+
}),
|
|
2781
|
+
/* @__PURE__ */ jsx("th", {
|
|
2782
|
+
className: "pb-2 font-medium",
|
|
2783
|
+
children: "Path"
|
|
2784
|
+
}),
|
|
2785
|
+
/* @__PURE__ */ jsx("th", {
|
|
2786
|
+
className: "pb-2 font-medium",
|
|
2787
|
+
children: "Status"
|
|
2788
|
+
})
|
|
2789
|
+
]
|
|
2790
|
+
}) }), /* @__PURE__ */ jsx("tbody", { children: rows.map((parts, i) => {
|
|
2791
|
+
const real = parts.length >= 5;
|
|
2792
|
+
const status = parts[2];
|
|
2793
|
+
return /* @__PURE__ */ jsxs("tr", {
|
|
2794
|
+
className: "border-b border-po-border-soft",
|
|
2795
|
+
children: [
|
|
2796
|
+
/* @__PURE__ */ jsxs("td", {
|
|
2797
|
+
className: "py-3 pr-3 font-mono text-po-text",
|
|
2798
|
+
children: [real ? parts[1] : parts[0], real && /* @__PURE__ */ jsx("span", {
|
|
2799
|
+
className: "mt-1 block text-po-dim",
|
|
2800
|
+
children: parts[0]
|
|
2801
|
+
})]
|
|
2802
|
+
}),
|
|
2803
|
+
/* @__PURE__ */ jsx("td", {
|
|
2804
|
+
className: "py-3 pr-3 font-mono text-po-dim",
|
|
2805
|
+
children: real ? parts.slice(4).join(" ") : parts[1]
|
|
2806
|
+
}),
|
|
2807
|
+
/* @__PURE__ */ jsxs("td", { children: [/* @__PURE__ */ jsx(StatusBadge, {
|
|
2808
|
+
tone: status === "orphaned" ? "neutral" : status === "unowned" ? "info" : "ok",
|
|
2809
|
+
size: "xs",
|
|
2810
|
+
className: status === "orphaned" ? "bg-po-tint-yellow text-po-yellow" : "",
|
|
2811
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
2812
|
+
className: "inline-block first-letter:uppercase",
|
|
2813
|
+
children: status
|
|
2814
|
+
})
|
|
2815
|
+
}), real && /* @__PURE__ */ jsx("span", {
|
|
2816
|
+
className: "ml-2 text-po-dim",
|
|
2817
|
+
children: parts[3]
|
|
2818
|
+
})] })
|
|
2819
|
+
]
|
|
2820
|
+
}, i);
|
|
2821
|
+
}) })]
|
|
2822
|
+
}) : /* @__PURE__ */ jsx("pre", {
|
|
2823
|
+
className: `whitespace-pre-wrap font-mono text-sm ${isError ? "text-po-red" : "text-po-dim"}`,
|
|
2824
|
+
children: output
|
|
2825
|
+
}) })
|
|
2826
|
+
] })
|
|
2710
2827
|
});
|
|
2711
2828
|
}
|
|
2712
2829
|
//#endregion
|
|
@@ -2740,6 +2857,10 @@ const webPlugin = defineWebPlugin({
|
|
|
2740
2857
|
},
|
|
2741
2858
|
get fills() {
|
|
2742
2859
|
return [
|
|
2860
|
+
via({
|
|
2861
|
+
slot: "vibe-lint.finding-review",
|
|
2862
|
+
id: "git-review"
|
|
2863
|
+
}, GitReview_vibe_lint_finding_review_web_default),
|
|
2743
2864
|
via({
|
|
2744
2865
|
slot: "session-menu",
|
|
2745
2866
|
id: "worktree"
|