@agimon-ai/po-git 0.0.0-alpha.1 → 0.0.0-alpha.100
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 +38 -5
- package/dist/apiContracts.cjs.map +1 -1
- package/dist/apiContracts.mjs +38 -5
- package/dist/apiContracts.mjs.map +1 -1
- package/dist/extensions/native.mjs +2726 -0
- package/dist/extensions/native.mjs.map +1 -0
- package/dist/extensions/server.cjs +531 -286
- 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 +529 -284
- package/dist/extensions/server.mjs.map +1 -1
- package/dist/extensions/web.mjs +1590 -1332
- package/dist/extensions/web.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.mts +5 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{worktreeOperations-MgIVDDu3.cjs → worktreeOperations-0Jrjcbg7.cjs} +342 -139
- package/dist/worktreeOperations-0Jrjcbg7.cjs.map +1 -0
- package/dist/{worktreeOperations-CaqwGsD_.mjs → worktreeOperations-DA_dbDv8.mjs} +304 -131
- package/dist/worktreeOperations-DA_dbDv8.mjs.map +1 -0
- package/llms.txt +1 -1
- package/package.json +41 -21
- package/src/prompts/{po-use-git → codemode-how-to-git}/SKILL.md +35 -32
- package/dist/worktreeOperations-CaqwGsD_.mjs.map +0 -1
- package/dist/worktreeOperations-MgIVDDu3.cjs.map +0 -1
|
@@ -0,0 +1,2726 @@
|
|
|
1
|
+
import { createApiClient, defineActivityGroup, defineChannelFile, defineFill, defineGlobalStore, defineNativePlugin, defineSessionStore, defineWebLifecycle, nativeApiTransport, sessionApiAddress } from "@agimon-ai/po-core/native";
|
|
2
|
+
import { View } from "react-native-unistyles/components/native/View";
|
|
3
|
+
import { Text } from "react-native-unistyles/components/native/Text";
|
|
4
|
+
import { ScrollView } from "react-native-unistyles/components/native/ScrollView";
|
|
5
|
+
import { AlertIcon, Badge, BranchIcon, Button, ChevronRightIcon, Dot, FileIcon, Input, Label, ListRow, MessageIcon, SegmentedControl, Sheet, SheetBody, SheetContent, SheetHeader, SheetTitle, Spinner, SyntaxLine, Textarea, fontOf } from "@agimon-ai/po-native-components";
|
|
6
|
+
import { createElement, memo, useCallback, useEffect, useEffectEvent, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
7
|
+
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|
8
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { Pressable } from "react-native-unistyles/components/native/Pressable";
|
|
10
|
+
const api = createApiClient({
|
|
11
|
+
/** Workspace: the new-session dialog's branch list. */
|
|
12
|
+
branches: {
|
|
13
|
+
method: "GET",
|
|
14
|
+
path: "/branches",
|
|
15
|
+
response: void 0
|
|
16
|
+
},
|
|
17
|
+
deleteBranch: {
|
|
18
|
+
method: "DELETE",
|
|
19
|
+
path: "/branches",
|
|
20
|
+
response: void 0
|
|
21
|
+
},
|
|
22
|
+
openBranchFolder: {
|
|
23
|
+
method: "POST",
|
|
24
|
+
path: "/branches/open",
|
|
25
|
+
response: void 0
|
|
26
|
+
},
|
|
27
|
+
/** Workspace: a top-level worktree session on an existing or new branch. */
|
|
28
|
+
createSession: {
|
|
29
|
+
method: "POST",
|
|
30
|
+
path: "/sessions",
|
|
31
|
+
response: void 0
|
|
32
|
+
},
|
|
33
|
+
/** Workspace: that workspace's saved remote auth, never the token itself. */
|
|
34
|
+
auth: {
|
|
35
|
+
method: "GET",
|
|
36
|
+
path: "/auth",
|
|
37
|
+
query: ["repositoryId"],
|
|
38
|
+
response: void 0
|
|
39
|
+
},
|
|
40
|
+
/** Workspace: replaces that workspace's remote auth. Passkey step-up gated for remote devices. */
|
|
41
|
+
saveAuth: {
|
|
42
|
+
method: "PUT",
|
|
43
|
+
path: "/auth",
|
|
44
|
+
query: ["repositoryId"],
|
|
45
|
+
response: void 0
|
|
46
|
+
},
|
|
47
|
+
/** Session: the files this session's checkout changed against its base. */
|
|
48
|
+
review: {
|
|
49
|
+
method: "GET",
|
|
50
|
+
path: "/review",
|
|
51
|
+
response: void 0
|
|
52
|
+
},
|
|
53
|
+
/** Session: one changed file's hunks. */
|
|
54
|
+
reviewFile: {
|
|
55
|
+
method: "GET",
|
|
56
|
+
path: "/review/file",
|
|
57
|
+
query: ["path"],
|
|
58
|
+
response: void 0
|
|
59
|
+
}
|
|
60
|
+
}, {
|
|
61
|
+
scopes: [
|
|
62
|
+
"global",
|
|
63
|
+
"workspace",
|
|
64
|
+
"session"
|
|
65
|
+
],
|
|
66
|
+
basePath: "git",
|
|
67
|
+
transport: nativeApiTransport,
|
|
68
|
+
sessionAddress: sessionApiAddress
|
|
69
|
+
});
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/extensions/workspaces/(frontend)/_lib/newSessionForm.ts
|
|
72
|
+
const MAX_LISTED = 200;
|
|
73
|
+
/** Local branches first, then remote-only ones, filtered by a case-insensitive substring. */
|
|
74
|
+
function branchOptions(branches, query) {
|
|
75
|
+
const needle = query.trim().toLowerCase();
|
|
76
|
+
const matches = (text) => needle === "" || text.toLowerCase().includes(needle);
|
|
77
|
+
const local = branches.local.filter((branch) => matches(branch.name)).map((branch) => ({
|
|
78
|
+
key: `local:${branch.name}`,
|
|
79
|
+
name: branch.name,
|
|
80
|
+
current: branch.name === branches.current,
|
|
81
|
+
...branch.checkedOutAt === void 0 ? {} : { busyAt: branch.checkedOutAt }
|
|
82
|
+
}));
|
|
83
|
+
const remote = branches.remote.filter((branch) => matches(`${branch.remote}/${branch.name}`)).map((branch) => ({
|
|
84
|
+
key: `remote:${branch.remote}/${branch.name}`,
|
|
85
|
+
name: branch.name,
|
|
86
|
+
remote: branch.remote,
|
|
87
|
+
current: false
|
|
88
|
+
}));
|
|
89
|
+
return [...local, ...remote].slice(0, MAX_LISTED);
|
|
90
|
+
}
|
|
91
|
+
/** Every base a new branch can start from: the default first, then remote-tracking names, then local branches. */
|
|
92
|
+
function baseOptions(branches) {
|
|
93
|
+
return [.../* @__PURE__ */ new Set([
|
|
94
|
+
...branches.defaultBase === void 0 ? [] : [branches.defaultBase],
|
|
95
|
+
...branches.remote.map((branch) => `${branch.remote}/${branch.name}`),
|
|
96
|
+
...branches.local.map((branch) => branch.name)
|
|
97
|
+
])];
|
|
98
|
+
}
|
|
99
|
+
/** Why the form cannot be submitted yet, or undefined when it can. */
|
|
100
|
+
function formProblem(state) {
|
|
101
|
+
if (state.mode === "existing") {
|
|
102
|
+
if (state.selected === void 0) return "Pick a branch.";
|
|
103
|
+
if (state.selected.busyAt !== void 0) return `That branch is checked out at ${state.selected.busyAt}.`;
|
|
104
|
+
}
|
|
105
|
+
if (state.mode === "new" && state.newBranch.trim() === "") return "Name the new branch.";
|
|
106
|
+
}
|
|
107
|
+
/** The launch profile choice for any session kind, or undefined when none was made. */
|
|
108
|
+
function launchSelection(state) {
|
|
109
|
+
return state.profile === void 0 ? void 0 : { profile: state.profile };
|
|
110
|
+
}
|
|
111
|
+
/** The worktree create request for the form, or undefined in plain mode. */
|
|
112
|
+
function createRequest(state) {
|
|
113
|
+
const name = state.name.trim();
|
|
114
|
+
const chosen = launchSelection(state);
|
|
115
|
+
const named = {
|
|
116
|
+
...name === "" ? {} : { name },
|
|
117
|
+
...chosen === void 0 ? {} : { selection: chosen }
|
|
118
|
+
};
|
|
119
|
+
if (state.mode === "existing" && state.selected !== void 0) return {
|
|
120
|
+
mode: "existing-branch",
|
|
121
|
+
branch: state.selected.name,
|
|
122
|
+
...state.selected.remote === void 0 ? {} : { remote: state.selected.remote },
|
|
123
|
+
...named
|
|
124
|
+
};
|
|
125
|
+
if (state.mode === "new") {
|
|
126
|
+
const baseRef = state.baseRef.trim();
|
|
127
|
+
return {
|
|
128
|
+
mode: "new-branch",
|
|
129
|
+
branch: state.newBranch.trim(),
|
|
130
|
+
...baseRef === "" ? {} : { baseRef },
|
|
131
|
+
...named
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/extensions/workspaces/(frontend)/fill/_components/GitNewSessionSheet.native.tsx
|
|
137
|
+
const MODES = [
|
|
138
|
+
{
|
|
139
|
+
value: "existing",
|
|
140
|
+
label: "Existing"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
value: "new",
|
|
144
|
+
label: "New branch"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
value: "plain",
|
|
148
|
+
label: "No branch"
|
|
149
|
+
}
|
|
150
|
+
];
|
|
151
|
+
/** The native twin of GitNewSessionDialogView: the same form state and checks, in sheet parts. */
|
|
152
|
+
function GitNewSessionSheetView({ workspaceRoot, branches, loadError = "", initialForm, busy = false, error = "", onCreate }) {
|
|
153
|
+
const [form, setForm] = useState({
|
|
154
|
+
mode: "existing",
|
|
155
|
+
newBranch: "",
|
|
156
|
+
baseRef: "",
|
|
157
|
+
name: "",
|
|
158
|
+
...initialForm
|
|
159
|
+
});
|
|
160
|
+
const [query, setQuery] = useState("");
|
|
161
|
+
const [basesOpen, setBasesOpen] = useState(false);
|
|
162
|
+
const ready = branches?.repository === true && loadError === "";
|
|
163
|
+
const loading = branches === null && loadError === "";
|
|
164
|
+
const mode = ready || loading ? form.mode : "plain";
|
|
165
|
+
const state = {
|
|
166
|
+
...form,
|
|
167
|
+
mode,
|
|
168
|
+
baseRef: form.baseRef || branches?.defaultBase || ""
|
|
169
|
+
};
|
|
170
|
+
const problem = formProblem(state);
|
|
171
|
+
const update = (patch) => setForm((current) => ({
|
|
172
|
+
...current,
|
|
173
|
+
...patch
|
|
174
|
+
}));
|
|
175
|
+
const options = branches === null ? [] : branchOptions(branches, query);
|
|
176
|
+
const bases = branches === null ? [] : baseOptions(branches);
|
|
177
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SheetHeader, { children: /* @__PURE__ */ jsx(SheetTitle, { children: "New session" }) }), /* @__PURE__ */ jsxs(SheetBody, {
|
|
178
|
+
style: styles$3.body,
|
|
179
|
+
children: [
|
|
180
|
+
/* @__PURE__ */ jsx(Text, {
|
|
181
|
+
style: styles$3.root,
|
|
182
|
+
numberOfLines: 1,
|
|
183
|
+
ellipsizeMode: "middle",
|
|
184
|
+
children: workspaceRoot
|
|
185
|
+
}),
|
|
186
|
+
!ready ? /* @__PURE__ */ jsx(Text, {
|
|
187
|
+
testID: "git-new-session-plain-note",
|
|
188
|
+
style: styles$3.dim,
|
|
189
|
+
children: loading ? "Reading branches..." : loadError || "This workspace is not a git checkout. Start a plain session in this folder."
|
|
190
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SegmentedControl, {
|
|
191
|
+
accessibilityLabel: "Session kind",
|
|
192
|
+
options: MODES,
|
|
193
|
+
value: mode,
|
|
194
|
+
disabled: busy,
|
|
195
|
+
onValueChange: (value) => update({ mode: value })
|
|
196
|
+
}), mode === "existing" ? /* @__PURE__ */ jsxs(View, {
|
|
197
|
+
style: styles$3.field,
|
|
198
|
+
children: [
|
|
199
|
+
/* @__PURE__ */ jsx(Label, { children: "Branch" }),
|
|
200
|
+
/* @__PURE__ */ jsx(Input, {
|
|
201
|
+
testID: "git-new-session-branch-search",
|
|
202
|
+
accessibilityLabel: "Find a branch",
|
|
203
|
+
placeholder: "Find a branch",
|
|
204
|
+
autoCapitalize: "none",
|
|
205
|
+
autoCorrect: false,
|
|
206
|
+
value: query,
|
|
207
|
+
onChangeText: setQuery,
|
|
208
|
+
disabled: busy
|
|
209
|
+
}),
|
|
210
|
+
/* @__PURE__ */ jsxs(ScrollView, {
|
|
211
|
+
style: styles$3.list,
|
|
212
|
+
nestedScrollEnabled: true,
|
|
213
|
+
accessibilityLabel: "Branches",
|
|
214
|
+
children: [options.length === 0 ? /* @__PURE__ */ jsx(Text, {
|
|
215
|
+
style: [styles$3.dim, styles$3.pad],
|
|
216
|
+
children: "No matching branches"
|
|
217
|
+
}) : null, options.map((branch, index) => /* @__PURE__ */ jsxs(View, { children: [branch.remote !== void 0 && (index === 0 || options[index - 1]?.remote === void 0) ? /* @__PURE__ */ jsx(Text, {
|
|
218
|
+
style: [
|
|
219
|
+
styles$3.dim,
|
|
220
|
+
styles$3.pad,
|
|
221
|
+
styles$3.divider
|
|
222
|
+
],
|
|
223
|
+
children: "Remote branches"
|
|
224
|
+
}) : null, /* @__PURE__ */ jsx(ListRow, {
|
|
225
|
+
title: /* @__PURE__ */ jsx(Text, {
|
|
226
|
+
style: styles$3.mono,
|
|
227
|
+
children: branch.remote === void 0 ? branch.name : `${branch.remote}/${branch.name}`
|
|
228
|
+
}),
|
|
229
|
+
description: branch.busyAt !== void 0 ? `Checked out in ${branch.busyAt}` : branch.current ? "Current" : void 0,
|
|
230
|
+
selected: form.selected?.key === branch.key,
|
|
231
|
+
disabled: branch.busyAt !== void 0 || busy,
|
|
232
|
+
disclosure: false,
|
|
233
|
+
onPress: () => update({ selected: branch })
|
|
234
|
+
})] }, branch.key))]
|
|
235
|
+
})
|
|
236
|
+
]
|
|
237
|
+
}) : mode === "new" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(View, {
|
|
238
|
+
style: styles$3.field,
|
|
239
|
+
children: [/* @__PURE__ */ jsx(Label, { children: "Branch name" }), /* @__PURE__ */ jsx(Input, {
|
|
240
|
+
testID: "git-new-session-new-branch",
|
|
241
|
+
accessibilityLabel: "Branch name",
|
|
242
|
+
placeholder: "feature/my-change",
|
|
243
|
+
autoCapitalize: "none",
|
|
244
|
+
autoCorrect: false,
|
|
245
|
+
style: styles$3.mono,
|
|
246
|
+
value: form.newBranch,
|
|
247
|
+
onChangeText: (newBranch) => update({ newBranch }),
|
|
248
|
+
disabled: busy
|
|
249
|
+
})]
|
|
250
|
+
}), /* @__PURE__ */ jsxs(View, {
|
|
251
|
+
style: styles$3.field,
|
|
252
|
+
children: [/* @__PURE__ */ jsx(Label, { children: "From" }), /* @__PURE__ */ jsxs(ScrollView, {
|
|
253
|
+
style: styles$3.list,
|
|
254
|
+
nestedScrollEnabled: true,
|
|
255
|
+
children: [/* @__PURE__ */ jsx(ListRow, {
|
|
256
|
+
accessibilityLabel: "From",
|
|
257
|
+
title: /* @__PURE__ */ jsx(Text, {
|
|
258
|
+
style: styles$3.mono,
|
|
259
|
+
children: state.baseRef || "Current branch"
|
|
260
|
+
}),
|
|
261
|
+
disabled: busy,
|
|
262
|
+
onPress: () => setBasesOpen((open) => !open)
|
|
263
|
+
}), basesOpen ? bases.map((base) => /* @__PURE__ */ jsx(ListRow, {
|
|
264
|
+
title: /* @__PURE__ */ jsx(Text, {
|
|
265
|
+
style: styles$3.mono,
|
|
266
|
+
children: base
|
|
267
|
+
}),
|
|
268
|
+
selected: state.baseRef === base,
|
|
269
|
+
disabled: busy,
|
|
270
|
+
disclosure: false,
|
|
271
|
+
onPress: () => {
|
|
272
|
+
update({ baseRef: base });
|
|
273
|
+
setBasesOpen(false);
|
|
274
|
+
}
|
|
275
|
+
}, base)) : null]
|
|
276
|
+
})]
|
|
277
|
+
})] }) : /* @__PURE__ */ jsx(Text, {
|
|
278
|
+
style: styles$3.dim,
|
|
279
|
+
children: "Start in this workspace folder, without creating a worktree."
|
|
280
|
+
})] }),
|
|
281
|
+
/* @__PURE__ */ jsxs(View, {
|
|
282
|
+
style: styles$3.field,
|
|
283
|
+
children: [/* @__PURE__ */ jsx(Label, { children: "Name" }), /* @__PURE__ */ jsx(Input, {
|
|
284
|
+
testID: "new-session-name",
|
|
285
|
+
accessibilityLabel: "Session name",
|
|
286
|
+
placeholder: mode === "plain" ? "Optional" : "Optional, defaults to the branch",
|
|
287
|
+
value: form.name,
|
|
288
|
+
onChangeText: (name) => update({ name }),
|
|
289
|
+
disabled: busy
|
|
290
|
+
})]
|
|
291
|
+
}),
|
|
292
|
+
mode !== "plain" ? /* @__PURE__ */ jsx(Text, {
|
|
293
|
+
style: styles$3.dim,
|
|
294
|
+
children: "Runs in its own worktree, so this checkout stays as it is."
|
|
295
|
+
}) : null,
|
|
296
|
+
error ? /* @__PURE__ */ jsx(Text, {
|
|
297
|
+
testID: "new-session-error",
|
|
298
|
+
style: styles$3.error,
|
|
299
|
+
accessibilityRole: "alert",
|
|
300
|
+
children: error
|
|
301
|
+
}) : null,
|
|
302
|
+
/* @__PURE__ */ jsx(Button, {
|
|
303
|
+
testID: "new-session-create",
|
|
304
|
+
variant: "primary",
|
|
305
|
+
size: "lg",
|
|
306
|
+
loading: busy,
|
|
307
|
+
loadingLabel: mode === "plain" ? "Creating session" : "Creating worktree",
|
|
308
|
+
disabled: loading || problem !== void 0,
|
|
309
|
+
accessibilityHint: problem,
|
|
310
|
+
onPress: () => onCreate(state),
|
|
311
|
+
children: "Create session"
|
|
312
|
+
})
|
|
313
|
+
]
|
|
314
|
+
})] });
|
|
315
|
+
}
|
|
316
|
+
function errorOf(body, status) {
|
|
317
|
+
return typeof body === "object" && body !== null && typeof body.error === "string" ? body.error : `The cockpit answered ${String(status)}.`;
|
|
318
|
+
}
|
|
319
|
+
/** The native new-session sheet when git is installed; mirrors GitNewSessionDialog's calls. */
|
|
320
|
+
function GitNewSessionSheet({ newSession }) {
|
|
321
|
+
const [branches, setBranches] = useState(null);
|
|
322
|
+
const [loadError, setLoadError] = useState("");
|
|
323
|
+
const [error, setError] = useState("");
|
|
324
|
+
const [busy, setBusy] = useState(false);
|
|
325
|
+
const workspaceId = newSession?.workspaceId;
|
|
326
|
+
const request = useEffectEvent((input) => newSession?.requestWithStepUp(input));
|
|
327
|
+
useEffect(() => {
|
|
328
|
+
if (workspaceId === void 0) return;
|
|
329
|
+
let stale = false;
|
|
330
|
+
request(api.workspace(workspaceId).branches.url())?.then(async (response) => {
|
|
331
|
+
const body = await response.json().catch(() => void 0);
|
|
332
|
+
if (stale) return;
|
|
333
|
+
if (!response.ok) setLoadError(errorOf(body, response.status));
|
|
334
|
+
else setBranches(body);
|
|
335
|
+
}).catch(() => {
|
|
336
|
+
if (!stale) setLoadError("The branches could not be loaded.");
|
|
337
|
+
});
|
|
338
|
+
return () => {
|
|
339
|
+
stale = true;
|
|
340
|
+
};
|
|
341
|
+
}, [workspaceId]);
|
|
342
|
+
if (newSession === void 0) return null;
|
|
343
|
+
const submit = async (state) => {
|
|
344
|
+
if (busy || formProblem(state) !== void 0) return;
|
|
345
|
+
setBusy(true);
|
|
346
|
+
setError("");
|
|
347
|
+
const body = createRequest(state);
|
|
348
|
+
let failure;
|
|
349
|
+
try {
|
|
350
|
+
if (body === void 0) failure = await newSession.createPlain(state.name);
|
|
351
|
+
else {
|
|
352
|
+
const response = await newSession.requestWithStepUp(api.workspace(newSession.workspaceId).createSession.url(), {
|
|
353
|
+
method: "POST",
|
|
354
|
+
headers: { "Content-Type": "application/json" },
|
|
355
|
+
body: JSON.stringify(body)
|
|
356
|
+
});
|
|
357
|
+
const answer = await response.json().catch(() => void 0);
|
|
358
|
+
failure = response.ok ? await newSession.openCreated(answer.sessionId) : errorOf(answer, response.status);
|
|
359
|
+
}
|
|
360
|
+
} catch {
|
|
361
|
+
failure = "The cockpit is unreachable.";
|
|
362
|
+
}
|
|
363
|
+
if (failure === void 0) return;
|
|
364
|
+
setError(failure);
|
|
365
|
+
setBusy(false);
|
|
366
|
+
};
|
|
367
|
+
return /* @__PURE__ */ jsx(GitNewSessionSheetView, {
|
|
368
|
+
workspaceRoot: newSession.workspaceRoot,
|
|
369
|
+
branches,
|
|
370
|
+
loadError,
|
|
371
|
+
busy,
|
|
372
|
+
error,
|
|
373
|
+
onCreate: (state) => void submit(state)
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
const styles$3 = StyleSheet.create((theme) => {
|
|
377
|
+
const sans = fontOf(theme.fontFamily.sans);
|
|
378
|
+
const mono = fontOf(theme.fontFamily.mono);
|
|
379
|
+
return {
|
|
380
|
+
body: {
|
|
381
|
+
gap: theme.spacing(4),
|
|
382
|
+
uni__dependencies: [0]
|
|
383
|
+
},
|
|
384
|
+
root: {
|
|
385
|
+
fontFamily: mono,
|
|
386
|
+
fontSize: theme.fontSize.sm,
|
|
387
|
+
lineHeight: theme.lineHeight.sm,
|
|
388
|
+
color: theme.colors.dim,
|
|
389
|
+
textAlign: "center",
|
|
390
|
+
uni__dependencies: [0]
|
|
391
|
+
},
|
|
392
|
+
field: {
|
|
393
|
+
gap: theme.spacing(1.5),
|
|
394
|
+
uni__dependencies: [0]
|
|
395
|
+
},
|
|
396
|
+
dim: {
|
|
397
|
+
fontFamily: sans,
|
|
398
|
+
fontSize: theme.fontSize.sm,
|
|
399
|
+
lineHeight: theme.lineHeightRelaxed.sm,
|
|
400
|
+
color: theme.colors.dim,
|
|
401
|
+
uni__dependencies: [0]
|
|
402
|
+
},
|
|
403
|
+
error: {
|
|
404
|
+
fontFamily: sans,
|
|
405
|
+
fontSize: theme.fontSize.sm,
|
|
406
|
+
lineHeight: theme.lineHeightRelaxed.sm,
|
|
407
|
+
color: theme.colors.red,
|
|
408
|
+
uni__dependencies: [0]
|
|
409
|
+
},
|
|
410
|
+
mono: {
|
|
411
|
+
fontFamily: mono,
|
|
412
|
+
fontSize: theme.fontSize.base,
|
|
413
|
+
lineHeight: theme.lineHeight.base,
|
|
414
|
+
color: theme.colors.hi,
|
|
415
|
+
uni__dependencies: [0]
|
|
416
|
+
},
|
|
417
|
+
list: {
|
|
418
|
+
maxHeight: 320,
|
|
419
|
+
borderRadius: theme.borderRadius.lg,
|
|
420
|
+
borderWidth: 1,
|
|
421
|
+
borderColor: theme.colors["border-soft"],
|
|
422
|
+
overflow: "hidden",
|
|
423
|
+
uni__dependencies: [0]
|
|
424
|
+
},
|
|
425
|
+
pad: {
|
|
426
|
+
paddingHorizontal: theme.spacing(4),
|
|
427
|
+
paddingVertical: theme.spacing(2),
|
|
428
|
+
uni__dependencies: [0]
|
|
429
|
+
},
|
|
430
|
+
divider: {
|
|
431
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
432
|
+
borderTopColor: theme.colors["border-soft"],
|
|
433
|
+
uni__dependencies: [0]
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
});
|
|
437
|
+
//#endregion
|
|
438
|
+
//#region src/extensions/workspaces/(frontend)/fill/GitNewSession.new-session.native.tsx
|
|
439
|
+
var GitNewSession_new_session_native_default = defineFill({ component: GitNewSessionSheet });
|
|
440
|
+
//#endregion
|
|
441
|
+
//#region src/types/webWorktrees.ts
|
|
442
|
+
/**
|
|
443
|
+
* The cockpit's view of this session's worktrees.
|
|
444
|
+
*
|
|
445
|
+
* DESIGN PATTERNS:
|
|
446
|
+
* - Declarations only. The channel frame type is a constant so the hub and the
|
|
447
|
+
* page cannot drift apart on a string.
|
|
448
|
+
* - A view is the subset of a record the dock needs, not the record. The
|
|
449
|
+
* registry may hold paths and provenance the page has no business rendering.
|
|
450
|
+
*
|
|
451
|
+
* AVOID:
|
|
452
|
+
* - Adding behaviour here. This file is imported by browser code, so it must
|
|
453
|
+
* stay free of node builtins.
|
|
454
|
+
*/
|
|
455
|
+
/** The data channel carrying one session's worktrees to the page. */
|
|
456
|
+
const GIT_WORKTREES_TYPE = "git_worktrees";
|
|
457
|
+
//#endregion
|
|
458
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/worktreesActivityStore.native.ts
|
|
459
|
+
/**
|
|
460
|
+
* The dock's view of this session's worktrees, and the commands it sends.
|
|
461
|
+
*
|
|
462
|
+
* DESIGN PATTERNS:
|
|
463
|
+
* - Session-scoped store fed by one hub channel. The hub is authoritative; the
|
|
464
|
+
* page never reads a registry and never guesses.
|
|
465
|
+
* - Actions are channel frames, not prompts. The hub half owns the tool's
|
|
466
|
+
* operations, so the panel names the work and the hub performs it; a
|
|
467
|
+
* worktree made from the dock is the same object the tool makes, without
|
|
468
|
+
* spending a turn of the conversation to ask for it.
|
|
469
|
+
* - The sender lives in a store rather than a module variable, because a web
|
|
470
|
+
* plugin keeps no mutable module state and the page may reconnect.
|
|
471
|
+
*
|
|
472
|
+
* AVOID:
|
|
473
|
+
* - Calling the hub package API from here. A dock slot gets no sealed request
|
|
474
|
+
* transport, and raw fetch breaks the remote cockpit.
|
|
475
|
+
* - `sendSessionFrame` for a command. It envelopes the frame as a session
|
|
476
|
+
* command bound for the agent, which never reaches a channel's `receive`.
|
|
477
|
+
*/
|
|
478
|
+
/** Presentation state follows hub operation labels, including session startup progress. */
|
|
479
|
+
function worktreesPresentation(state) {
|
|
480
|
+
return {
|
|
481
|
+
working: state.worktrees.filter((item) => !item.orphaned && !item.unowned),
|
|
482
|
+
attention: state.worktrees.filter((item) => item.orphaned || item.unowned),
|
|
483
|
+
starting: state.requestedBranch !== void 0 ? !state.error && !state.worktrees.some((item) => item.branch === state.requestedBranch) : !!state.pending && /^(creating|starting session)/i.test(state.pending),
|
|
484
|
+
closeErrorId: state.errorTarget?.action === "close" ? state.errorTarget.id : void 0
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
const worktreeActivity = defineSessionStore({
|
|
488
|
+
worktrees: [],
|
|
489
|
+
pending: void 0,
|
|
490
|
+
error: void 0,
|
|
491
|
+
errorTarget: void 0
|
|
492
|
+
});
|
|
493
|
+
/** Captures the clicked card, so a focus change cannot retarget an open dialog. */
|
|
494
|
+
const worktreeCreateDialog = defineGlobalStore(null);
|
|
495
|
+
/**
|
|
496
|
+
* The page's hub socket sender, held for as long as the plugin runs.
|
|
497
|
+
*
|
|
498
|
+
* Undefined before `startWorktreeRuntime` and after it disposes; a command
|
|
499
|
+
* sent in that window is dropped rather than queued, because a queue would
|
|
500
|
+
* replay a worktree create against a socket that has since changed sessions.
|
|
501
|
+
*/
|
|
502
|
+
const worktreeSender = defineGlobalStore(void 0);
|
|
503
|
+
/** Binds the page's hub socket. The plugin host calls this once per page. */
|
|
504
|
+
function startWorktreeRuntime(runtime) {
|
|
505
|
+
const send = (frame) => {
|
|
506
|
+
runtime.sendHubFrame(frame);
|
|
507
|
+
};
|
|
508
|
+
worktreeSender.update(() => send);
|
|
509
|
+
return () => {
|
|
510
|
+
worktreeSender.update((current) => current === send ? void 0 : current);
|
|
511
|
+
worktreeCreateDialog.update(() => null);
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Whether this session has worktree work in flight.
|
|
516
|
+
*
|
|
517
|
+
* A standing worktree is state, not work: it sits there until someone closes
|
|
518
|
+
* it, so counting it would leave the session reading as busy forever, with a
|
|
519
|
+
* "background work is still running" notice under a conversation where
|
|
520
|
+
* nothing is running. Only a create or close actually in progress counts.
|
|
521
|
+
*
|
|
522
|
+
* The group stays visible either way. Declaring an active source at all is
|
|
523
|
+
* what keeps it in the dock; this answers how it is drawn, not whether it is
|
|
524
|
+
* there.
|
|
525
|
+
*/
|
|
526
|
+
const worktreeActivitySource = {
|
|
527
|
+
subscribe(listener) {
|
|
528
|
+
const subscription = worktreeActivity.store.subscribe(listener);
|
|
529
|
+
return () => subscription.unsubscribe();
|
|
530
|
+
},
|
|
531
|
+
isActive(sessionId) {
|
|
532
|
+
return worktreeActivity.select(worktreeActivity.store.state, sessionId).pending !== void 0;
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
function isRecord$2(value) {
|
|
536
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
537
|
+
}
|
|
538
|
+
function optionalText$1(value) {
|
|
539
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
540
|
+
}
|
|
541
|
+
function optionalErrorTarget$1(value) {
|
|
542
|
+
if (!isRecord$2(value)) return void 0;
|
|
543
|
+
if (value.action === "create") return value.riftMissing === true ? {
|
|
544
|
+
action: "create",
|
|
545
|
+
riftMissing: true
|
|
546
|
+
} : { action: "create" };
|
|
547
|
+
if (value.action === "close" && typeof value.id === "string" && value.id !== "") return {
|
|
548
|
+
action: "close",
|
|
549
|
+
id: value.id
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
/** The plugin's session data channel: 'git_worktrees' payloads into the store. */
|
|
553
|
+
const worktreesChannel = worktreeActivity.channel({
|
|
554
|
+
channel: GIT_WORKTREES_TYPE,
|
|
555
|
+
parse(input) {
|
|
556
|
+
if (!isRecord$2(input) || !Array.isArray(input.worktrees)) return null;
|
|
557
|
+
return {
|
|
558
|
+
worktrees: input.worktrees.filter(isRecord$2),
|
|
559
|
+
pending: optionalText$1(input.pending),
|
|
560
|
+
error: optionalText$1(input.error),
|
|
561
|
+
errorTarget: optionalErrorTarget$1(input.errorTarget)
|
|
562
|
+
};
|
|
563
|
+
},
|
|
564
|
+
reduce(current, next) {
|
|
565
|
+
const requestedBranch = current.requestedBranch;
|
|
566
|
+
return {
|
|
567
|
+
...next,
|
|
568
|
+
...requestedBranch && next.pending && !next.error ? { requestedBranch } : {}
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
/** Sends one command to the session's worktrees channel. */
|
|
573
|
+
function command(sessionId, payload) {
|
|
574
|
+
worktreeSender.store.state?.({
|
|
575
|
+
type: GIT_WORKTREES_TYPE,
|
|
576
|
+
sessionId,
|
|
577
|
+
payload
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
/** Asks the hub for a worktree. The hub answers on the same channel. */
|
|
581
|
+
function requestWorktreeCreate(sessionId, branch, baseRef, reservationId) {
|
|
582
|
+
const trimmedBase = baseRef.trim();
|
|
583
|
+
if (worktreeSender.store.state) worktreeActivity.update(sessionId, (current) => ({
|
|
584
|
+
...current,
|
|
585
|
+
requestedBranch: branch.trim(),
|
|
586
|
+
pending: "creating",
|
|
587
|
+
error: void 0,
|
|
588
|
+
errorTarget: void 0
|
|
589
|
+
}));
|
|
590
|
+
command(sessionId, {
|
|
591
|
+
action: "create",
|
|
592
|
+
branch: branch.trim(),
|
|
593
|
+
...trimmedBase === "" ? {} : { baseRef: trimmedBase },
|
|
594
|
+
...reservationId === void 0 ? {} : { reservationId }
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
/** Asks the hub to close one worktree. Refusal on a dirty tree stays the hub's call. */
|
|
598
|
+
function requestWorktreeClose(sessionId, id) {
|
|
599
|
+
command(sessionId, {
|
|
600
|
+
action: "close",
|
|
601
|
+
id
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/WorktreesPanel.native.tsx
|
|
606
|
+
function useWorktrees(sessionId) {
|
|
607
|
+
const subscribe = useCallback((listener) => {
|
|
608
|
+
const sub = worktreeActivity.store.subscribe(listener);
|
|
609
|
+
return () => sub.unsubscribe();
|
|
610
|
+
}, []);
|
|
611
|
+
return useSyncExternalStore(subscribe, () => worktreeActivity.select(worktreeActivity.store.state, sessionId));
|
|
612
|
+
}
|
|
613
|
+
function worktreesTab() {
|
|
614
|
+
return {
|
|
615
|
+
id: "git-worktrees",
|
|
616
|
+
label: "worktrees",
|
|
617
|
+
panel: WorktreesPanel
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
function WorktreesHeader({ activity = false, onNew, disabled = false }) {
|
|
621
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
622
|
+
style: styles$2.header,
|
|
623
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
624
|
+
style: styles$2.title,
|
|
625
|
+
children: activity ? "Activity" : "Worktrees"
|
|
626
|
+
}), !activity ? /* @__PURE__ */ jsx(Button, {
|
|
627
|
+
testID: "git-worktree-create",
|
|
628
|
+
variant: "ghost",
|
|
629
|
+
style: styles$2.newPill,
|
|
630
|
+
disabled,
|
|
631
|
+
onPress: onNew,
|
|
632
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
633
|
+
style: styles$2.jade,
|
|
634
|
+
children: "+ New"
|
|
635
|
+
})
|
|
636
|
+
}) : null]
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
function WorktreeRow({ item, error, compact = false, onClose, onOpen, disabled = false }) {
|
|
640
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
641
|
+
style: styles$2.worktree,
|
|
642
|
+
children: [
|
|
643
|
+
/* @__PURE__ */ jsxs(View, {
|
|
644
|
+
style: styles$2.line,
|
|
645
|
+
children: [
|
|
646
|
+
/* @__PURE__ */ jsx(Dot, {
|
|
647
|
+
tone: item.orphaned ? "yellow" : item.unowned ? "blue" : "green",
|
|
648
|
+
size: "md"
|
|
649
|
+
}),
|
|
650
|
+
/* @__PURE__ */ jsxs(Pressable, {
|
|
651
|
+
style: styles$2.grow,
|
|
652
|
+
accessibilityRole: onOpen ? "button" : void 0,
|
|
653
|
+
accessibilityLabel: onOpen ? `Open session for ${item.branch}` : void 0,
|
|
654
|
+
onPress: onOpen,
|
|
655
|
+
disabled: !onOpen,
|
|
656
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
657
|
+
numberOfLines: 1,
|
|
658
|
+
ellipsizeMode: "middle",
|
|
659
|
+
style: styles$2.branch,
|
|
660
|
+
children: compact && item.branch.length > 36 ? `${item.branch.slice(0, 19)}…${item.branch.slice(-14)}` : item.branch
|
|
661
|
+
}), !compact ? /* @__PURE__ */ jsx(Text, {
|
|
662
|
+
style: styles$2.dim,
|
|
663
|
+
children: item.orphaned ? "Its folder is missing" : item.unowned ? "Its session has ended" : "Session running"
|
|
664
|
+
}) : null]
|
|
665
|
+
}),
|
|
666
|
+
item.orphaned ? /* @__PURE__ */ jsx(Badge, {
|
|
667
|
+
tone: "yellow",
|
|
668
|
+
style: styles$2.caution,
|
|
669
|
+
children: "Orphaned"
|
|
670
|
+
}) : null,
|
|
671
|
+
item.unowned ? /* @__PURE__ */ jsx(Badge, {
|
|
672
|
+
tone: "blue",
|
|
673
|
+
style: styles$2.info,
|
|
674
|
+
children: "Unowned"
|
|
675
|
+
}) : null,
|
|
676
|
+
!compact ? /* @__PURE__ */ jsx(Button, {
|
|
677
|
+
variant: "ghost",
|
|
678
|
+
style: styles$2.more,
|
|
679
|
+
accessibilityLabel: `Close ${item.branch}`,
|
|
680
|
+
testID: `git-worktree-close-${item.id}`,
|
|
681
|
+
onPress: onClose,
|
|
682
|
+
disabled,
|
|
683
|
+
children: "⋮"
|
|
684
|
+
}) : /* @__PURE__ */ jsx(ChevronRightIcon, {
|
|
685
|
+
size: 16,
|
|
686
|
+
color: "dim"
|
|
687
|
+
})
|
|
688
|
+
]
|
|
689
|
+
}),
|
|
690
|
+
!compact ? /* @__PURE__ */ jsx(Text, {
|
|
691
|
+
numberOfLines: 1,
|
|
692
|
+
ellipsizeMode: "middle",
|
|
693
|
+
style: styles$2.path,
|
|
694
|
+
children: item.path
|
|
695
|
+
}) : null,
|
|
696
|
+
error ? /* @__PURE__ */ jsx(Text, {
|
|
697
|
+
accessibilityRole: "alert",
|
|
698
|
+
style: styles$2.error,
|
|
699
|
+
children: error
|
|
700
|
+
}) : null
|
|
701
|
+
]
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
function WorktreeStarting({ branch }) {
|
|
705
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
706
|
+
style: styles$2.starting,
|
|
707
|
+
children: [/* @__PURE__ */ jsx(Spinner, { label: "Starting session..." }), /* @__PURE__ */ jsxs(View, {
|
|
708
|
+
style: styles$2.grow,
|
|
709
|
+
children: [branch ? /* @__PURE__ */ jsx(Text, {
|
|
710
|
+
numberOfLines: 1,
|
|
711
|
+
ellipsizeMode: "middle",
|
|
712
|
+
style: styles$2.branch,
|
|
713
|
+
children: branch
|
|
714
|
+
}) : null, /* @__PURE__ */ jsx(Text, {
|
|
715
|
+
style: styles$2.dim,
|
|
716
|
+
children: "Starting session..."
|
|
717
|
+
})]
|
|
718
|
+
})]
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
function WorktreeGroup({ title, count }) {
|
|
722
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
723
|
+
style: styles$2.group,
|
|
724
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
725
|
+
style: styles$2.heading,
|
|
726
|
+
children: title
|
|
727
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
728
|
+
style: styles$2.dim,
|
|
729
|
+
children: count
|
|
730
|
+
})]
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
function WorktreeForm({ close = false, branch = "", baseRef = "", onBranch, onBaseRef, onConfirm, onCancel, disabled = false, framed = true }) {
|
|
734
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
735
|
+
style: [styles$2.sheet, !framed && styles$2.unframed],
|
|
736
|
+
children: [
|
|
737
|
+
framed ? /* @__PURE__ */ jsx(View, { style: styles$2.handle }) : null,
|
|
738
|
+
/* @__PURE__ */ jsx(Text, {
|
|
739
|
+
style: styles$2.sheetTitle,
|
|
740
|
+
children: close ? "Close this worktree?" : "New worktree"
|
|
741
|
+
}),
|
|
742
|
+
close ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {
|
|
743
|
+
style: styles$2.branch,
|
|
744
|
+
children: branch
|
|
745
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
746
|
+
style: styles$2.body,
|
|
747
|
+
children: "Removes the checkout and stops its session."
|
|
748
|
+
})] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
749
|
+
/* @__PURE__ */ jsx(Text, {
|
|
750
|
+
style: styles$2.body,
|
|
751
|
+
children: "A separate checkout on its own branch, with its own session. This session stays where it is."
|
|
752
|
+
}),
|
|
753
|
+
/* @__PURE__ */ jsx(Text, {
|
|
754
|
+
style: styles$2.label,
|
|
755
|
+
children: "Branch"
|
|
756
|
+
}),
|
|
757
|
+
/* @__PURE__ */ jsx(Input, {
|
|
758
|
+
accessibilityLabel: "Branch",
|
|
759
|
+
value: branch,
|
|
760
|
+
onChangeText: onBranch,
|
|
761
|
+
style: styles$2.monoInput
|
|
762
|
+
}),
|
|
763
|
+
/* @__PURE__ */ jsx(Text, {
|
|
764
|
+
style: styles$2.label,
|
|
765
|
+
children: "Start from"
|
|
766
|
+
}),
|
|
767
|
+
/* @__PURE__ */ jsx(Input, {
|
|
768
|
+
accessibilityLabel: "Start from",
|
|
769
|
+
value: baseRef,
|
|
770
|
+
onChangeText: onBaseRef,
|
|
771
|
+
placeholder: "Current branch"
|
|
772
|
+
})
|
|
773
|
+
] }),
|
|
774
|
+
/* @__PURE__ */ jsx(Button, {
|
|
775
|
+
onPress: onConfirm,
|
|
776
|
+
disabled: disabled || !close && branch.trim() === "",
|
|
777
|
+
variant: close ? "danger" : "primary",
|
|
778
|
+
style: [styles$2.fullButton, !close && styles$2.createButton],
|
|
779
|
+
children: close ? "Close worktree" : /* @__PURE__ */ jsx(Text, {
|
|
780
|
+
style: styles$2.createLabel,
|
|
781
|
+
children: "Create worktree"
|
|
782
|
+
})
|
|
783
|
+
}),
|
|
784
|
+
/* @__PURE__ */ jsx(Button, {
|
|
785
|
+
variant: "outline",
|
|
786
|
+
style: styles$2.fullButton,
|
|
787
|
+
onPress: onCancel,
|
|
788
|
+
children: "Cancel"
|
|
789
|
+
})
|
|
790
|
+
]
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
function WorktreesView({ state, disabled = false, onNew, onClose, onOpen }) {
|
|
794
|
+
const { working, attention, starting, closeErrorId } = worktreesPresentation(state);
|
|
795
|
+
const row = (item) => /* @__PURE__ */ jsx(WorktreeRow, {
|
|
796
|
+
item,
|
|
797
|
+
disabled: disabled || !!state.pending,
|
|
798
|
+
onClose: () => onClose?.(item),
|
|
799
|
+
onOpen: item.sessionId && onOpen ? () => onOpen(item.sessionId) : void 0,
|
|
800
|
+
error: state.errorTarget?.action === "close" && state.errorTarget.id === item.id ? state.error : void 0
|
|
801
|
+
}, item.id);
|
|
802
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
803
|
+
style: styles$2.screen,
|
|
804
|
+
children: [
|
|
805
|
+
/* @__PURE__ */ jsx(WorktreesHeader, {
|
|
806
|
+
disabled: disabled || !!state.pending,
|
|
807
|
+
onNew
|
|
808
|
+
}),
|
|
809
|
+
state.worktrees.length || starting ? /* @__PURE__ */ jsxs(View, {
|
|
810
|
+
style: styles$2.content,
|
|
811
|
+
children: [
|
|
812
|
+
/* @__PURE__ */ jsx(WorktreeGroup, {
|
|
813
|
+
title: "Working",
|
|
814
|
+
count: `${working.length} open${starting ? " · 1 starting" : ""}`
|
|
815
|
+
}),
|
|
816
|
+
working.map(row),
|
|
817
|
+
starting ? /* @__PURE__ */ jsx(WorktreeStarting, { branch: state.requestedBranch }) : null,
|
|
818
|
+
attention.length ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
819
|
+
/* @__PURE__ */ jsx(WorktreeGroup, {
|
|
820
|
+
title: "Needs a look",
|
|
821
|
+
count: `${attention.length} open`
|
|
822
|
+
}),
|
|
823
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
824
|
+
style: styles$2.dim,
|
|
825
|
+
children: [
|
|
826
|
+
attention.length === 2 ? "Two worktrees" : attention.length === 1 ? "One worktree" : `${attention.length} worktrees`,
|
|
827
|
+
" ",
|
|
828
|
+
"would like your eye, whenever you are ready."
|
|
829
|
+
]
|
|
830
|
+
}),
|
|
831
|
+
attention.map(row)
|
|
832
|
+
] }) : null
|
|
833
|
+
]
|
|
834
|
+
}) : /* @__PURE__ */ jsxs(View, {
|
|
835
|
+
style: styles$2.empty,
|
|
836
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
837
|
+
style: styles$2.heading,
|
|
838
|
+
children: "No worktrees yet"
|
|
839
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
840
|
+
style: styles$2.emptyCopy,
|
|
841
|
+
children: "Create a worktree to run a separate session on its own branch."
|
|
842
|
+
})]
|
|
843
|
+
}),
|
|
844
|
+
state.pending && !starting ? /* @__PURE__ */ jsx(Text, {
|
|
845
|
+
style: styles$2.dim,
|
|
846
|
+
children: state.pending
|
|
847
|
+
}) : null,
|
|
848
|
+
state.error && (!closeErrorId || !state.worktrees.some((item) => item.id === closeErrorId)) ? /* @__PURE__ */ jsx(Text, {
|
|
849
|
+
accessibilityRole: "alert",
|
|
850
|
+
style: styles$2.error,
|
|
851
|
+
children: state.error
|
|
852
|
+
}) : null
|
|
853
|
+
]
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
function WorktreesPanel({ sessionId, sessionReservationId, openSession }) {
|
|
857
|
+
const state = useWorktrees(sessionId);
|
|
858
|
+
const [creating, setCreating] = useState(false);
|
|
859
|
+
const [branch, setBranch] = useState("");
|
|
860
|
+
const [baseRef, setBaseRef] = useState("");
|
|
861
|
+
const [closing, setClosing] = useState();
|
|
862
|
+
const busy = sessionId === null || state.pending !== void 0;
|
|
863
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
864
|
+
/* @__PURE__ */ jsx(ScrollView, {
|
|
865
|
+
contentContainerStyle: styles$2.scroll,
|
|
866
|
+
children: /* @__PURE__ */ jsx(WorktreesView, {
|
|
867
|
+
state,
|
|
868
|
+
disabled: sessionId === null,
|
|
869
|
+
onNew: () => setCreating(true),
|
|
870
|
+
onClose: setClosing,
|
|
871
|
+
onOpen: openSession
|
|
872
|
+
})
|
|
873
|
+
}),
|
|
874
|
+
/* @__PURE__ */ jsx(Sheet, {
|
|
875
|
+
open: creating,
|
|
876
|
+
onOpenChange: setCreating,
|
|
877
|
+
children: /* @__PURE__ */ jsx(SheetContent, { children: /* @__PURE__ */ jsx(WorktreeForm, {
|
|
878
|
+
framed: false,
|
|
879
|
+
branch,
|
|
880
|
+
baseRef,
|
|
881
|
+
onBranch: setBranch,
|
|
882
|
+
onBaseRef: setBaseRef,
|
|
883
|
+
disabled: busy,
|
|
884
|
+
onCancel: () => setCreating(false),
|
|
885
|
+
onConfirm: () => {
|
|
886
|
+
if (sessionId === null) return;
|
|
887
|
+
requestWorktreeCreate(sessionId, branch, baseRef, sessionReservationId);
|
|
888
|
+
setCreating(false);
|
|
889
|
+
if (sessionReservationId === void 0) {
|
|
890
|
+
setBranch("");
|
|
891
|
+
setBaseRef("");
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}) })
|
|
895
|
+
}),
|
|
896
|
+
/* @__PURE__ */ jsx(Sheet, {
|
|
897
|
+
open: !!closing,
|
|
898
|
+
onOpenChange: (open) => {
|
|
899
|
+
if (!open) setClosing(void 0);
|
|
900
|
+
},
|
|
901
|
+
children: /* @__PURE__ */ jsx(SheetContent, { children: /* @__PURE__ */ jsx(WorktreeForm, {
|
|
902
|
+
framed: false,
|
|
903
|
+
close: true,
|
|
904
|
+
branch: closing?.branch,
|
|
905
|
+
disabled: busy,
|
|
906
|
+
onCancel: () => setClosing(void 0),
|
|
907
|
+
onConfirm: () => {
|
|
908
|
+
if (sessionId !== null && closing) requestWorktreeClose(sessionId, closing.id);
|
|
909
|
+
setClosing(void 0);
|
|
910
|
+
}
|
|
911
|
+
}) })
|
|
912
|
+
})
|
|
913
|
+
] });
|
|
914
|
+
}
|
|
915
|
+
const styles$2 = StyleSheet.create((theme) => ({
|
|
916
|
+
screen: {
|
|
917
|
+
flex: 1,
|
|
918
|
+
backgroundColor: theme.colors.rail,
|
|
919
|
+
uni__dependencies: [0]
|
|
920
|
+
},
|
|
921
|
+
scroll: { flexGrow: 1 },
|
|
922
|
+
header: {
|
|
923
|
+
flexDirection: "row",
|
|
924
|
+
alignItems: "center",
|
|
925
|
+
justifyContent: "space-between",
|
|
926
|
+
padding: theme.spacing(5),
|
|
927
|
+
minHeight: 88,
|
|
928
|
+
uni__dependencies: [0]
|
|
929
|
+
},
|
|
930
|
+
title: {
|
|
931
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
932
|
+
fontSize: theme.fontSize["2xl"],
|
|
933
|
+
fontWeight: "600",
|
|
934
|
+
color: theme.colors.hi,
|
|
935
|
+
uni__dependencies: [0]
|
|
936
|
+
},
|
|
937
|
+
newPill: {
|
|
938
|
+
backgroundColor: theme.colors["tint-green"],
|
|
939
|
+
borderRadius: theme.borderRadius.full,
|
|
940
|
+
minHeight: 44,
|
|
941
|
+
paddingHorizontal: theme.spacing(4),
|
|
942
|
+
uni__dependencies: [0]
|
|
943
|
+
},
|
|
944
|
+
content: {
|
|
945
|
+
paddingHorizontal: theme.spacing(5),
|
|
946
|
+
gap: theme.spacing(2),
|
|
947
|
+
uni__dependencies: [0]
|
|
948
|
+
},
|
|
949
|
+
group: {
|
|
950
|
+
flexDirection: "row",
|
|
951
|
+
alignItems: "center",
|
|
952
|
+
gap: theme.spacing(3),
|
|
953
|
+
paddingTop: theme.spacing(4),
|
|
954
|
+
paddingBottom: theme.spacing(2),
|
|
955
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
956
|
+
borderColor: theme.colors["border-soft"],
|
|
957
|
+
uni__dependencies: [0]
|
|
958
|
+
},
|
|
959
|
+
heading: {
|
|
960
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
961
|
+
fontSize: theme.fontSize.lg,
|
|
962
|
+
lineHeight: theme.lineHeight.lg,
|
|
963
|
+
fontWeight: "600",
|
|
964
|
+
color: theme.colors.hi,
|
|
965
|
+
uni__dependencies: [0]
|
|
966
|
+
},
|
|
967
|
+
dim: {
|
|
968
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
969
|
+
fontSize: theme.fontSize.sm,
|
|
970
|
+
lineHeight: theme.lineHeight.sm,
|
|
971
|
+
color: theme.colors.dim,
|
|
972
|
+
uni__dependencies: [0]
|
|
973
|
+
},
|
|
974
|
+
body: {
|
|
975
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
976
|
+
fontSize: theme.fontSize.base,
|
|
977
|
+
lineHeight: theme.lineHeight.lg,
|
|
978
|
+
color: theme.colors.dim,
|
|
979
|
+
uni__dependencies: [0]
|
|
980
|
+
},
|
|
981
|
+
label: {
|
|
982
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
983
|
+
fontSize: theme.fontSize.base,
|
|
984
|
+
fontWeight: "500",
|
|
985
|
+
color: theme.colors.text,
|
|
986
|
+
marginTop: theme.spacing(2),
|
|
987
|
+
uni__dependencies: [0]
|
|
988
|
+
},
|
|
989
|
+
branch: {
|
|
990
|
+
fontFamily: fontOf(theme.fontFamily.mono),
|
|
991
|
+
fontSize: theme.fontSize.base,
|
|
992
|
+
lineHeight: theme.lineHeight.lg,
|
|
993
|
+
color: theme.colors.hi,
|
|
994
|
+
uni__dependencies: [0]
|
|
995
|
+
},
|
|
996
|
+
path: {
|
|
997
|
+
fontFamily: fontOf(theme.fontFamily.mono),
|
|
998
|
+
fontSize: theme.fontSize.sm,
|
|
999
|
+
lineHeight: theme.lineHeight.sm,
|
|
1000
|
+
color: theme.colors.dim,
|
|
1001
|
+
marginLeft: theme.spacing(4.5),
|
|
1002
|
+
uni__dependencies: [0]
|
|
1003
|
+
},
|
|
1004
|
+
worktree: {
|
|
1005
|
+
gap: theme.spacing(1),
|
|
1006
|
+
paddingVertical: theme.spacing(2),
|
|
1007
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
1008
|
+
borderColor: theme.colors["border-soft"],
|
|
1009
|
+
uni__dependencies: [0]
|
|
1010
|
+
},
|
|
1011
|
+
line: {
|
|
1012
|
+
flexDirection: "row",
|
|
1013
|
+
alignItems: "center",
|
|
1014
|
+
gap: theme.spacing(2),
|
|
1015
|
+
minHeight: 44,
|
|
1016
|
+
uni__dependencies: [0]
|
|
1017
|
+
},
|
|
1018
|
+
grow: {
|
|
1019
|
+
flex: 1,
|
|
1020
|
+
minWidth: 0,
|
|
1021
|
+
minHeight: 44,
|
|
1022
|
+
justifyContent: "center",
|
|
1023
|
+
gap: theme.spacing(1),
|
|
1024
|
+
uni__dependencies: [0]
|
|
1025
|
+
},
|
|
1026
|
+
more: {
|
|
1027
|
+
width: 44,
|
|
1028
|
+
height: 44,
|
|
1029
|
+
padding: 0
|
|
1030
|
+
},
|
|
1031
|
+
caution: {
|
|
1032
|
+
backgroundColor: theme.colors["tint-yellow"],
|
|
1033
|
+
uni__dependencies: [0]
|
|
1034
|
+
},
|
|
1035
|
+
info: {
|
|
1036
|
+
backgroundColor: theme.colors["tint-blue"],
|
|
1037
|
+
uni__dependencies: [0]
|
|
1038
|
+
},
|
|
1039
|
+
starting: {
|
|
1040
|
+
flexDirection: "row",
|
|
1041
|
+
alignItems: "center",
|
|
1042
|
+
gap: theme.spacing(2),
|
|
1043
|
+
minHeight: 52,
|
|
1044
|
+
uni__dependencies: [0]
|
|
1045
|
+
},
|
|
1046
|
+
jade: {
|
|
1047
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
1048
|
+
fontSize: theme.fontSize.base,
|
|
1049
|
+
lineHeight: theme.lineHeight.base,
|
|
1050
|
+
color: theme.colors.green,
|
|
1051
|
+
fontWeight: "500",
|
|
1052
|
+
uni__dependencies: [0]
|
|
1053
|
+
},
|
|
1054
|
+
error: {
|
|
1055
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
1056
|
+
fontSize: theme.fontSize.sm,
|
|
1057
|
+
lineHeight: theme.lineHeight.sm,
|
|
1058
|
+
color: theme.colors.red,
|
|
1059
|
+
uni__dependencies: [0]
|
|
1060
|
+
},
|
|
1061
|
+
unframed: { borderTopWidth: 0 },
|
|
1062
|
+
sheet: {
|
|
1063
|
+
backgroundColor: theme.colors.bg,
|
|
1064
|
+
borderTopWidth: 1,
|
|
1065
|
+
borderColor: theme.colors.border,
|
|
1066
|
+
borderTopLeftRadius: theme.borderRadius.lg,
|
|
1067
|
+
borderTopRightRadius: theme.borderRadius.lg,
|
|
1068
|
+
padding: theme.spacing(5),
|
|
1069
|
+
gap: theme.spacing(3),
|
|
1070
|
+
uni__dependencies: [0]
|
|
1071
|
+
},
|
|
1072
|
+
handle: {
|
|
1073
|
+
alignSelf: "center",
|
|
1074
|
+
width: 40,
|
|
1075
|
+
height: 4,
|
|
1076
|
+
borderRadius: theme.borderRadius.full,
|
|
1077
|
+
backgroundColor: theme.colors.border,
|
|
1078
|
+
marginBottom: theme.spacing(2),
|
|
1079
|
+
uni__dependencies: [0]
|
|
1080
|
+
},
|
|
1081
|
+
sheetTitle: {
|
|
1082
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
1083
|
+
fontSize: theme.fontSize.xl,
|
|
1084
|
+
fontWeight: "600",
|
|
1085
|
+
color: theme.colors.hi,
|
|
1086
|
+
uni__dependencies: [0]
|
|
1087
|
+
},
|
|
1088
|
+
monoInput: {
|
|
1089
|
+
fontFamily: fontOf(theme.fontFamily.mono),
|
|
1090
|
+
uni__dependencies: [0]
|
|
1091
|
+
},
|
|
1092
|
+
fullButton: {
|
|
1093
|
+
height: 44,
|
|
1094
|
+
alignSelf: "stretch"
|
|
1095
|
+
},
|
|
1096
|
+
createButton: {
|
|
1097
|
+
backgroundColor: theme.colors.green,
|
|
1098
|
+
uni__dependencies: [0]
|
|
1099
|
+
},
|
|
1100
|
+
createLabel: {
|
|
1101
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
1102
|
+
fontSize: theme.fontSize.base,
|
|
1103
|
+
fontWeight: "600",
|
|
1104
|
+
color: theme.colors.bg,
|
|
1105
|
+
uni__dependencies: [0]
|
|
1106
|
+
},
|
|
1107
|
+
empty: {
|
|
1108
|
+
flex: 1,
|
|
1109
|
+
alignItems: "center",
|
|
1110
|
+
justifyContent: "center",
|
|
1111
|
+
padding: theme.spacing(8),
|
|
1112
|
+
gap: theme.spacing(3),
|
|
1113
|
+
uni__dependencies: [0]
|
|
1114
|
+
},
|
|
1115
|
+
emptyCopy: {
|
|
1116
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
1117
|
+
fontSize: theme.fontSize.base,
|
|
1118
|
+
lineHeight: theme.lineHeight.lg,
|
|
1119
|
+
color: theme.colors.dim,
|
|
1120
|
+
textAlign: "center",
|
|
1121
|
+
uni__dependencies: [0]
|
|
1122
|
+
}
|
|
1123
|
+
}));
|
|
1124
|
+
//#endregion
|
|
1125
|
+
//#region src/types/gitReview.ts
|
|
1126
|
+
/**
|
|
1127
|
+
* The cockpit's view of what this session changed against its base: the counts
|
|
1128
|
+
* the '# diff' group shows, what the review tab lists and draws, and the manual
|
|
1129
|
+
* sync that sits beside them.
|
|
1130
|
+
*
|
|
1131
|
+
* DESIGN PATTERNS:
|
|
1132
|
+
* - Declarations only, shared by the hub and the page.
|
|
1133
|
+
* - Its own channel, apart from worktrees. Worktrees are repository
|
|
1134
|
+
* management; the diff is this session's code change.
|
|
1135
|
+
* - A diff row carries one line number whose file follows from its marker: the
|
|
1136
|
+
* new file's for context and additions, the old file's for removals. It is
|
|
1137
|
+
* the same shape the shared DiffView draws.
|
|
1138
|
+
*
|
|
1139
|
+
* AVOID:
|
|
1140
|
+
* - Behaviour or node builtins. Browser code imports this file.
|
|
1141
|
+
*/
|
|
1142
|
+
/** The session channel carrying this checkout's changes and the sync around them. */
|
|
1143
|
+
const GIT_CHANGES_TYPE = "git_changes";
|
|
1144
|
+
//#endregion
|
|
1145
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/gitChangesStore.native.ts
|
|
1146
|
+
/**
|
|
1147
|
+
* This session's code change against its base, and the manual sync beside it.
|
|
1148
|
+
*
|
|
1149
|
+
* DESIGN PATTERNS:
|
|
1150
|
+
* - Its own session store and hub channel, apart from worktrees. Worktrees are
|
|
1151
|
+
* repository management; this is the session's diff.
|
|
1152
|
+
* - The hub is authoritative: it computes the counts, runs pull, push and
|
|
1153
|
+
* rebase one at a time, and reports pending, errors and a paused rebase here.
|
|
1154
|
+
* - The sender lives in a store rather than a module variable, because a web
|
|
1155
|
+
* plugin keeps no mutable module state and the page may reconnect.
|
|
1156
|
+
*
|
|
1157
|
+
* AVOID:
|
|
1158
|
+
* - `sendSessionFrame` for a sync command. That frame is bound for the agent
|
|
1159
|
+
* and never reaches a channel's `receive`.
|
|
1160
|
+
*/
|
|
1161
|
+
const gitChanges = defineSessionStore({
|
|
1162
|
+
changes: void 0,
|
|
1163
|
+
pending: void 0,
|
|
1164
|
+
error: void 0,
|
|
1165
|
+
errorTarget: void 0
|
|
1166
|
+
});
|
|
1167
|
+
/** The page's hub socket sender; undefined before start and after dispose, when a command is dropped. */
|
|
1168
|
+
const changesSender = defineGlobalStore(void 0);
|
|
1169
|
+
/** Binds the page's hub socket for sync commands. The plugin host calls this once per page. */
|
|
1170
|
+
function startGitChangesRuntime(runtime) {
|
|
1171
|
+
const send = (frame) => {
|
|
1172
|
+
runtime.sendHubFrame(frame);
|
|
1173
|
+
};
|
|
1174
|
+
changesSender.update(() => send);
|
|
1175
|
+
return () => {
|
|
1176
|
+
changesSender.update((current) => current === send ? void 0 : current);
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
/** Whether a sync command is running for this session. */
|
|
1180
|
+
const gitChangesActivitySource = {
|
|
1181
|
+
subscribe(listener) {
|
|
1182
|
+
const subscription = gitChanges.store.subscribe(listener);
|
|
1183
|
+
return () => subscription.unsubscribe();
|
|
1184
|
+
},
|
|
1185
|
+
isActive(sessionId) {
|
|
1186
|
+
return gitChanges.select(gitChanges.store.state, sessionId).pending !== void 0;
|
|
1187
|
+
}
|
|
1188
|
+
};
|
|
1189
|
+
function isRecord$1(value) {
|
|
1190
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1191
|
+
}
|
|
1192
|
+
function optionalText(value) {
|
|
1193
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
1194
|
+
}
|
|
1195
|
+
function count(value) {
|
|
1196
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
1197
|
+
}
|
|
1198
|
+
/** The page never trusts a frame's shape: a malformed field drops, the rest still renders. */
|
|
1199
|
+
const MAX_CONFLICTS = 100;
|
|
1200
|
+
function optionalChanges(value) {
|
|
1201
|
+
if (!isRecord$1(value)) return void 0;
|
|
1202
|
+
const added = count(value.added);
|
|
1203
|
+
const removed = count(value.removed);
|
|
1204
|
+
const files = count(value.files);
|
|
1205
|
+
if (added === void 0 || removed === void 0 || files === void 0) return void 0;
|
|
1206
|
+
const branch = optionalText(value.branch);
|
|
1207
|
+
const base = optionalText(value.base);
|
|
1208
|
+
const upstream = isRecord$1(value.upstream) ? value.upstream : void 0;
|
|
1209
|
+
const ahead = count(upstream?.ahead);
|
|
1210
|
+
const behind = count(upstream?.behind);
|
|
1211
|
+
const upstreamRef = optionalText(upstream?.ref);
|
|
1212
|
+
const conflicts = isRecord$1(value.rebase) && Array.isArray(value.rebase.conflicts) ? value.rebase.conflicts.filter((file) => typeof file === "string").slice(0, MAX_CONFLICTS) : void 0;
|
|
1213
|
+
return {
|
|
1214
|
+
added,
|
|
1215
|
+
removed,
|
|
1216
|
+
files,
|
|
1217
|
+
...branch === void 0 ? {} : { branch },
|
|
1218
|
+
...base === void 0 ? {} : { base },
|
|
1219
|
+
...value.truncated === true ? { truncated: true } : {},
|
|
1220
|
+
...upstreamRef === void 0 || ahead === void 0 || behind === void 0 ? {} : { upstream: {
|
|
1221
|
+
ref: upstreamRef,
|
|
1222
|
+
ahead,
|
|
1223
|
+
behind
|
|
1224
|
+
} },
|
|
1225
|
+
...conflicts === void 0 ? {} : { rebase: { conflicts } }
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
function optionalErrorTarget(value) {
|
|
1229
|
+
if (!isRecord$1(value)) return void 0;
|
|
1230
|
+
if (value.action === "pull" || value.action === "rebase" || value.action === "abort-rebase") return { action: value.action };
|
|
1231
|
+
if (value.action === "push") return value.forceRequired === true ? {
|
|
1232
|
+
action: "push",
|
|
1233
|
+
forceRequired: true
|
|
1234
|
+
} : { action: "push" };
|
|
1235
|
+
}
|
|
1236
|
+
/** The session data channel: 'git_changes' payloads into the store. */
|
|
1237
|
+
const gitChangesChannel = gitChanges.channel({
|
|
1238
|
+
channel: GIT_CHANGES_TYPE,
|
|
1239
|
+
parse(input) {
|
|
1240
|
+
if (!isRecord$1(input)) return null;
|
|
1241
|
+
return {
|
|
1242
|
+
changes: optionalChanges(input.changes),
|
|
1243
|
+
pending: optionalText(input.pending),
|
|
1244
|
+
error: optionalText(input.error),
|
|
1245
|
+
errorTarget: optionalErrorTarget(input.errorTarget)
|
|
1246
|
+
};
|
|
1247
|
+
},
|
|
1248
|
+
reduce(_current, next) {
|
|
1249
|
+
return next;
|
|
1250
|
+
}
|
|
1251
|
+
});
|
|
1252
|
+
/**
|
|
1253
|
+
* Asks the hub to pull, push, rebase or abort a paused rebase. `force` is only
|
|
1254
|
+
* ever sent after the reader confirmed a lease force for a rejected push.
|
|
1255
|
+
*/
|
|
1256
|
+
function requestGitSync(sessionId, action, force = false) {
|
|
1257
|
+
const payload = action === "push" && force ? {
|
|
1258
|
+
action: "push",
|
|
1259
|
+
force: true
|
|
1260
|
+
} : { action };
|
|
1261
|
+
changesSender.store.state?.({
|
|
1262
|
+
type: GIT_CHANGES_TYPE,
|
|
1263
|
+
sessionId,
|
|
1264
|
+
payload
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
//#endregion
|
|
1268
|
+
//#region src/extensions/workspaces/sessions/(frontend)/activity-group/git.native.ts
|
|
1269
|
+
var git_native_default = defineActivityGroup({
|
|
1270
|
+
keys: "g w",
|
|
1271
|
+
icon: () => createElement(BranchIcon),
|
|
1272
|
+
activeSource: {
|
|
1273
|
+
subscribe(listener) {
|
|
1274
|
+
const stopWorktrees = worktreeActivitySource.subscribe(listener);
|
|
1275
|
+
const stopChanges = gitChangesActivitySource.subscribe(listener);
|
|
1276
|
+
return () => {
|
|
1277
|
+
stopWorktrees();
|
|
1278
|
+
stopChanges();
|
|
1279
|
+
};
|
|
1280
|
+
},
|
|
1281
|
+
isActive(sessionId) {
|
|
1282
|
+
return worktreeActivitySource.isActive(sessionId) || gitChangesActivitySource.isActive(sessionId);
|
|
1283
|
+
}
|
|
1284
|
+
},
|
|
1285
|
+
marksBackgroundWork: false,
|
|
1286
|
+
transientTab: worktreesTab,
|
|
1287
|
+
order: 30
|
|
1288
|
+
});
|
|
1289
|
+
//#endregion
|
|
1290
|
+
//#region src/extensions/workspaces/sessions/(frontend)/channel/git-changes.native.ts
|
|
1291
|
+
var git_changes_native_default = defineChannelFile(gitChangesChannel);
|
|
1292
|
+
//#endregion
|
|
1293
|
+
//#region src/extensions/workspaces/sessions/(frontend)/channel/git-worktrees.native.ts
|
|
1294
|
+
var git_worktrees_native_default = defineChannelFile(worktreesChannel);
|
|
1295
|
+
//#endregion
|
|
1296
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/gitReviewApi.native.ts
|
|
1297
|
+
/**
|
|
1298
|
+
* The review tab's half of the session API: the changed-file list and one
|
|
1299
|
+
* file's hunks.
|
|
1300
|
+
*
|
|
1301
|
+
* The generated client owns the URL and the sealed transport, so nothing here
|
|
1302
|
+
* spells a route or reaches for `fetch`. What travels is file contents, which a
|
|
1303
|
+
* tunnel's relay must never see in the clear.
|
|
1304
|
+
*/
|
|
1305
|
+
const UNREACHABLE = "The session is unreachable.";
|
|
1306
|
+
function isRecord(value) {
|
|
1307
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1308
|
+
}
|
|
1309
|
+
/** The message a reader is shown: the route's own words, or why there were none. */
|
|
1310
|
+
function messageOf(result) {
|
|
1311
|
+
if (result.status === 0) return UNREACHABLE;
|
|
1312
|
+
return result.error === "" ? `The session answered ${String(result.status)}.` : result.error;
|
|
1313
|
+
}
|
|
1314
|
+
async function fetchReviewSummary(sessionId, signal) {
|
|
1315
|
+
const result = await api.session(sessionId).review(signal === void 0 ? {} : { signal });
|
|
1316
|
+
if (!result.ok) return {
|
|
1317
|
+
ok: false,
|
|
1318
|
+
error: messageOf(result)
|
|
1319
|
+
};
|
|
1320
|
+
if (!isRecord(result.data) || !Array.isArray(result.data.files)) return {
|
|
1321
|
+
ok: false,
|
|
1322
|
+
error: "The session answered with no file list."
|
|
1323
|
+
};
|
|
1324
|
+
return {
|
|
1325
|
+
ok: true,
|
|
1326
|
+
summary: result.data
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
async function fetchReviewFile(sessionId, path, signal) {
|
|
1330
|
+
const result = await api.session(sessionId).reviewFile({
|
|
1331
|
+
query: { path },
|
|
1332
|
+
...signal === void 0 ? {} : { signal }
|
|
1333
|
+
});
|
|
1334
|
+
if (!result.ok) return {
|
|
1335
|
+
ok: false,
|
|
1336
|
+
error: messageOf(result)
|
|
1337
|
+
};
|
|
1338
|
+
if (!isRecord(result.data) || !Array.isArray(result.data.hunks)) return {
|
|
1339
|
+
ok: false,
|
|
1340
|
+
error: "The session answered with no diff."
|
|
1341
|
+
};
|
|
1342
|
+
return {
|
|
1343
|
+
ok: true,
|
|
1344
|
+
diff: result.data
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
//#endregion
|
|
1348
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/gitReviewSelection.ts
|
|
1349
|
+
/** A different side starts a fresh selection. The second same-side tap completes it. */
|
|
1350
|
+
function selectReviewLine(anchor, row) {
|
|
1351
|
+
const side = row.marker === "-" ? "old" : "new";
|
|
1352
|
+
if (!anchor || anchor.side !== side) return {
|
|
1353
|
+
range: {
|
|
1354
|
+
side,
|
|
1355
|
+
startLine: row.line,
|
|
1356
|
+
endLine: row.line
|
|
1357
|
+
},
|
|
1358
|
+
complete: false
|
|
1359
|
+
};
|
|
1360
|
+
return {
|
|
1361
|
+
range: {
|
|
1362
|
+
side,
|
|
1363
|
+
startLine: Math.min(anchor.startLine, row.line),
|
|
1364
|
+
endLine: Math.max(anchor.startLine, row.line)
|
|
1365
|
+
},
|
|
1366
|
+
complete: true
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
function reviewRangeRows(rows, range) {
|
|
1370
|
+
return rows.filter((row) => (row.marker === "-" ? "old" : "new") === range.side && row.line >= range.startLine && row.line <= range.endLine);
|
|
1371
|
+
}
|
|
1372
|
+
//#endregion
|
|
1373
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/gitReviewStore.native.ts
|
|
1374
|
+
const gitReview = defineSessionStore({ comments: [] });
|
|
1375
|
+
function addReviewComment(sessionId, comment) {
|
|
1376
|
+
const id = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
1377
|
+
gitReview.update(sessionId, (current) => ({ comments: [...current.comments, {
|
|
1378
|
+
...comment,
|
|
1379
|
+
id
|
|
1380
|
+
}] }));
|
|
1381
|
+
}
|
|
1382
|
+
function removeReviewComment(sessionId, id) {
|
|
1383
|
+
gitReview.update(sessionId, (current) => ({ comments: current.comments.filter((comment) => comment.id !== id) }));
|
|
1384
|
+
}
|
|
1385
|
+
function clearReviewComments(sessionId) {
|
|
1386
|
+
gitReview.update(sessionId, () => ({ comments: [] }));
|
|
1387
|
+
}
|
|
1388
|
+
//#endregion
|
|
1389
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/gitReviewView.ts
|
|
1390
|
+
/**
|
|
1391
|
+
* Pure wording for what the review tab sends the agent, and the one send.
|
|
1392
|
+
*
|
|
1393
|
+
* Both messages go as `follow_up`: the host starts a turn when idle, or after
|
|
1394
|
+
* the current one, without treating a review send as an explicit Queue action.
|
|
1395
|
+
* A `prompt` frame instead fails while a turn runs.
|
|
1396
|
+
*/
|
|
1397
|
+
/** The opening line of a review, naming what was reviewed. */
|
|
1398
|
+
function reviewHeading(count, branch, base) {
|
|
1399
|
+
return `I reviewed ${branch === void 0 ? "this checkout" : `branch ${branch}`} (${base === void 0 ? "its uncommitted changes" : `its changes against ${base}`}) and left ${count === 1 ? "one review comment" : `${String(count)} review comments`}. ${count === 1 ? "Please address it." : "Please address them all."}`;
|
|
1400
|
+
}
|
|
1401
|
+
/** What "ask agent to resolve" sends: the files, and the steps that finish a paused rebase. */
|
|
1402
|
+
function conflictPrompt(branch, conflicts) {
|
|
1403
|
+
const files = conflicts.length === 0 ? "- (git reports no unmerged paths yet)" : conflicts.map((file) => `- ${file}`).join("\n");
|
|
1404
|
+
return [
|
|
1405
|
+
`A rebase of ${branch ?? "this checkout"} stopped on conflicts in:`,
|
|
1406
|
+
"",
|
|
1407
|
+
files,
|
|
1408
|
+
"",
|
|
1409
|
+
"Please resolve them:",
|
|
1410
|
+
"1. Fix the conflict markers in each file, keeping the intent of both sides.",
|
|
1411
|
+
"2. `git add` each resolved file.",
|
|
1412
|
+
"3. Run `GIT_EDITOR=true git rebase --continue`, and repeat if it stops again.",
|
|
1413
|
+
"",
|
|
1414
|
+
"Do not push. I will push from the cockpit once the rebase is done."
|
|
1415
|
+
].join("\n");
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* The one sync the bar fills in: pull while the branch is behind (a push would
|
|
1419
|
+
* be rejected), push while it is only ahead or has no upstream yet. Nothing
|
|
1420
|
+
* while a rebase is paused, detached, or already level.
|
|
1421
|
+
*/
|
|
1422
|
+
function suggestedSyncAction(changes) {
|
|
1423
|
+
if (changes?.branch === void 0 || changes.rebase !== void 0) return void 0;
|
|
1424
|
+
if (changes.upstream === void 0) return "push";
|
|
1425
|
+
if (changes.upstream.behind > 0) return "pull";
|
|
1426
|
+
return changes.upstream.ahead > 0 ? "push" : void 0;
|
|
1427
|
+
}
|
|
1428
|
+
/** "2 to push, 1 new on origin/feat/git", or "Up to date with origin/feat/git". */
|
|
1429
|
+
function upstreamSummary(upstream) {
|
|
1430
|
+
if (upstream === void 0) return "No upstream yet";
|
|
1431
|
+
const parts = [...upstream.ahead > 0 ? [`${String(upstream.ahead)} to push`] : [], ...upstream.behind > 0 ? [`${String(upstream.behind)} new on ${upstream.ref}`] : []];
|
|
1432
|
+
return parts.length === 0 ? `Up to date with ${upstream.ref}` : parts.join(", ");
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Hands one deferred follow-up to the session. Returns the reason it failed,
|
|
1436
|
+
* or undefined once sent. The sender throws when the session is disconnected.
|
|
1437
|
+
*/
|
|
1438
|
+
function deliverToAgent(send, sessionId, message) {
|
|
1439
|
+
try {
|
|
1440
|
+
send(sessionId, {
|
|
1441
|
+
type: "follow_up",
|
|
1442
|
+
message
|
|
1443
|
+
});
|
|
1444
|
+
return;
|
|
1445
|
+
} catch (error) {
|
|
1446
|
+
return error instanceof Error ? error.message : "The session did not take the message.";
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
//#endregion
|
|
1450
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/GitReviewView.native.tsx
|
|
1451
|
+
function Action({ children, variant = "outline", disabled = false, onPress }) {
|
|
1452
|
+
const { theme } = useUnistyles();
|
|
1453
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
1454
|
+
onPress,
|
|
1455
|
+
variant,
|
|
1456
|
+
disabled,
|
|
1457
|
+
style: [styles$1.action, variant === "primary" && styles$1.primary],
|
|
1458
|
+
children: variant === "primary" ? /* @__PURE__ */ jsx(Text, {
|
|
1459
|
+
style: [styles$1.primaryLabel, { color: theme.scheme === "dark" ? theme.colors.rail : theme.colors["on-selected"] }],
|
|
1460
|
+
children
|
|
1461
|
+
}) : children
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
function Footer({ count, discard = false, confirmation = false, onReview, onSend, onDiscard }) {
|
|
1465
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
1466
|
+
style: styles$1.footer,
|
|
1467
|
+
children: [discard ? /* @__PURE__ */ jsx(Action, {
|
|
1468
|
+
variant: "ghost",
|
|
1469
|
+
onPress: onDiscard,
|
|
1470
|
+
children: "Discard"
|
|
1471
|
+
}) : /* @__PURE__ */ jsxs(Pressable, {
|
|
1472
|
+
accessibilityRole: "button",
|
|
1473
|
+
accessibilityLabel: "Review queued comments",
|
|
1474
|
+
onPress: onReview,
|
|
1475
|
+
style: [styles$1.inline, styles$1.action],
|
|
1476
|
+
children: [/* @__PURE__ */ jsx(MessageIcon, {
|
|
1477
|
+
size: 16,
|
|
1478
|
+
color: "blue"
|
|
1479
|
+
}), /* @__PURE__ */ jsxs(Text, {
|
|
1480
|
+
style: styles$1.dim,
|
|
1481
|
+
children: [
|
|
1482
|
+
count,
|
|
1483
|
+
" ",
|
|
1484
|
+
count === 1 ? "comment" : "comments",
|
|
1485
|
+
" queued"
|
|
1486
|
+
]
|
|
1487
|
+
})]
|
|
1488
|
+
}), /* @__PURE__ */ jsx(Action, {
|
|
1489
|
+
variant: confirmation ? "outline" : "primary",
|
|
1490
|
+
disabled: count === 0,
|
|
1491
|
+
onPress: onSend,
|
|
1492
|
+
children: "Send review"
|
|
1493
|
+
})]
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
function SyncCard({ changes, busy = false, onSync }) {
|
|
1497
|
+
const suggested = suggestedSyncAction(changes);
|
|
1498
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
1499
|
+
style: styles$1.card,
|
|
1500
|
+
children: [
|
|
1501
|
+
/* @__PURE__ */ jsxs(View, {
|
|
1502
|
+
style: styles$1.inline,
|
|
1503
|
+
children: [
|
|
1504
|
+
/* @__PURE__ */ jsx(BranchIcon, {
|
|
1505
|
+
size: 18,
|
|
1506
|
+
color: "dim"
|
|
1507
|
+
}),
|
|
1508
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1509
|
+
style: styles$1.branch,
|
|
1510
|
+
children: changes?.branch ?? "Detached HEAD"
|
|
1511
|
+
}),
|
|
1512
|
+
/* @__PURE__ */ jsx(View, { style: styles$1.grow }),
|
|
1513
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1514
|
+
style: styles$1.green,
|
|
1515
|
+
children: ["+", changes?.added ?? 0]
|
|
1516
|
+
}),
|
|
1517
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1518
|
+
style: styles$1.red,
|
|
1519
|
+
children: ["-", changes?.removed ?? 0]
|
|
1520
|
+
})
|
|
1521
|
+
]
|
|
1522
|
+
}),
|
|
1523
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1524
|
+
style: [styles$1.dim, styles$1.mono],
|
|
1525
|
+
children: changes?.base ? `vs ${changes.base}` : "Uncommitted changes"
|
|
1526
|
+
}),
|
|
1527
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1528
|
+
style: styles$1.dim,
|
|
1529
|
+
children: upstreamSummary(changes?.upstream)
|
|
1530
|
+
}),
|
|
1531
|
+
/* @__PURE__ */ jsx(View, {
|
|
1532
|
+
style: styles$1.inline,
|
|
1533
|
+
children: [
|
|
1534
|
+
"pull",
|
|
1535
|
+
"push",
|
|
1536
|
+
"rebase"
|
|
1537
|
+
].map((action) => /* @__PURE__ */ jsx(Button, {
|
|
1538
|
+
variant: suggested === action ? "outline" : "ghost",
|
|
1539
|
+
style: suggested === action ? [styles$1.action, styles$1.suggested] : styles$1.action,
|
|
1540
|
+
disabled: busy || changes?.rebase !== void 0 || (action === "pull" ? changes?.upstream === void 0 : action === "push" ? changes?.branch === void 0 : changes?.base === void 0),
|
|
1541
|
+
onPress: () => onSync?.(action),
|
|
1542
|
+
children: action === "pull" ? "Pull" : action === "push" ? "Push" : "Rebase"
|
|
1543
|
+
}, action))
|
|
1544
|
+
})
|
|
1545
|
+
]
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
function FileList({ files, comments = [], onFile }) {
|
|
1549
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
1550
|
+
style: styles$1.list,
|
|
1551
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
1552
|
+
style: styles$1.section,
|
|
1553
|
+
children: [files.length, " files"]
|
|
1554
|
+
}), files.map((file) => /* @__PURE__ */ jsxs(Pressable, {
|
|
1555
|
+
accessibilityRole: "button",
|
|
1556
|
+
testID: `git-review-file-${file.path}`,
|
|
1557
|
+
onPress: () => onFile?.(file.path),
|
|
1558
|
+
style: styles$1.fileRow,
|
|
1559
|
+
children: [
|
|
1560
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1561
|
+
style: file.status === "added" ? styles$1.green : file.status === "untracked" ? styles$1.blue : styles$1.yellow,
|
|
1562
|
+
children: file.status[0]?.toUpperCase()
|
|
1563
|
+
}),
|
|
1564
|
+
/* @__PURE__ */ jsxs(View, {
|
|
1565
|
+
style: styles$1.grow,
|
|
1566
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
1567
|
+
style: styles$1.body,
|
|
1568
|
+
children: file.path.split("/").at(-1)
|
|
1569
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
1570
|
+
numberOfLines: 1,
|
|
1571
|
+
style: [styles$1.dim, styles$1.mono],
|
|
1572
|
+
children: file.path.split("/").slice(0, -1).join("/") || "."
|
|
1573
|
+
})]
|
|
1574
|
+
}),
|
|
1575
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
1576
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1577
|
+
style: styles$1.green,
|
|
1578
|
+
children: ["+", file.added]
|
|
1579
|
+
}),
|
|
1580
|
+
" ",
|
|
1581
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1582
|
+
style: styles$1.red,
|
|
1583
|
+
children: ["-", file.removed]
|
|
1584
|
+
})
|
|
1585
|
+
] }),
|
|
1586
|
+
comments.some((comment) => comment.path === file.path) ? /* @__PURE__ */ jsxs(View, {
|
|
1587
|
+
style: styles$1.inline,
|
|
1588
|
+
children: [/* @__PURE__ */ jsx(MessageIcon, {
|
|
1589
|
+
size: 12,
|
|
1590
|
+
color: "blue"
|
|
1591
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
1592
|
+
style: styles$1.blue,
|
|
1593
|
+
children: comments.filter((comment) => comment.path === file.path).length
|
|
1594
|
+
})]
|
|
1595
|
+
}) : null,
|
|
1596
|
+
/* @__PURE__ */ jsx(ChevronRightIcon, {
|
|
1597
|
+
size: 16,
|
|
1598
|
+
color: "dim"
|
|
1599
|
+
})
|
|
1600
|
+
]
|
|
1601
|
+
}, file.path))]
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
function RebaseCard({ confirmation = false, conflicts, busy = false, onResolve, onAbort }) {
|
|
1605
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
1606
|
+
style: [styles$1.card, styles$1.caution],
|
|
1607
|
+
children: [
|
|
1608
|
+
/* @__PURE__ */ jsxs(View, {
|
|
1609
|
+
style: styles$1.inline,
|
|
1610
|
+
children: [/* @__PURE__ */ jsx(AlertIcon, {
|
|
1611
|
+
size: 20,
|
|
1612
|
+
color: "yellow"
|
|
1613
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
1614
|
+
style: styles$1.section,
|
|
1615
|
+
children: "Rebase paused"
|
|
1616
|
+
})]
|
|
1617
|
+
}),
|
|
1618
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1619
|
+
style: styles$1.body,
|
|
1620
|
+
children: "Resolve conflicts or ask Po to do it for you."
|
|
1621
|
+
}),
|
|
1622
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1623
|
+
style: [styles$1.dim, styles$1.mono],
|
|
1624
|
+
children: conflicts.join("\n")
|
|
1625
|
+
}),
|
|
1626
|
+
/* @__PURE__ */ jsx(Action, {
|
|
1627
|
+
disabled: busy,
|
|
1628
|
+
onPress: onResolve,
|
|
1629
|
+
variant: confirmation ? "outline" : "primary",
|
|
1630
|
+
children: "Ask Po to resolve"
|
|
1631
|
+
}),
|
|
1632
|
+
/* @__PURE__ */ jsx(Action, {
|
|
1633
|
+
disabled: busy,
|
|
1634
|
+
onPress: onAbort,
|
|
1635
|
+
variant: "danger-outline",
|
|
1636
|
+
children: "Abort rebase"
|
|
1637
|
+
})
|
|
1638
|
+
]
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
function FileHeader({ entry, index, total, onPrevious, onNext, onChanges }) {
|
|
1642
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
1643
|
+
style: styles$1.content,
|
|
1644
|
+
children: [
|
|
1645
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1646
|
+
style: styles$1.heading,
|
|
1647
|
+
children: entry.path.split("/").at(-1)
|
|
1648
|
+
}),
|
|
1649
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1650
|
+
style: [styles$1.dim, styles$1.mono],
|
|
1651
|
+
children: entry.path.split("/").slice(0, -1).join("/") || "."
|
|
1652
|
+
}),
|
|
1653
|
+
/* @__PURE__ */ jsxs(View, {
|
|
1654
|
+
style: styles$1.inline,
|
|
1655
|
+
children: [
|
|
1656
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1657
|
+
style: styles$1.dim,
|
|
1658
|
+
children: [entry.status[0]?.toUpperCase(), entry.status.slice(1)]
|
|
1659
|
+
}),
|
|
1660
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1661
|
+
style: styles$1.green,
|
|
1662
|
+
children: ["+", entry.added]
|
|
1663
|
+
}),
|
|
1664
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1665
|
+
style: styles$1.red,
|
|
1666
|
+
children: ["-", entry.removed]
|
|
1667
|
+
})
|
|
1668
|
+
]
|
|
1669
|
+
}),
|
|
1670
|
+
/* @__PURE__ */ jsxs(View, {
|
|
1671
|
+
style: styles$1.navigation,
|
|
1672
|
+
children: [
|
|
1673
|
+
/* @__PURE__ */ jsx(Action, {
|
|
1674
|
+
variant: "ghost",
|
|
1675
|
+
disabled: index === 0,
|
|
1676
|
+
onPress: onPrevious,
|
|
1677
|
+
children: "Previous"
|
|
1678
|
+
}),
|
|
1679
|
+
/* @__PURE__ */ jsx(Pressable, {
|
|
1680
|
+
accessibilityRole: "button",
|
|
1681
|
+
accessibilityLabel: "Return to changes",
|
|
1682
|
+
onPress: onChanges,
|
|
1683
|
+
style: [styles$1.inline, styles$1.action],
|
|
1684
|
+
children: /* @__PURE__ */ jsxs(Text, {
|
|
1685
|
+
style: styles$1.dim,
|
|
1686
|
+
children: [
|
|
1687
|
+
onChanges ? "Changes · " : "",
|
|
1688
|
+
index + 1,
|
|
1689
|
+
" of ",
|
|
1690
|
+
total
|
|
1691
|
+
]
|
|
1692
|
+
})
|
|
1693
|
+
}),
|
|
1694
|
+
/* @__PURE__ */ jsx(Action, {
|
|
1695
|
+
variant: "ghost",
|
|
1696
|
+
disabled: index === total - 1,
|
|
1697
|
+
onPress: onNext,
|
|
1698
|
+
children: "Next"
|
|
1699
|
+
})
|
|
1700
|
+
]
|
|
1701
|
+
})
|
|
1702
|
+
]
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
const DiffRows = memo(function DiffRows({ rows, comments = [], selection, onLine, showInlineComments = true, gutterDigits }) {
|
|
1706
|
+
const anchoredComments = useMemo(() => {
|
|
1707
|
+
const index = /* @__PURE__ */ new Map();
|
|
1708
|
+
for (const comment of comments) {
|
|
1709
|
+
const key = `${comment.side}:${comment.endLine ?? comment.startLine}`;
|
|
1710
|
+
const entries = index.get(key);
|
|
1711
|
+
if (entries) entries.push(comment);
|
|
1712
|
+
else index.set(key, [comment]);
|
|
1713
|
+
}
|
|
1714
|
+
return index;
|
|
1715
|
+
}, [comments]);
|
|
1716
|
+
const digits = gutterDigits ?? rows.reduce((max, row) => Math.max(max, String(row.line).length), 1);
|
|
1717
|
+
return /* @__PURE__ */ jsx(View, { children: rows.map((row, i) => {
|
|
1718
|
+
const side = row.marker === "-" ? "old" : "new";
|
|
1719
|
+
const anchored = anchoredComments.get(`${side}:${row.line}`) ?? [];
|
|
1720
|
+
return /* @__PURE__ */ jsxs(View, { children: [/* @__PURE__ */ jsxs(Pressable, {
|
|
1721
|
+
accessibilityRole: "button",
|
|
1722
|
+
accessibilityLabel: `Select ${side} line ${row.line}`,
|
|
1723
|
+
onPress: () => onLine?.(row),
|
|
1724
|
+
style: [
|
|
1725
|
+
styles$1.diffRow,
|
|
1726
|
+
row.marker === "+" && styles$1.added,
|
|
1727
|
+
row.marker === "-" && styles$1.removed,
|
|
1728
|
+
selection?.side === side && row.line >= selection.startLine && row.line <= selection.endLine && styles$1.selection
|
|
1729
|
+
],
|
|
1730
|
+
children: [
|
|
1731
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1732
|
+
numberOfLines: 1,
|
|
1733
|
+
style: [styles$1.mono, styles$1.gutter],
|
|
1734
|
+
children: String(row.line).padStart(digits, "\xA0")
|
|
1735
|
+
}),
|
|
1736
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1737
|
+
style: [styles$1.dim, styles$1.mono],
|
|
1738
|
+
children: row.marker
|
|
1739
|
+
}),
|
|
1740
|
+
/* @__PURE__ */ jsx(SyntaxLine, {
|
|
1741
|
+
text: row.content || " ",
|
|
1742
|
+
style: [styles$1.mono, styles$1.code]
|
|
1743
|
+
}),
|
|
1744
|
+
anchored.length ? /* @__PURE__ */ jsx(MessageIcon, {
|
|
1745
|
+
size: 12,
|
|
1746
|
+
color: "blue"
|
|
1747
|
+
}) : null
|
|
1748
|
+
]
|
|
1749
|
+
}), showInlineComments && anchored.map((comment) => /* @__PURE__ */ jsxs(View, {
|
|
1750
|
+
style: styles$1.inlineComment,
|
|
1751
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
1752
|
+
style: styles$1.blue,
|
|
1753
|
+
children: ["Queued comment · ", lineLabel(comment)]
|
|
1754
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
1755
|
+
style: styles$1.body,
|
|
1756
|
+
children: comment.body
|
|
1757
|
+
})]
|
|
1758
|
+
}, comment.id))] }, i);
|
|
1759
|
+
}) });
|
|
1760
|
+
});
|
|
1761
|
+
function lineLabel(comment) {
|
|
1762
|
+
return comment.startLine === void 0 ? "File comment" : comment.endLine === comment.startLine || comment.endLine === void 0 ? `Line ${comment.startLine}` : `Lines ${comment.startLine} to ${comment.endLine}`;
|
|
1763
|
+
}
|
|
1764
|
+
function CommentSheet({ range, snippet, value, onChange, onAdd, onCancel }) {
|
|
1765
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
1766
|
+
style: styles$1.sheet,
|
|
1767
|
+
children: [
|
|
1768
|
+
/* @__PURE__ */ jsx(View, { style: styles$1.handle }),
|
|
1769
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1770
|
+
style: styles$1.sheetTitle,
|
|
1771
|
+
children: range ? lineLabel(range) : "Comment on file"
|
|
1772
|
+
}),
|
|
1773
|
+
snippet ? /* @__PURE__ */ jsx(View, {
|
|
1774
|
+
style: styles$1.quote,
|
|
1775
|
+
children: /* @__PURE__ */ jsx(SyntaxLine, {
|
|
1776
|
+
text: snippet,
|
|
1777
|
+
style: [styles$1.body, styles$1.mono]
|
|
1778
|
+
})
|
|
1779
|
+
}) : null,
|
|
1780
|
+
/* @__PURE__ */ jsx(Textarea, {
|
|
1781
|
+
accessibilityLabel: "What should change here?",
|
|
1782
|
+
placeholder: "What should change here?",
|
|
1783
|
+
rows: 4,
|
|
1784
|
+
value,
|
|
1785
|
+
onChangeText: onChange
|
|
1786
|
+
}),
|
|
1787
|
+
/* @__PURE__ */ jsx(Action, {
|
|
1788
|
+
variant: "primary",
|
|
1789
|
+
disabled: value !== void 0 && value.trim() === "",
|
|
1790
|
+
onPress: onAdd,
|
|
1791
|
+
children: "Add comment"
|
|
1792
|
+
}),
|
|
1793
|
+
/* @__PURE__ */ jsx(Action, {
|
|
1794
|
+
variant: "ghost",
|
|
1795
|
+
onPress: onCancel,
|
|
1796
|
+
children: "Cancel"
|
|
1797
|
+
})
|
|
1798
|
+
]
|
|
1799
|
+
});
|
|
1800
|
+
}
|
|
1801
|
+
function CommentCards({ comments, onRemove }) {
|
|
1802
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
1803
|
+
style: styles$1.content,
|
|
1804
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
1805
|
+
style: styles$1.body,
|
|
1806
|
+
children: [
|
|
1807
|
+
comments.length === 2 ? "Two" : comments.length,
|
|
1808
|
+
" ",
|
|
1809
|
+
comments.length === 1 ? "comment" : "comments",
|
|
1810
|
+
" queued for Po, whenever you are ready."
|
|
1811
|
+
]
|
|
1812
|
+
}), comments.map((comment) => /* @__PURE__ */ jsxs(View, {
|
|
1813
|
+
style: styles$1.card,
|
|
1814
|
+
children: [
|
|
1815
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1816
|
+
style: [styles$1.body, styles$1.mono],
|
|
1817
|
+
children: comment.relPath
|
|
1818
|
+
}),
|
|
1819
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1820
|
+
style: styles$1.dim,
|
|
1821
|
+
children: lineLabel(comment)
|
|
1822
|
+
}),
|
|
1823
|
+
comment.snippet ? /* @__PURE__ */ jsx(View, {
|
|
1824
|
+
style: styles$1.quote,
|
|
1825
|
+
children: /* @__PURE__ */ jsx(SyntaxLine, {
|
|
1826
|
+
text: comment.snippet,
|
|
1827
|
+
style: [styles$1.body, styles$1.mono]
|
|
1828
|
+
})
|
|
1829
|
+
}) : null,
|
|
1830
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1831
|
+
style: styles$1.body,
|
|
1832
|
+
children: comment.body
|
|
1833
|
+
}),
|
|
1834
|
+
/* @__PURE__ */ jsx(View, {
|
|
1835
|
+
style: styles$1.remove,
|
|
1836
|
+
children: /* @__PURE__ */ jsx(Action, {
|
|
1837
|
+
variant: "ghost",
|
|
1838
|
+
onPress: () => onRemove?.(comment.id),
|
|
1839
|
+
children: "Remove"
|
|
1840
|
+
})
|
|
1841
|
+
})
|
|
1842
|
+
]
|
|
1843
|
+
}, comment.id))]
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
const styles$1 = StyleSheet.create((theme) => ({
|
|
1847
|
+
heading: {
|
|
1848
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
1849
|
+
fontSize: theme.fontSize["2xl"],
|
|
1850
|
+
fontWeight: "600",
|
|
1851
|
+
color: theme.colors.hi,
|
|
1852
|
+
uni__dependencies: [0]
|
|
1853
|
+
},
|
|
1854
|
+
sheetTitle: {
|
|
1855
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
1856
|
+
fontSize: theme.fontSize.xl,
|
|
1857
|
+
fontWeight: "600",
|
|
1858
|
+
color: theme.colors.hi,
|
|
1859
|
+
uni__dependencies: [0]
|
|
1860
|
+
},
|
|
1861
|
+
section: {
|
|
1862
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
1863
|
+
fontSize: theme.fontSize.lg,
|
|
1864
|
+
fontWeight: "600",
|
|
1865
|
+
color: theme.colors.hi,
|
|
1866
|
+
uni__dependencies: [0]
|
|
1867
|
+
},
|
|
1868
|
+
body: {
|
|
1869
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
1870
|
+
fontSize: theme.fontSize.base,
|
|
1871
|
+
lineHeight: theme.lineHeightRelaxed.base,
|
|
1872
|
+
color: theme.colors.text,
|
|
1873
|
+
uni__dependencies: [0]
|
|
1874
|
+
},
|
|
1875
|
+
dim: {
|
|
1876
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
1877
|
+
fontSize: theme.fontSize.sm,
|
|
1878
|
+
lineHeight: theme.lineHeightRelaxed.sm,
|
|
1879
|
+
color: theme.colors.dim,
|
|
1880
|
+
uni__dependencies: [0]
|
|
1881
|
+
},
|
|
1882
|
+
mono: {
|
|
1883
|
+
fontFamily: fontOf(theme.fontFamily.mono),
|
|
1884
|
+
fontSize: theme.fontSize.sm,
|
|
1885
|
+
lineHeight: theme.lineHeightRelaxed.sm,
|
|
1886
|
+
uni__dependencies: [0]
|
|
1887
|
+
},
|
|
1888
|
+
branch: {
|
|
1889
|
+
fontFamily: fontOf(theme.fontFamily.mono),
|
|
1890
|
+
fontSize: theme.fontSize.lg,
|
|
1891
|
+
fontWeight: "600",
|
|
1892
|
+
color: theme.colors.hi,
|
|
1893
|
+
uni__dependencies: [0]
|
|
1894
|
+
},
|
|
1895
|
+
green: {
|
|
1896
|
+
fontSize: theme.fontSize.sm,
|
|
1897
|
+
color: theme.colors.green,
|
|
1898
|
+
uni__dependencies: [0]
|
|
1899
|
+
},
|
|
1900
|
+
red: {
|
|
1901
|
+
fontSize: theme.fontSize.sm,
|
|
1902
|
+
color: theme.colors.red,
|
|
1903
|
+
uni__dependencies: [0]
|
|
1904
|
+
},
|
|
1905
|
+
blue: {
|
|
1906
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
1907
|
+
fontSize: theme.fontSize.sm,
|
|
1908
|
+
color: theme.colors.blue,
|
|
1909
|
+
uni__dependencies: [0]
|
|
1910
|
+
},
|
|
1911
|
+
yellow: {
|
|
1912
|
+
fontSize: theme.fontSize.sm,
|
|
1913
|
+
color: theme.colors.yellow,
|
|
1914
|
+
uni__dependencies: [0]
|
|
1915
|
+
},
|
|
1916
|
+
content: {
|
|
1917
|
+
padding: theme.spacing(4),
|
|
1918
|
+
gap: theme.spacing(3),
|
|
1919
|
+
uni__dependencies: [0]
|
|
1920
|
+
},
|
|
1921
|
+
card: {
|
|
1922
|
+
borderWidth: 1,
|
|
1923
|
+
borderColor: theme.colors.border,
|
|
1924
|
+
borderRadius: theme.borderRadius.lg,
|
|
1925
|
+
padding: theme.spacing(3),
|
|
1926
|
+
gap: theme.spacing(2),
|
|
1927
|
+
uni__dependencies: [0]
|
|
1928
|
+
},
|
|
1929
|
+
inline: {
|
|
1930
|
+
flexDirection: "row",
|
|
1931
|
+
alignItems: "center",
|
|
1932
|
+
gap: theme.spacing(2),
|
|
1933
|
+
uni__dependencies: [0]
|
|
1934
|
+
},
|
|
1935
|
+
grow: {
|
|
1936
|
+
flex: 1,
|
|
1937
|
+
minWidth: 0
|
|
1938
|
+
},
|
|
1939
|
+
action: { minHeight: 44 },
|
|
1940
|
+
primary: {
|
|
1941
|
+
backgroundColor: theme.colors.green,
|
|
1942
|
+
uni__dependencies: [0]
|
|
1943
|
+
},
|
|
1944
|
+
primaryLabel: {
|
|
1945
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
1946
|
+
fontSize: theme.fontSize.base,
|
|
1947
|
+
fontWeight: "600",
|
|
1948
|
+
color: theme.scheme === "dark" ? theme.colors.rail : theme.colors["on-selected"],
|
|
1949
|
+
uni__dependencies: [0]
|
|
1950
|
+
},
|
|
1951
|
+
suggested: {
|
|
1952
|
+
backgroundColor: theme.colors["tint-green"],
|
|
1953
|
+
borderColor: theme.colors["edge-green"],
|
|
1954
|
+
flex: 1,
|
|
1955
|
+
uni__dependencies: [0]
|
|
1956
|
+
},
|
|
1957
|
+
list: {
|
|
1958
|
+
gap: theme.spacing(2),
|
|
1959
|
+
uni__dependencies: [0]
|
|
1960
|
+
},
|
|
1961
|
+
fileRow: {
|
|
1962
|
+
minHeight: 44,
|
|
1963
|
+
paddingVertical: theme.spacing(2),
|
|
1964
|
+
flexDirection: "row",
|
|
1965
|
+
alignItems: "center",
|
|
1966
|
+
gap: theme.spacing(2),
|
|
1967
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
1968
|
+
borderColor: theme.colors.border,
|
|
1969
|
+
uni__dependencies: [0]
|
|
1970
|
+
},
|
|
1971
|
+
footer: {
|
|
1972
|
+
minHeight: 76,
|
|
1973
|
+
padding: theme.spacing(4),
|
|
1974
|
+
flexDirection: "row",
|
|
1975
|
+
alignItems: "center",
|
|
1976
|
+
justifyContent: "space-between",
|
|
1977
|
+
gap: theme.spacing(2),
|
|
1978
|
+
borderTopWidth: 1,
|
|
1979
|
+
borderColor: theme.colors.border,
|
|
1980
|
+
backgroundColor: theme.colors.rail,
|
|
1981
|
+
uni__dependencies: [0]
|
|
1982
|
+
},
|
|
1983
|
+
caution: {
|
|
1984
|
+
backgroundColor: theme.colors["tint-yellow"],
|
|
1985
|
+
borderColor: theme.colors["edge-yellow"],
|
|
1986
|
+
uni__dependencies: [0]
|
|
1987
|
+
},
|
|
1988
|
+
failure: {
|
|
1989
|
+
backgroundColor: theme.colors["tint-red"],
|
|
1990
|
+
borderColor: theme.colors["edge-red"],
|
|
1991
|
+
uni__dependencies: [0]
|
|
1992
|
+
},
|
|
1993
|
+
sheet: {
|
|
1994
|
+
padding: theme.spacing(4),
|
|
1995
|
+
gap: theme.spacing(3),
|
|
1996
|
+
borderTopWidth: 1,
|
|
1997
|
+
borderColor: theme.colors.border,
|
|
1998
|
+
borderTopLeftRadius: theme.borderRadius.lg,
|
|
1999
|
+
borderTopRightRadius: theme.borderRadius.lg,
|
|
2000
|
+
backgroundColor: theme.colors.rail,
|
|
2001
|
+
uni__dependencies: [0]
|
|
2002
|
+
},
|
|
2003
|
+
handle: {
|
|
2004
|
+
height: 4,
|
|
2005
|
+
width: 40,
|
|
2006
|
+
borderRadius: theme.borderRadius.md,
|
|
2007
|
+
backgroundColor: theme.colors.border,
|
|
2008
|
+
alignSelf: "center",
|
|
2009
|
+
uni__dependencies: [0]
|
|
2010
|
+
},
|
|
2011
|
+
empty: {
|
|
2012
|
+
flex: 1,
|
|
2013
|
+
alignItems: "center",
|
|
2014
|
+
justifyContent: "center",
|
|
2015
|
+
gap: theme.spacing(3),
|
|
2016
|
+
padding: theme.spacing(6),
|
|
2017
|
+
uni__dependencies: [0]
|
|
2018
|
+
},
|
|
2019
|
+
navigation: {
|
|
2020
|
+
flexDirection: "row",
|
|
2021
|
+
justifyContent: "space-between",
|
|
2022
|
+
alignItems: "center"
|
|
2023
|
+
},
|
|
2024
|
+
diffRow: {
|
|
2025
|
+
flexDirection: "row",
|
|
2026
|
+
alignItems: "flex-start",
|
|
2027
|
+
gap: theme.spacing(2),
|
|
2028
|
+
paddingHorizontal: theme.spacing(2),
|
|
2029
|
+
paddingVertical: theme.spacing(1),
|
|
2030
|
+
borderLeftWidth: 3,
|
|
2031
|
+
borderLeftColor: theme.colors.bg,
|
|
2032
|
+
uni__dependencies: [0]
|
|
2033
|
+
},
|
|
2034
|
+
gutter: {
|
|
2035
|
+
color: theme.colors.dim,
|
|
2036
|
+
width: "auto",
|
|
2037
|
+
minWidth: 24,
|
|
2038
|
+
flexShrink: 0,
|
|
2039
|
+
textAlign: "right",
|
|
2040
|
+
uni__dependencies: [0]
|
|
2041
|
+
},
|
|
2042
|
+
disclosure: {
|
|
2043
|
+
padding: theme.spacing(3),
|
|
2044
|
+
gap: theme.spacing(2),
|
|
2045
|
+
borderTopWidth: 1,
|
|
2046
|
+
borderColor: theme.colors.border,
|
|
2047
|
+
backgroundColor: theme.colors.bg,
|
|
2048
|
+
uni__dependencies: [0]
|
|
2049
|
+
},
|
|
2050
|
+
code: {
|
|
2051
|
+
flex: 1,
|
|
2052
|
+
minWidth: 0,
|
|
2053
|
+
color: theme.colors.text,
|
|
2054
|
+
uni__dependencies: [0]
|
|
2055
|
+
},
|
|
2056
|
+
added: {
|
|
2057
|
+
backgroundColor: theme.colors["tint-green"],
|
|
2058
|
+
uni__dependencies: [0]
|
|
2059
|
+
},
|
|
2060
|
+
removed: {
|
|
2061
|
+
backgroundColor: theme.colors["tint-red"],
|
|
2062
|
+
uni__dependencies: [0]
|
|
2063
|
+
},
|
|
2064
|
+
selection: {
|
|
2065
|
+
borderLeftColor: theme.colors.green,
|
|
2066
|
+
uni__dependencies: [0]
|
|
2067
|
+
},
|
|
2068
|
+
inlineComment: {
|
|
2069
|
+
margin: theme.spacing(2),
|
|
2070
|
+
padding: theme.spacing(3),
|
|
2071
|
+
borderWidth: 1,
|
|
2072
|
+
borderColor: theme.colors["edge-blue"],
|
|
2073
|
+
borderRadius: theme.borderRadius.md,
|
|
2074
|
+
gap: theme.spacing(1),
|
|
2075
|
+
uni__dependencies: [0]
|
|
2076
|
+
},
|
|
2077
|
+
quote: {
|
|
2078
|
+
padding: theme.spacing(2),
|
|
2079
|
+
backgroundColor: theme.colors.panel,
|
|
2080
|
+
borderRadius: theme.borderRadius.md,
|
|
2081
|
+
uni__dependencies: [0]
|
|
2082
|
+
},
|
|
2083
|
+
remove: { alignSelf: "flex-end" }
|
|
2084
|
+
}));
|
|
2085
|
+
const DIFF_ROW_STEP = 200;
|
|
2086
|
+
function GitReviewView(props) {
|
|
2087
|
+
const { screen, activity, summary, file, path, comments } = props;
|
|
2088
|
+
const rows = useMemo(() => file?.hunks.flatMap((hunk) => hunk.rows) ?? [], [file?.hunks]);
|
|
2089
|
+
const gutterDigits = useMemo(() => rows.reduce((max, row) => Math.max(max, String(row.line).length), 1), [rows]);
|
|
2090
|
+
const fileComments = useMemo(() => comments.filter((comment) => comment.path === path), [comments, path]);
|
|
2091
|
+
const initialLimit = props.initialVisibleRows ?? DIFF_ROW_STEP;
|
|
2092
|
+
const [disclosure, setDisclosure] = useState({
|
|
2093
|
+
rows,
|
|
2094
|
+
path,
|
|
2095
|
+
limit: initialLimit
|
|
2096
|
+
});
|
|
2097
|
+
const current = disclosure.rows === rows && disclosure.path === path ? disclosure : {
|
|
2098
|
+
rows,
|
|
2099
|
+
path,
|
|
2100
|
+
limit: initialLimit
|
|
2101
|
+
};
|
|
2102
|
+
if (current !== disclosure) setDisclosure(current);
|
|
2103
|
+
const visibleRows = useMemo(() => rows.slice(0, current.limit), [rows, current.limit]);
|
|
2104
|
+
const index = summary?.files.findIndex((entry) => entry.path === path) ?? -1;
|
|
2105
|
+
const entry = summary?.files[index];
|
|
2106
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
2107
|
+
style: styles$1.grow,
|
|
2108
|
+
children: [
|
|
2109
|
+
screen === "review" ? /* @__PURE__ */ jsxs(View, {
|
|
2110
|
+
style: styles$1.inline,
|
|
2111
|
+
children: [/* @__PURE__ */ jsx(Action, {
|
|
2112
|
+
variant: "ghost",
|
|
2113
|
+
onPress: () => props.onScreen("changes"),
|
|
2114
|
+
children: "Changes"
|
|
2115
|
+
}), screen === "review" && path ? /* @__PURE__ */ jsx(Action, {
|
|
2116
|
+
variant: "ghost",
|
|
2117
|
+
onPress: () => props.onScreen("file"),
|
|
2118
|
+
children: "File diff"
|
|
2119
|
+
}) : null]
|
|
2120
|
+
}) : null,
|
|
2121
|
+
/* @__PURE__ */ jsxs(ScrollView, {
|
|
2122
|
+
style: styles$1.grow,
|
|
2123
|
+
children: [screen === "changes" ? /* @__PURE__ */ jsxs(View, {
|
|
2124
|
+
style: styles$1.content,
|
|
2125
|
+
children: [
|
|
2126
|
+
summary?.repository !== false ? /* @__PURE__ */ jsx(SyncCard, {
|
|
2127
|
+
changes: activity.changes,
|
|
2128
|
+
busy: activity.pending !== void 0,
|
|
2129
|
+
onSync: props.onSync
|
|
2130
|
+
}) : null,
|
|
2131
|
+
activity.pending ? /* @__PURE__ */ jsx(Text, {
|
|
2132
|
+
style: styles$1.dim,
|
|
2133
|
+
children: activity.pending
|
|
2134
|
+
}) : null,
|
|
2135
|
+
activity.error ? /* @__PURE__ */ jsxs(View, {
|
|
2136
|
+
style: [styles$1.card, props.forceRequired ? styles$1.caution : styles$1.failure],
|
|
2137
|
+
children: [
|
|
2138
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2139
|
+
style: styles$1.body,
|
|
2140
|
+
children: activity.error
|
|
2141
|
+
}),
|
|
2142
|
+
props.forceRequired ? /* @__PURE__ */ jsx(Action, {
|
|
2143
|
+
variant: "danger-outline",
|
|
2144
|
+
onPress: props.onForce,
|
|
2145
|
+
children: "Force push with lease"
|
|
2146
|
+
}) : null,
|
|
2147
|
+
/* @__PURE__ */ jsx(Action, {
|
|
2148
|
+
variant: "ghost",
|
|
2149
|
+
onPress: props.onDismiss,
|
|
2150
|
+
children: "Dismiss"
|
|
2151
|
+
})
|
|
2152
|
+
]
|
|
2153
|
+
}) : null,
|
|
2154
|
+
activity.changes?.rebase ? /* @__PURE__ */ jsx(RebaseCard, {
|
|
2155
|
+
conflicts: activity.changes.rebase.conflicts,
|
|
2156
|
+
busy: activity.pending !== void 0,
|
|
2157
|
+
onResolve: props.onResolve,
|
|
2158
|
+
onAbort: props.onAbort
|
|
2159
|
+
}) : null,
|
|
2160
|
+
!summary ? /* @__PURE__ */ jsx(Text, {
|
|
2161
|
+
style: styles$1.dim,
|
|
2162
|
+
children: "Loading changes…"
|
|
2163
|
+
}) : summary.files.length ? /* @__PURE__ */ jsx(FileList, {
|
|
2164
|
+
files: summary.files,
|
|
2165
|
+
comments,
|
|
2166
|
+
onFile: props.onFile
|
|
2167
|
+
}) : /* @__PURE__ */ jsxs(View, {
|
|
2168
|
+
style: styles$1.empty,
|
|
2169
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
2170
|
+
style: styles$1.sheetTitle,
|
|
2171
|
+
children: summary.repository ? "No changes yet" : "Not a Git checkout"
|
|
2172
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
2173
|
+
style: styles$1.dim,
|
|
2174
|
+
children: summary.repository ? `Nothing to review on ${summary.branch ?? "this checkout"}.` : "This folder is not a Git repository, so there is nothing to sync."
|
|
2175
|
+
})]
|
|
2176
|
+
}),
|
|
2177
|
+
summary?.truncated ? /* @__PURE__ */ jsx(Text, {
|
|
2178
|
+
style: styles$1.dim,
|
|
2179
|
+
children: "The file list was truncated."
|
|
2180
|
+
}) : null
|
|
2181
|
+
]
|
|
2182
|
+
}) : screen === "file" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2183
|
+
entry ? /* @__PURE__ */ jsx(FileHeader, {
|
|
2184
|
+
entry,
|
|
2185
|
+
index,
|
|
2186
|
+
total: summary?.files.length ?? 0,
|
|
2187
|
+
onChanges: () => props.onScreen("changes"),
|
|
2188
|
+
onPrevious: () => {
|
|
2189
|
+
const previous = summary?.files[index - 1];
|
|
2190
|
+
if (previous) props.onFile(previous.path);
|
|
2191
|
+
},
|
|
2192
|
+
onNext: () => {
|
|
2193
|
+
const next = summary?.files[index + 1];
|
|
2194
|
+
if (next) props.onFile(next.path);
|
|
2195
|
+
}
|
|
2196
|
+
}) : /* @__PURE__ */ jsx(Text, {
|
|
2197
|
+
style: styles$1.heading,
|
|
2198
|
+
children: path
|
|
2199
|
+
}),
|
|
2200
|
+
!file ? /* @__PURE__ */ jsxs(View, {
|
|
2201
|
+
style: styles$1.content,
|
|
2202
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
2203
|
+
style: styles$1.dim,
|
|
2204
|
+
children: props.error ? "The diff could not be loaded. Please try again." : "Loading diff…"
|
|
2205
|
+
}), props.error && path ? /* @__PURE__ */ jsx(Action, {
|
|
2206
|
+
onPress: () => props.onFile(path),
|
|
2207
|
+
children: "Retry"
|
|
2208
|
+
}) : null]
|
|
2209
|
+
}) : file.binary || file.tooLarge ? /* @__PURE__ */ jsx(View, {
|
|
2210
|
+
style: styles$1.content,
|
|
2211
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
2212
|
+
style: styles$1.section,
|
|
2213
|
+
children: file.binary ? "Binary file" : "Diff too large to display"
|
|
2214
|
+
})
|
|
2215
|
+
}) : /* @__PURE__ */ jsx(DiffRows, {
|
|
2216
|
+
rows: visibleRows,
|
|
2217
|
+
comments: fileComments,
|
|
2218
|
+
selection: props.selection,
|
|
2219
|
+
onLine: props.onLine,
|
|
2220
|
+
gutterDigits
|
|
2221
|
+
}),
|
|
2222
|
+
fileComments.filter((comment) => comment.startLine === void 0).map((comment) => /* @__PURE__ */ jsxs(View, {
|
|
2223
|
+
style: styles$1.inlineComment,
|
|
2224
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
2225
|
+
style: styles$1.blue,
|
|
2226
|
+
children: "Queued file comment"
|
|
2227
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
2228
|
+
style: styles$1.body,
|
|
2229
|
+
children: comment.body
|
|
2230
|
+
})]
|
|
2231
|
+
}, comment.id)),
|
|
2232
|
+
/* @__PURE__ */ jsx(View, {
|
|
2233
|
+
style: styles$1.content,
|
|
2234
|
+
children: /* @__PURE__ */ jsx(Action, {
|
|
2235
|
+
onPress: props.onComment,
|
|
2236
|
+
children: "Comment on file"
|
|
2237
|
+
})
|
|
2238
|
+
})
|
|
2239
|
+
] }) : /* @__PURE__ */ jsx(CommentCards, {
|
|
2240
|
+
comments,
|
|
2241
|
+
onRemove: props.onRemove
|
|
2242
|
+
}), props.error ? /* @__PURE__ */ jsx(Text, {
|
|
2243
|
+
style: [styles$1.red, styles$1.content],
|
|
2244
|
+
children: props.error
|
|
2245
|
+
}) : null]
|
|
2246
|
+
}, `${screen}:${path ?? ""}`),
|
|
2247
|
+
/* @__PURE__ */ jsx(Footer, {
|
|
2248
|
+
count: comments.length,
|
|
2249
|
+
confirmation: screen === "changes" && activity.changes?.rebase !== void 0,
|
|
2250
|
+
discard: screen === "review",
|
|
2251
|
+
onReview: () => props.onScreen("review"),
|
|
2252
|
+
onSend: props.onSend,
|
|
2253
|
+
onDiscard: props.onDiscard
|
|
2254
|
+
}),
|
|
2255
|
+
screen === "file" && file && !file.binary && !file.tooLarge ? /* @__PURE__ */ jsxs(View, {
|
|
2256
|
+
style: styles$1.disclosure,
|
|
2257
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
2258
|
+
style: styles$1.dim,
|
|
2259
|
+
accessibilityLiveRegion: "polite",
|
|
2260
|
+
children: [
|
|
2261
|
+
Math.min(current.limit, rows.length),
|
|
2262
|
+
" of ",
|
|
2263
|
+
rows.length,
|
|
2264
|
+
" diff rows shown",
|
|
2265
|
+
props.selection ? ` · ${props.selection.side} line ${props.selection.startLine} selected` : ""
|
|
2266
|
+
]
|
|
2267
|
+
}), current.limit < rows.length ? /* @__PURE__ */ jsxs(Button, {
|
|
2268
|
+
variant: "outline",
|
|
2269
|
+
onPress: () => setDisclosure({
|
|
2270
|
+
...current,
|
|
2271
|
+
limit: Math.min(rows.length, current.limit + DIFF_ROW_STEP)
|
|
2272
|
+
}),
|
|
2273
|
+
children: [
|
|
2274
|
+
"Show ",
|
|
2275
|
+
Math.min(DIFF_ROW_STEP, rows.length - current.limit),
|
|
2276
|
+
" more rows"
|
|
2277
|
+
]
|
|
2278
|
+
}) : /* @__PURE__ */ jsx(Text, {
|
|
2279
|
+
style: styles$1.dim,
|
|
2280
|
+
children: "All diff rows shown"
|
|
2281
|
+
})]
|
|
2282
|
+
}) : null
|
|
2283
|
+
]
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
//#endregion
|
|
2287
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/GitReviewPanel.native.tsx
|
|
2288
|
+
function useGitChanges(sessionId) {
|
|
2289
|
+
const subscribe = useCallback((listener) => {
|
|
2290
|
+
const sub = gitChanges.store.subscribe(listener);
|
|
2291
|
+
return () => sub.unsubscribe();
|
|
2292
|
+
}, []);
|
|
2293
|
+
return useSyncExternalStore(subscribe, () => gitChanges.select(gitChanges.store.state, sessionId));
|
|
2294
|
+
}
|
|
2295
|
+
function reviewTab() {
|
|
2296
|
+
return {
|
|
2297
|
+
id: "git-review",
|
|
2298
|
+
label: "review",
|
|
2299
|
+
panel: GitReviewPanel
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
function GitReviewPanel(props) {
|
|
2303
|
+
return /* @__PURE__ */ jsx(GitReviewPanelSession, { ...props }, props.sessionId);
|
|
2304
|
+
}
|
|
2305
|
+
function GitReviewPanelSession({ sessionId, sendSessionFrame }) {
|
|
2306
|
+
const activity = useGitChanges(sessionId);
|
|
2307
|
+
const subscribe = useCallback((listener) => {
|
|
2308
|
+
const sub = gitReview.store.subscribe(listener);
|
|
2309
|
+
return () => sub.unsubscribe();
|
|
2310
|
+
}, []);
|
|
2311
|
+
const comments = useSyncExternalStore(subscribe, () => gitReview.select(gitReview.store.state, sessionId).comments);
|
|
2312
|
+
const [summary, setSummary] = useState();
|
|
2313
|
+
const [file, setFile] = useState();
|
|
2314
|
+
const [error, setError] = useState();
|
|
2315
|
+
const [dismissedError, setDismissedError] = useState();
|
|
2316
|
+
const [confirm, setConfirm] = useState();
|
|
2317
|
+
const [screen, setScreen] = useState("changes");
|
|
2318
|
+
const [path, setPath] = useState();
|
|
2319
|
+
const [selection, setSelection] = useState();
|
|
2320
|
+
const [draft, setDraft] = useState();
|
|
2321
|
+
const request = useRef(void 0);
|
|
2322
|
+
const fingerprint = JSON.stringify(activity.changes);
|
|
2323
|
+
useEffect(() => {
|
|
2324
|
+
if (sessionId === null) return void 0;
|
|
2325
|
+
const controller = new AbortController();
|
|
2326
|
+
fetchReviewSummary(sessionId, controller.signal).then((result) => {
|
|
2327
|
+
if (!controller.signal.aborted) {
|
|
2328
|
+
if (result.ok) setSummary(result.summary);
|
|
2329
|
+
else setError(result.error);
|
|
2330
|
+
}
|
|
2331
|
+
}).catch((cause) => {
|
|
2332
|
+
if (!controller.signal.aborted) setError(cause instanceof Error ? cause.message : String(cause));
|
|
2333
|
+
});
|
|
2334
|
+
return () => {
|
|
2335
|
+
controller.abort();
|
|
2336
|
+
request.current?.abort();
|
|
2337
|
+
};
|
|
2338
|
+
}, [sessionId, fingerprint]);
|
|
2339
|
+
const loadFile = async (path) => {
|
|
2340
|
+
if (sessionId === null) return;
|
|
2341
|
+
setScreen("file");
|
|
2342
|
+
setPath(path);
|
|
2343
|
+
setSelection(void 0);
|
|
2344
|
+
setDraft(void 0);
|
|
2345
|
+
request.current?.abort();
|
|
2346
|
+
const controller = new AbortController();
|
|
2347
|
+
request.current = controller;
|
|
2348
|
+
setFile(void 0);
|
|
2349
|
+
setError(void 0);
|
|
2350
|
+
try {
|
|
2351
|
+
const result = await fetchReviewFile(sessionId, path, controller.signal);
|
|
2352
|
+
if (!controller.signal.aborted) {
|
|
2353
|
+
if (result.ok) setFile(result.diff);
|
|
2354
|
+
else setError(result.error);
|
|
2355
|
+
}
|
|
2356
|
+
} catch (cause) {
|
|
2357
|
+
if (!controller.signal.aborted) setError(cause instanceof Error ? cause.message : String(cause));
|
|
2358
|
+
}
|
|
2359
|
+
};
|
|
2360
|
+
if (sessionId === null) return null;
|
|
2361
|
+
const changes = activity.changes;
|
|
2362
|
+
const send = (message, onSent) => {
|
|
2363
|
+
const failure = deliverToAgent(sendSessionFrame, sessionId, message);
|
|
2364
|
+
setError(failure);
|
|
2365
|
+
if (failure === void 0) onSent?.();
|
|
2366
|
+
};
|
|
2367
|
+
const sync = (action) => requestGitSync(sessionId, action);
|
|
2368
|
+
const forceRequired = activity.error !== dismissedError && activity.errorTarget?.action === "push" && activity.errorTarget.forceRequired === true;
|
|
2369
|
+
const sendReview = () => {
|
|
2370
|
+
if (comments.length === 0) return;
|
|
2371
|
+
const body = comments.map((item) => {
|
|
2372
|
+
return `### ${item.startLine === void 0 ? item.relPath : item.side === "old" ? `${item.relPath} (removed lines ${item.startLine}-${item.endLine ?? item.startLine})` : `${item.relPath}:${item.startLine}-${item.endLine ?? item.startLine}`}\n\n${item.snippet.split("\n").slice(0, 20).join("\n").slice(0, 2e3).split("\n").map((line) => `> ${line}`).join("\n")}\n\n${item.body.trim()}`;
|
|
2373
|
+
}).join("\n\n");
|
|
2374
|
+
send(`${reviewHeading(comments.length, summary?.branch, summary?.base)}\n\n${body}`, () => clearReviewComments(sessionId));
|
|
2375
|
+
};
|
|
2376
|
+
const selectLine = (row) => {
|
|
2377
|
+
if (!file) return;
|
|
2378
|
+
const next = selectReviewLine(selection, row);
|
|
2379
|
+
setSelection(next.range);
|
|
2380
|
+
if (next.complete) {
|
|
2381
|
+
const rows = reviewRangeRows(file.hunks.flatMap((hunk) => hunk.rows), next.range);
|
|
2382
|
+
setDraft({
|
|
2383
|
+
path: file.path,
|
|
2384
|
+
range: next.range,
|
|
2385
|
+
snippet: rows.map((item) => item.content).join("\n")
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
};
|
|
2389
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2390
|
+
/* @__PURE__ */ jsx(GitReviewView, {
|
|
2391
|
+
screen,
|
|
2392
|
+
activity: {
|
|
2393
|
+
...activity,
|
|
2394
|
+
error: activity.error === dismissedError ? void 0 : activity.error
|
|
2395
|
+
},
|
|
2396
|
+
summary,
|
|
2397
|
+
file,
|
|
2398
|
+
path,
|
|
2399
|
+
comments,
|
|
2400
|
+
error,
|
|
2401
|
+
forceRequired,
|
|
2402
|
+
selection,
|
|
2403
|
+
onScreen: setScreen,
|
|
2404
|
+
onFile: (path) => void loadFile(path),
|
|
2405
|
+
onLine: selectLine,
|
|
2406
|
+
onSync: sync,
|
|
2407
|
+
onForce: () => setConfirm("force"),
|
|
2408
|
+
onAbort: () => setConfirm("abort"),
|
|
2409
|
+
onResolve: () => send(conflictPrompt(changes?.branch, changes?.rebase?.conflicts ?? [])),
|
|
2410
|
+
onDismiss: () => setDismissedError(activity.error),
|
|
2411
|
+
onComment: () => {
|
|
2412
|
+
if (path) {
|
|
2413
|
+
setSelection(void 0);
|
|
2414
|
+
setDraft({
|
|
2415
|
+
path,
|
|
2416
|
+
snippet: ""
|
|
2417
|
+
});
|
|
2418
|
+
}
|
|
2419
|
+
},
|
|
2420
|
+
onRemove: (id) => removeReviewComment(sessionId, id),
|
|
2421
|
+
onSend: sendReview,
|
|
2422
|
+
onDiscard: () => setConfirm("discard")
|
|
2423
|
+
}),
|
|
2424
|
+
/* @__PURE__ */ jsx(Sheet, {
|
|
2425
|
+
open: draft !== void 0,
|
|
2426
|
+
onOpenChange: (open) => {
|
|
2427
|
+
if (!open) {
|
|
2428
|
+
setDraft(void 0);
|
|
2429
|
+
setSelection(void 0);
|
|
2430
|
+
}
|
|
2431
|
+
},
|
|
2432
|
+
children: /* @__PURE__ */ jsx(SheetContent, { children: /* @__PURE__ */ jsx(SheetBody, { children: /* @__PURE__ */ jsx(CommentEditor, {
|
|
2433
|
+
range: draft?.range,
|
|
2434
|
+
snippet: draft?.snippet ?? "",
|
|
2435
|
+
onCancel: () => {
|
|
2436
|
+
setDraft(void 0);
|
|
2437
|
+
setSelection(void 0);
|
|
2438
|
+
},
|
|
2439
|
+
onAdd: (comment) => {
|
|
2440
|
+
if (!draft || !comment.trim()) return;
|
|
2441
|
+
addReviewComment(sessionId, {
|
|
2442
|
+
path: draft.path,
|
|
2443
|
+
relPath: draft.path,
|
|
2444
|
+
body: comment.trim(),
|
|
2445
|
+
snippet: draft.snippet,
|
|
2446
|
+
...draft.range
|
|
2447
|
+
});
|
|
2448
|
+
setDraft(void 0);
|
|
2449
|
+
setSelection(void 0);
|
|
2450
|
+
}
|
|
2451
|
+
}, draft ? "open" : "closed") }) })
|
|
2452
|
+
}),
|
|
2453
|
+
/* @__PURE__ */ jsx(Sheet, {
|
|
2454
|
+
open: confirm !== void 0,
|
|
2455
|
+
onOpenChange: (open) => {
|
|
2456
|
+
if (!open) setConfirm(void 0);
|
|
2457
|
+
},
|
|
2458
|
+
children: /* @__PURE__ */ jsxs(SheetContent, { children: [/* @__PURE__ */ jsx(SheetHeader, { children: /* @__PURE__ */ jsx(SheetTitle, { children: confirm === "force" ? "Force push with lease?" : confirm === "abort" ? "Abort rebase?" : "Discard review?" }) }), /* @__PURE__ */ jsxs(SheetBody, { children: [
|
|
2459
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2460
|
+
style: styles$1.dim,
|
|
2461
|
+
children: confirm === "force" ? "Git refuses this if the remote holds commits you never fetched and integrated." : confirm === "abort" ? "Abort discards any conflict resolution in progress." : "Your queued comments will be removed."
|
|
2462
|
+
}),
|
|
2463
|
+
/* @__PURE__ */ jsx(Button, {
|
|
2464
|
+
variant: "danger",
|
|
2465
|
+
disabled: confirm !== "discard" && activity.pending !== void 0,
|
|
2466
|
+
onPress: () => {
|
|
2467
|
+
if (confirm === "force") requestGitSync(sessionId, "push", true);
|
|
2468
|
+
else if (confirm === "abort") sync("abort-rebase");
|
|
2469
|
+
else clearReviewComments(sessionId);
|
|
2470
|
+
setConfirm(void 0);
|
|
2471
|
+
},
|
|
2472
|
+
children: confirm === "force" ? "Force push with lease" : confirm === "abort" ? "Abort rebase" : "Discard"
|
|
2473
|
+
}),
|
|
2474
|
+
/* @__PURE__ */ jsx(Button, {
|
|
2475
|
+
variant: "ghost",
|
|
2476
|
+
onPress: () => setConfirm(void 0),
|
|
2477
|
+
children: "Cancel"
|
|
2478
|
+
})
|
|
2479
|
+
] })] })
|
|
2480
|
+
})
|
|
2481
|
+
] });
|
|
2482
|
+
}
|
|
2483
|
+
function CommentEditor({ onAdd, ...props }) {
|
|
2484
|
+
const [body, setBody] = useState("");
|
|
2485
|
+
return /* @__PURE__ */ jsx(CommentSheet, {
|
|
2486
|
+
...props,
|
|
2487
|
+
value: body,
|
|
2488
|
+
onChange: setBody,
|
|
2489
|
+
onAdd: () => onAdd(body)
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
2492
|
+
//#endregion
|
|
2493
|
+
//#region src/extensions/workspaces/sessions/(frontend)/fill/_components/WorktreesActivitySection.native.tsx
|
|
2494
|
+
function GitChangesRow({ state = "changes", branch = "Detached HEAD", added = 0, removed = 0, onReview }) {
|
|
2495
|
+
return /* @__PURE__ */ jsxs(Pressable, {
|
|
2496
|
+
testID: "activity-diff-changes",
|
|
2497
|
+
accessibilityRole: "button",
|
|
2498
|
+
accessibilityLabel: "Open changes review",
|
|
2499
|
+
onPress: onReview,
|
|
2500
|
+
style: styles.changeRow,
|
|
2501
|
+
children: [/* @__PURE__ */ jsxs(View, {
|
|
2502
|
+
style: styles.line,
|
|
2503
|
+
children: [
|
|
2504
|
+
/* @__PURE__ */ jsx(FileIcon, {
|
|
2505
|
+
size: 18,
|
|
2506
|
+
color: "dim"
|
|
2507
|
+
}),
|
|
2508
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2509
|
+
style: [styles.heading, styles.grow],
|
|
2510
|
+
children: "Changes"
|
|
2511
|
+
}),
|
|
2512
|
+
/* @__PURE__ */ jsx(ChevronRightIcon, {
|
|
2513
|
+
size: 16,
|
|
2514
|
+
color: "dim"
|
|
2515
|
+
})
|
|
2516
|
+
]
|
|
2517
|
+
}), /* @__PURE__ */ jsxs(View, {
|
|
2518
|
+
style: styles.changeDetail,
|
|
2519
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
2520
|
+
style: [styles.branch, styles.grow],
|
|
2521
|
+
children: branch
|
|
2522
|
+
}), state === "changes" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Text, {
|
|
2523
|
+
style: styles.jade,
|
|
2524
|
+
children: ["+", added]
|
|
2525
|
+
}), /* @__PURE__ */ jsxs(Text, {
|
|
2526
|
+
style: styles.error,
|
|
2527
|
+
children: ["-", removed]
|
|
2528
|
+
})] }) : state === "pushing" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Spinner, { label: "Pushing..." }), /* @__PURE__ */ jsx(Text, {
|
|
2529
|
+
style: styles.dim,
|
|
2530
|
+
children: "Pushing..."
|
|
2531
|
+
})] }) : /* @__PURE__ */ jsx(Text, {
|
|
2532
|
+
style: styles.dim,
|
|
2533
|
+
children: "No changes yet"
|
|
2534
|
+
})]
|
|
2535
|
+
})]
|
|
2536
|
+
});
|
|
2537
|
+
}
|
|
2538
|
+
function WorktreesActivityView({ git, state, disabled = false, onReview, onWorktrees }) {
|
|
2539
|
+
const { starting } = worktreesPresentation(state);
|
|
2540
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
2541
|
+
style: styles.section,
|
|
2542
|
+
children: [
|
|
2543
|
+
git.changes ? /* @__PURE__ */ jsx(GitChangesRow, {
|
|
2544
|
+
branch: git.changes.branch ?? "Detached HEAD",
|
|
2545
|
+
added: git.changes.added,
|
|
2546
|
+
removed: git.changes.removed,
|
|
2547
|
+
state: git.pending?.startsWith("pushing") ? "pushing" : git.changes.added === 0 && git.changes.removed === 0 && git.changes.files === 0 ? "clean" : "changes",
|
|
2548
|
+
onReview
|
|
2549
|
+
}) : null,
|
|
2550
|
+
/* @__PURE__ */ jsx(WorktreeGroup, {
|
|
2551
|
+
title: "Worktrees",
|
|
2552
|
+
count: `${state.worktrees.length} open${starting ? " · 1 starting" : ""}`
|
|
2553
|
+
}),
|
|
2554
|
+
state.worktrees.map((item) => /* @__PURE__ */ jsx(WorktreeRow, {
|
|
2555
|
+
item,
|
|
2556
|
+
compact: true,
|
|
2557
|
+
onOpen: onWorktrees
|
|
2558
|
+
}, item.id)),
|
|
2559
|
+
starting ? /* @__PURE__ */ jsx(WorktreeStarting, { branch: state.requestedBranch }) : state.pending ? /* @__PURE__ */ jsx(Text, {
|
|
2560
|
+
style: styles.dim,
|
|
2561
|
+
children: state.pending
|
|
2562
|
+
}) : null,
|
|
2563
|
+
state.worktrees.length === 0 && !state.pending ? /* @__PURE__ */ jsxs(View, {
|
|
2564
|
+
style: styles.idle,
|
|
2565
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
2566
|
+
style: styles.dim,
|
|
2567
|
+
children: "Idle"
|
|
2568
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
2569
|
+
testID: "activity-git-create",
|
|
2570
|
+
variant: "ghost",
|
|
2571
|
+
style: styles.link,
|
|
2572
|
+
disabled,
|
|
2573
|
+
onPress: onWorktrees,
|
|
2574
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
2575
|
+
style: styles.jade,
|
|
2576
|
+
children: "Create a worktree →"
|
|
2577
|
+
})
|
|
2578
|
+
})]
|
|
2579
|
+
}) : null
|
|
2580
|
+
]
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
function WorktreesActivitySection({ sessionId, openTransientTab }) {
|
|
2584
|
+
const git = useGitChanges(sessionId);
|
|
2585
|
+
const state = useWorktrees(sessionId);
|
|
2586
|
+
return /* @__PURE__ */ jsx(WorktreesActivityView, {
|
|
2587
|
+
git,
|
|
2588
|
+
state: {
|
|
2589
|
+
...state,
|
|
2590
|
+
worktrees: state.worktrees.filter((item) => item.sessionId !== sessionId)
|
|
2591
|
+
},
|
|
2592
|
+
disabled: sessionId === null,
|
|
2593
|
+
onReview: () => openTransientTab(reviewTab()),
|
|
2594
|
+
onWorktrees: () => openTransientTab(worktreesTab())
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
const styles = StyleSheet.create((theme) => ({
|
|
2598
|
+
section: {
|
|
2599
|
+
gap: theme.spacing(2),
|
|
2600
|
+
uni__dependencies: [0]
|
|
2601
|
+
},
|
|
2602
|
+
heading: {
|
|
2603
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
2604
|
+
fontSize: theme.fontSize.lg,
|
|
2605
|
+
lineHeight: theme.lineHeight.lg,
|
|
2606
|
+
fontWeight: "600",
|
|
2607
|
+
color: theme.colors.hi,
|
|
2608
|
+
uni__dependencies: [0]
|
|
2609
|
+
},
|
|
2610
|
+
dim: {
|
|
2611
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
2612
|
+
fontSize: theme.fontSize.sm,
|
|
2613
|
+
lineHeight: theme.lineHeight.sm,
|
|
2614
|
+
color: theme.colors.dim,
|
|
2615
|
+
uni__dependencies: [0]
|
|
2616
|
+
},
|
|
2617
|
+
branch: {
|
|
2618
|
+
fontFamily: fontOf(theme.fontFamily.mono),
|
|
2619
|
+
fontSize: theme.fontSize.base,
|
|
2620
|
+
lineHeight: theme.lineHeight.lg,
|
|
2621
|
+
color: theme.colors.hi,
|
|
2622
|
+
uni__dependencies: [0]
|
|
2623
|
+
},
|
|
2624
|
+
line: {
|
|
2625
|
+
flexDirection: "row",
|
|
2626
|
+
alignItems: "center",
|
|
2627
|
+
gap: theme.spacing(2),
|
|
2628
|
+
minHeight: 44,
|
|
2629
|
+
uni__dependencies: [0]
|
|
2630
|
+
},
|
|
2631
|
+
grow: {
|
|
2632
|
+
flex: 1,
|
|
2633
|
+
minWidth: 0,
|
|
2634
|
+
gap: theme.spacing(1),
|
|
2635
|
+
uni__dependencies: [0]
|
|
2636
|
+
},
|
|
2637
|
+
jade: {
|
|
2638
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
2639
|
+
fontSize: theme.fontSize.base,
|
|
2640
|
+
lineHeight: theme.lineHeight.base,
|
|
2641
|
+
color: theme.colors.green,
|
|
2642
|
+
fontWeight: "500",
|
|
2643
|
+
uni__dependencies: [0]
|
|
2644
|
+
},
|
|
2645
|
+
error: {
|
|
2646
|
+
fontFamily: fontOf(theme.fontFamily.sans),
|
|
2647
|
+
fontSize: theme.fontSize.sm,
|
|
2648
|
+
lineHeight: theme.lineHeight.sm,
|
|
2649
|
+
color: theme.colors.red,
|
|
2650
|
+
uni__dependencies: [0]
|
|
2651
|
+
},
|
|
2652
|
+
changeRow: {
|
|
2653
|
+
gap: theme.spacing(1),
|
|
2654
|
+
paddingVertical: theme.spacing(2),
|
|
2655
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
2656
|
+
borderColor: theme.colors["border-soft"],
|
|
2657
|
+
uni__dependencies: [0]
|
|
2658
|
+
},
|
|
2659
|
+
changeDetail: {
|
|
2660
|
+
flexDirection: "row",
|
|
2661
|
+
alignItems: "center",
|
|
2662
|
+
gap: theme.spacing(3),
|
|
2663
|
+
paddingLeft: theme.spacing(6),
|
|
2664
|
+
minHeight: 28,
|
|
2665
|
+
uni__dependencies: [0]
|
|
2666
|
+
},
|
|
2667
|
+
idle: {
|
|
2668
|
+
flexDirection: "row",
|
|
2669
|
+
alignItems: "center",
|
|
2670
|
+
gap: theme.spacing(4),
|
|
2671
|
+
uni__dependencies: [0]
|
|
2672
|
+
},
|
|
2673
|
+
link: {
|
|
2674
|
+
minHeight: 44,
|
|
2675
|
+
paddingHorizontal: 0
|
|
2676
|
+
}
|
|
2677
|
+
}));
|
|
2678
|
+
//#endregion
|
|
2679
|
+
//#region src/extensions/workspaces/sessions/(frontend)/fill/Worktrees.activity.git.native.tsx
|
|
2680
|
+
var Worktrees_activity_git_native_default = defineFill({ component: WorktreesActivitySection });
|
|
2681
|
+
//#endregion
|
|
2682
|
+
//#region src/extensions/workspaces/sessions/(frontend)/lifecycle/start.native.ts
|
|
2683
|
+
var start_native_default = defineWebLifecycle((runtime) => {
|
|
2684
|
+
const stopWorktrees = startWorktreeRuntime(runtime);
|
|
2685
|
+
const stopChanges = startGitChangesRuntime(runtime);
|
|
2686
|
+
return () => {
|
|
2687
|
+
stopChanges();
|
|
2688
|
+
stopWorktrees();
|
|
2689
|
+
};
|
|
2690
|
+
});
|
|
2691
|
+
//#endregion
|
|
2692
|
+
//#region generated/native.ts
|
|
2693
|
+
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
2694
|
+
...identity,
|
|
2695
|
+
...value
|
|
2696
|
+
};
|
|
2697
|
+
const nativePlugin = defineNativePlugin({
|
|
2698
|
+
id: "git",
|
|
2699
|
+
workspace: { get fills() {
|
|
2700
|
+
return [via({
|
|
2701
|
+
slot: "new-session",
|
|
2702
|
+
id: "git-new-session"
|
|
2703
|
+
}, GitNewSession_new_session_native_default)];
|
|
2704
|
+
} },
|
|
2705
|
+
session: {
|
|
2706
|
+
get activityGroups() {
|
|
2707
|
+
return [via({ name: "git" }, git_native_default)];
|
|
2708
|
+
},
|
|
2709
|
+
get channels() {
|
|
2710
|
+
return [via({ channel: "git_changes" }, git_changes_native_default), via({ channel: "git_worktrees" }, git_worktrees_native_default)];
|
|
2711
|
+
},
|
|
2712
|
+
get fills() {
|
|
2713
|
+
return [via({
|
|
2714
|
+
slot: "activity.git",
|
|
2715
|
+
id: "worktrees"
|
|
2716
|
+
}, Worktrees_activity_git_native_default)];
|
|
2717
|
+
},
|
|
2718
|
+
get start() {
|
|
2719
|
+
return start_native_default;
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
});
|
|
2723
|
+
//#endregion
|
|
2724
|
+
export { nativePlugin };
|
|
2725
|
+
|
|
2726
|
+
//# sourceMappingURL=native.mjs.map
|