@agimon-ai/po-git 0.0.0-alpha.12 → 0.0.0-alpha.121

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