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