@agimon-ai/po-git 0.0.0-alpha.7 → 0.0.0-alpha.70

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