@agimon-ai/po-git 0.0.0-alpha.6 → 0.0.0-alpha.61

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