@agimon-ai/po-git 0.0.0-alpha.4 → 0.0.0-alpha.40

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