@agimon-ai/po-git 0.0.0-alpha.8 → 0.0.0-alpha.80

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