@nkzw/codiff-core 1.8.1 → 1.9.1

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.
package/dist/react.mjs CHANGED
@@ -7,11 +7,14 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
7
  import { MarkdownEditor } from "@nkzw/mdx-editor";
8
8
  import { frontmatterPlugin, imagePlugin } from "@nkzw/mdx-editor/core";
9
9
  import { DownloadSimpleIcon } from "@phosphor-icons/react/DownloadSimple";
10
- import { Copy, ExternalLink, X } from "lucide-react";
10
+ import { Copy, Trash2, X } from "lucide-react";
11
+ import { ArrowSquareOutIcon } from "@phosphor-icons/react/ArrowSquareOut";
12
+ import { ChatCircleIcon } from "@phosphor-icons/react/ChatCircle";
13
+ import { PathIcon } from "@phosphor-icons/react/Path";
14
+ import { TreeStructureIcon } from "@phosphor-icons/react/TreeStructure";
11
15
  import { FileTree, useFileTree } from "@pierre/trees/react";
12
16
  import { hydratePartialDiff, parseDiffFromFile, parsePatchFiles, registerCustomTheme, setCustomExtension } from "@pierre/diffs";
13
17
  import useRelativeTime from "@nkzw/use-relative-time";
14
- import { ChatCircleIcon } from "@phosphor-icons/react/ChatCircle";
15
18
  import { CaretDownIcon } from "@phosphor-icons/react/CaretDown";
16
19
  import "@phosphor-icons/react/CaretUp";
17
20
  import { CheckIcon } from "@phosphor-icons/react/Check";
@@ -25,19 +28,19 @@ import { ImageBrokenIcon } from "@phosphor-icons/react/ImageBroken";
25
28
  import { SquareSplitVerticalIcon } from "@phosphor-icons/react/SquareSplitVertical";
26
29
  import { CodeView, WorkerPoolContextProvider } from "@pierre/diffs/react";
27
30
  import { PersistentMarkdownEditor } from "@nkzw/mdx-editor/persistence";
28
- import { ArrowsClockwiseIcon } from "@phosphor-icons/react/ArrowsClockwise";
29
31
  import { GitBranchIcon } from "@phosphor-icons/react/GitBranch";
30
- import { PathIcon } from "@phosphor-icons/react/Path";
31
32
  import { ShareNetworkIcon } from "@phosphor-icons/react/ShareNetwork";
32
33
  import { BugIcon } from "@phosphor-icons/react/Bug";
33
34
  import { FlaskIcon } from "@phosphor-icons/react/Flask";
34
35
  import { GearIcon } from "@phosphor-icons/react/Gear";
35
36
  import { ReadCvLogoIcon } from "@phosphor-icons/react/ReadCvLogo";
36
37
  import { WrenchIcon } from "@phosphor-icons/react/Wrench";
38
+ import { SidebarSimpleIcon } from "@phosphor-icons/react/SidebarSimple";
37
39
  import { ArrowLeftIcon } from "@phosphor-icons/react/ArrowLeft";
38
40
  import { ArrowRightIcon } from "@phosphor-icons/react/ArrowRight";
39
41
  import { CaretLeftIcon } from "@phosphor-icons/react/CaretLeft";
40
42
  import { CaretRightIcon } from "@phosphor-icons/react/CaretRight";
43
+ import { ArrowsClockwiseIcon } from "@phosphor-icons/react/ArrowsClockwise";
41
44
  import { Terminal, init } from "ghostty-web";
42
45
  //#region app/components/Button.tsx
43
46
  const buttonVariants = cva("codiff-button", {
@@ -92,6 +95,15 @@ function Button({ action, asChild = false, children, className, disabled, onClic
92
95
  });
93
96
  }
94
97
  //#endregion
98
+ //#region app/components/Thinking.tsx
99
+ function Thinking() {
100
+ return /* @__PURE__ */ jsx("div", {
101
+ className: "review-source-loading loading pulse italic",
102
+ role: "status",
103
+ children: "Thinking…"
104
+ });
105
+ }
106
+ //#endregion
95
107
  //#region app/components/Avatar.tsx
96
108
  const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
97
109
  const getNameParts = (name) => {
@@ -951,7 +963,7 @@ function SharedPlanCommentRail({ activeThreadId, commenting, layoutPass, layouts
951
963
  })
952
964
  });
953
965
  }
954
- function PlanReviewSurface({ commenting, signInLabel = "Sign in to comment", snapshot }) {
966
+ function PlanReviewSurface({ commenting, onDeleteShare, signInLabel = "Sign in to comment", snapshot }) {
955
967
  const editorRef = useRef(null);
956
968
  const [activeThreadId, setActiveThreadId] = useState(null);
957
969
  const [commentTarget, setCommentTarget] = useState(null);
@@ -1046,11 +1058,19 @@ function PlanReviewSurface({ commenting, signInLabel = "Sign in to comment", sna
1046
1058
  link.remove();
1047
1059
  URL.revokeObjectURL(url);
1048
1060
  }, [snapshot]);
1061
+ const deleteShare = useCallback(async () => {
1062
+ if (!onDeleteShare || !window.confirm("Delete this shared plan? This cannot be undone.")) return;
1063
+ try {
1064
+ await onDeleteShare();
1065
+ } catch (error) {
1066
+ window.alert(error instanceof Error ? error.message : String(error));
1067
+ }
1068
+ }, [onDeleteShare]);
1049
1069
  return /* @__PURE__ */ jsxs("main", {
1050
1070
  className: "plan-shell shared-plan-shell",
1051
1071
  "data-theme": snapshot.preferences.theme === "system" ? void 0 : snapshot.preferences.theme,
1052
1072
  children: [/* @__PURE__ */ jsx("header", {
1053
- className: "plan-header",
1073
+ className: "plan-header workspace-top-bar",
1054
1074
  children: /* @__PURE__ */ jsx("div", {
1055
1075
  className: "plan-title",
1056
1076
  title: snapshot.document.title,
@@ -1085,18 +1105,33 @@ function PlanReviewSurface({ commenting, signInLabel = "Sign in to comment", sna
1085
1105
  })
1086
1106
  })
1087
1107
  })
1088
- }), /* @__PURE__ */ jsx(Button, {
1089
- "aria-label": "Download plan",
1090
- className: "plan-download-button",
1091
- onClick: downloadPlan,
1092
- size: "icon",
1093
- title: "Download plan",
1094
- type: "button",
1095
- children: /* @__PURE__ */ jsx(DownloadSimpleIcon, {
1096
- "aria-hidden": true,
1097
- size: 16,
1098
- weight: "bold"
1099
- })
1108
+ }), /* @__PURE__ */ jsxs("div", {
1109
+ className: "plan-file-actions",
1110
+ children: [onDeleteShare ? /* @__PURE__ */ jsx(Button, {
1111
+ action: deleteShare,
1112
+ "aria-label": "Delete shared plan",
1113
+ pendingPlaceholder: "…",
1114
+ size: "icon",
1115
+ title: "Delete shared plan",
1116
+ type: "button",
1117
+ variant: "destructive",
1118
+ children: /* @__PURE__ */ jsx(Trash2, {
1119
+ "aria-hidden": true,
1120
+ size: 16
1121
+ })
1122
+ }) : null, /* @__PURE__ */ jsx(Button, {
1123
+ "aria-label": "Download plan",
1124
+ className: "plan-download-button",
1125
+ onClick: downloadPlan,
1126
+ size: "icon",
1127
+ title: "Download plan",
1128
+ type: "button",
1129
+ children: /* @__PURE__ */ jsx(DownloadSimpleIcon, {
1130
+ "aria-hidden": true,
1131
+ size: 16,
1132
+ weight: "bold"
1133
+ })
1134
+ })]
1100
1135
  })]
1101
1136
  }), /* @__PURE__ */ jsx(MarkdownEditor, {
1102
1137
  activeAnnotationId: pendingTarget ? pendingPlanCommentId : activeThreadId,
@@ -2098,14 +2133,41 @@ const getReviewCommentRangeProps = (comment) => {
2098
2133
  ...startSide !== comment.side ? { startSide } : {}
2099
2134
  } : {};
2100
2135
  };
2101
- const toPullRequestReviewComment = (comment) => ({
2136
+ const toPullRequestReviewComment = (comment, { includeSectionId = false } = {}) => ({
2102
2137
  body: comment.body,
2103
2138
  filePath: comment.filePath,
2104
2139
  ...comment.lineNumber != null ? { lineNumber: comment.lineNumber } : {},
2140
+ ...includeSectionId ? { sectionId: comment.sectionId } : {},
2105
2141
  ...comment.side ? { side: comment.side } : {},
2106
2142
  ...getReviewCommentRangeProps(comment),
2107
2143
  ...comment.threadId ? { threadId: comment.threadId } : {}
2108
2144
  });
2145
+ const toSubmittedReviewComment = (comment, draft) => ({
2146
+ ...comment.anchor ? { anchor: comment.anchor } : {},
2147
+ author: comment.author,
2148
+ body: comment.body,
2149
+ ...comment.canDelete ? { canDelete: true } : {},
2150
+ ...comment.canEdit ? { canEdit: true } : {},
2151
+ ...comment.canReplyThread === false ? { canReplyThread: false } : {},
2152
+ ...comment.canResolveThread ? { canResolveThread: true } : {},
2153
+ filePath: comment.filePath,
2154
+ id: comment.id,
2155
+ ...comment.isOutdated ? { isOutdated: true } : {},
2156
+ isReadOnly: true,
2157
+ ...comment.isThreadResolved ? { isThreadResolved: true } : {},
2158
+ ...comment.lineNumber != null ? { lineNumber: comment.lineNumber } : {},
2159
+ sectionId: comment.sectionId ?? draft.sectionId,
2160
+ ...comment.side ? { side: comment.side } : {},
2161
+ ...comment.startLineNumber != null ? { startLineNumber: comment.startLineNumber } : {},
2162
+ ...comment.startSide ? { startSide: comment.startSide } : {},
2163
+ ...comment.submittedAt ? { submittedAt: comment.submittedAt } : {},
2164
+ ...comment.threadId ? { threadId: comment.threadId } : {},
2165
+ ...comment.url ? { url: comment.url } : {}
2166
+ });
2167
+ const mergeReviewComments = (snapshotComments, localComments) => {
2168
+ const snapshotIds = new Set(snapshotComments.map((comment) => comment.id));
2169
+ return [...snapshotComments, ...localComments.filter((comment) => !snapshotIds.has(comment.id))];
2170
+ };
2109
2171
  const isPendingPullRequestReviewComment = (comment) => !comment.isReadOnly && !comment.threadId && comment.remoteSubmit?.status !== "submitting" && comment.body.trim().length > 0;
2110
2172
  const getPendingPullRequestReviewComments = (comments, activeDraft = null) => {
2111
2173
  return comments.flatMap((comment) => {
@@ -2120,8 +2182,9 @@ const findReusableReviewCommentDraft = (comments, activeDraft = null) => comment
2120
2182
  const getCommentKey = (comment) => isFileReviewComment(comment) ? `${comment.sectionId}:file` : `${comment.sectionId}:${comment.side}:${comment.lineNumber}:${comment.startLineNumber ?? comment.lineNumber}:${comment.startSide ?? comment.side}`;
2121
2183
  const getCommentTextDigest = (value) => value ? `${value.length},${value.split("\n").length}` : "0,0";
2122
2184
  const getReviewCommentsDigest = (comments) => comments.map((comment) => `${comment.id}:${comment.sectionId}:${comment.side}:${comment.lineNumber}:${comment.startLineNumber ?? ""}:${comment.startSide ?? ""}:${comment.anchor ?? ""}:${getCommentTextDigest(comment.body)}:${comment.codexReply?.status ?? ""}:${getCommentTextDigest(comment.codexReply?.body)}:${getCommentTextDigest(comment.codexReply?.error)}:${comment.remoteSubmit?.status ?? ""}:${comment.remoteSubmit?.error ?? ""}:${comment.threadId ?? ""}:${comment.canResolveThread === true ? "1" : "0"}:${comment.isThreadResolved === true ? "1" : "0"}`).join("\0");
2123
- const getReviewCommentsFromState = (state) => state.source.type === "pull-request" ? (state.reviewComments ?? []).flatMap((comment) => {
2124
- const section = state.files.find((candidate) => candidate.path === comment.filePath)?.sections[0];
2185
+ const getReviewCommentsFromState = (state) => (state.reviewComments ?? []).flatMap((comment) => {
2186
+ const file = state.files.find((candidate) => candidate.path === comment.filePath);
2187
+ const section = file?.sections.find((candidate) => candidate.id === comment.sectionId) ?? file?.sections[0];
2125
2188
  return section ? [{
2126
2189
  author: comment.author,
2127
2190
  body: comment.body,
@@ -2143,7 +2206,7 @@ const getReviewCommentsFromState = (state) => state.source.type === "pull-reques
2143
2206
  ...comment.threadId ? { threadId: comment.threadId } : {},
2144
2207
  url: comment.url
2145
2208
  }] : [];
2146
- }) : [];
2209
+ });
2147
2210
  const shouldDiscardReviewCommentOnEscape = (body, confirmDiscard = window.confirm) => body.trim().length === 0 || confirmDiscard("Discard this review comment?");
2148
2211
  //#endregion
2149
2212
  //#region app/components/useCopiedState.ts
@@ -4097,8 +4160,8 @@ function TocStop({ current, onSelect, stop, visited }) {
4097
4160
  })]
4098
4161
  });
4099
4162
  }
4100
- function SupportingFilesStop({ changedPaths, files, navigation, showWhitespace, walkthroughView }) {
4101
- const uncoveredFiles = getUncoveredWalkthroughFileLineItems(files, walkthroughView, showWhitespace).filter((file) => !changedPaths?.has(file.path));
4163
+ function SupportingFilesStop({ files, navigation, showWhitespace, walkthroughView }) {
4164
+ const uncoveredFiles = getUncoveredWalkthroughFileLineItems(files, walkthroughView, showWhitespace);
4102
4165
  if (walkthroughView.support.length === 0 && uncoveredFiles.length === 0) return null;
4103
4166
  const current = navigation.mode === "support";
4104
4167
  const isDone = navigation.supportVisited && !current;
@@ -4141,40 +4204,7 @@ function SupportingFilesStop({ changedPaths, files, navigation, showWhitespace,
4141
4204
  })]
4142
4205
  });
4143
4206
  }
4144
- function ChangedFilesStop({ changedPaths, files, navigation, showWhitespace, walkthroughView }) {
4145
- const changedFiles = getUncoveredWalkthroughFileLineItems(files, walkthroughView, showWhitespace).filter((file) => changedPaths?.has(file.path));
4146
- if (changedFiles.length === 0) return null;
4147
- const fileRows = formatWalkthroughFileLineRows(changedFiles);
4148
- return /* @__PURE__ */ jsxs("div", {
4149
- className: "wt-toc-chapter",
4150
- children: [/* @__PURE__ */ jsxs("div", {
4151
- className: "wt-toc-chapter-head",
4152
- children: [/* @__PURE__ */ jsx("span", {
4153
- className: "wt-toc-chapter-icon",
4154
- children: /* @__PURE__ */ jsx(ArrowsClockwiseIcon, { size: 15 })
4155
- }), /* @__PURE__ */ jsx("span", {
4156
- className: "wt-toc-chapter-title",
4157
- children: "Changed"
4158
- })]
4159
- }), /* @__PURE__ */ jsx("div", {
4160
- className: "wt-toc-stops",
4161
- children: /* @__PURE__ */ jsxs("button", {
4162
- className: "wt-toc-stop",
4163
- onClick: navigation.openSupport,
4164
- title: "Changed after the walkthrough was generated",
4165
- type: "button",
4166
- children: [/* @__PURE__ */ jsx("span", {
4167
- className: "wt-toc-rail",
4168
- children: /* @__PURE__ */ jsx("span", { className: "wt-toc-node" })
4169
- }), /* @__PURE__ */ jsx("span", {
4170
- className: "wt-toc-main",
4171
- children: /* @__PURE__ */ jsx(TocFileRows, { files: fileRows })
4172
- })]
4173
- })
4174
- })]
4175
- });
4176
- }
4177
- function NarrativeSidebar({ allowCommit = true, changedPaths, files, navigation, onShareWalkthrough, shareWalkthroughDisabled = false, showWhitespace, walkthrough }) {
4207
+ function NarrativeSidebar({ allowCommit = true, files, navigation, onShareWalkthrough, shareWalkthroughDisabled = false, showWhitespace, walkthrough }) {
4178
4208
  const { walkthroughView } = navigation;
4179
4209
  if (!walkthroughView) return /* @__PURE__ */ jsx("div", {
4180
4210
  className: "wt-empty",
@@ -4220,14 +4250,6 @@ function NarrativeSidebar({ allowCommit = true, changedPaths, files, navigation,
4220
4250
  })]
4221
4251
  }, chapter.id)),
4222
4252
  /* @__PURE__ */ jsx(SupportingFilesStop, {
4223
- changedPaths,
4224
- files,
4225
- navigation,
4226
- showWhitespace,
4227
- walkthroughView
4228
- }),
4229
- /* @__PURE__ */ jsx(ChangedFilesStop, {
4230
- changedPaths,
4231
4253
  files,
4232
4254
  navigation,
4233
4255
  showWhitespace,
@@ -5171,7 +5193,7 @@ function ImageDiffPreview({ file, loadImageContent, onLayoutReady, section, sour
5171
5193
  })]
5172
5194
  });
5173
5195
  }
5174
- function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusCommentId, focusEditorRef, identity, isPullRequest, keymap, onAskCodex, onCommentBlur, onCommentDraftChange, onCommentFocus, onDeleteComment, onSaveCommentEdit, onSubmitComment, onUpdateComment }) {
5196
+ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusCommentId, focusEditorRef, identity, keymap, onAskCodex, onCommentBlur, onCommentDraftChange, onCommentFocus, onDeleteComment, onSaveCommentEdit, onSubmitComment, onUpdateComment, supportsReviewCommentActions }) {
5175
5197
  const [editState, setEditState] = useState(() => ({
5176
5198
  commentId: comment.id,
5177
5199
  draft: comment.body,
@@ -5213,7 +5235,7 @@ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusC
5213
5235
  const draftComment = withCommentBody(comment, draft);
5214
5236
  const canAskCodex = onAskCodex != null && canAskCodexForComment(draftComment);
5215
5237
  const commentCanSubmit = canSubmitComment(draftComment);
5216
- const canEditExistingComment = isPullRequest && comment.isReadOnly && comment.canEdit === true;
5238
+ const canEditExistingComment = supportsReviewCommentActions && comment.isReadOnly && comment.canEdit === true;
5217
5239
  const canSaveEdit = canEditExistingComment && !editSubmitting && Boolean(editDraft.trim()) && editDraft.trim() !== displayedBody.trim();
5218
5240
  const editEditorRef = useRef(null);
5219
5241
  const setEditEditorRef = useCallback((editor) => {
@@ -5395,13 +5417,13 @@ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusC
5395
5417
  ]);
5396
5418
  const handleKeyDown = useCallback((event) => {
5397
5419
  if (matchesShortcut(event, keymap, "submitComment")) {
5398
- if (isPullRequest && commentCanSubmit) {
5420
+ if (supportsReviewCommentActions && commentCanSubmit) {
5399
5421
  event.preventDefault();
5400
5422
  event.stopPropagation();
5401
5423
  handleSubmitComment();
5402
5424
  return;
5403
5425
  }
5404
- if (!isPullRequest && canAskCodex) {
5426
+ if (!supportsReviewCommentActions && canAskCodex) {
5405
5427
  event.preventDefault();
5406
5428
  event.stopPropagation();
5407
5429
  handleAskCodex();
@@ -5421,9 +5443,9 @@ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusC
5421
5443
  draft,
5422
5444
  handleAskCodex,
5423
5445
  handleSubmitComment,
5424
- isPullRequest,
5425
5446
  keymap,
5426
- onDeleteComment
5447
+ onDeleteComment,
5448
+ supportsReviewCommentActions
5427
5449
  ]);
5428
5450
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
5429
5451
  className: "review-comment",
@@ -5431,7 +5453,7 @@ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusC
5431
5453
  className: "review-comment-body",
5432
5454
  children: [
5433
5455
  /* @__PURE__ */ jsxs("div", {
5434
- className: `review-comment-header${isPullRequest && !comment.isReadOnly || canEditExistingComment || comment.canDelete || editingExistingComment ? " with-comment-action" : ""}${comment.isReadOnly ? " read-only" : ""}`,
5456
+ className: `review-comment-header${supportsReviewCommentActions && !comment.isReadOnly || canEditExistingComment || comment.canDelete || editingExistingComment ? " with-comment-action" : ""}${comment.isReadOnly ? " read-only" : ""}`,
5435
5457
  children: [
5436
5458
  /* @__PURE__ */ jsx("strong", { children: displayName }),
5437
5459
  editingExistingComment ? /* @__PURE__ */ jsxs("span", {
@@ -5484,7 +5506,7 @@ function ReviewCommentEditor({ agentId, agentLabel, comment, displayName, focusC
5484
5506
  src: agentIconUrl(agentId)
5485
5507
  }), "Ask"]
5486
5508
  }) : null,
5487
- isPullRequest && !comment.isReadOnly ? /* @__PURE__ */ jsxs("button", {
5509
+ supportsReviewCommentActions && !comment.isReadOnly ? /* @__PURE__ */ jsxs("button", {
5488
5510
  className: "review-comment-action",
5489
5511
  disabled: !commentCanSubmit,
5490
5512
  onClick: handleSubmitComment,
@@ -5636,7 +5658,7 @@ const groupReviewCommentsByThread = (comments) => {
5636
5658
  };
5637
5659
  const noopResolveThread = () => {};
5638
5660
  const noopLayoutReady = () => {};
5639
- function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentId, focusEditorRef, identity, isPullRequest, keymap, onAskCodex, onCommentBlur, onCommentDraftChange, onCommentFocus, onDeleteComment, onReplyToThread, onResolveThread = noopResolveThread, onSaveCommentEdit, onSubmitComment, onUpdateComment }) {
5661
+ function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentId, focusEditorRef, identity, keymap, onAskCodex, onCommentBlur, onCommentDraftChange, onCommentFocus, onDeleteComment, onReplyToThread, onResolveThread = noopResolveThread, onSaveCommentEdit, onSubmitComment, onUpdateComment, supportsReviewCommentActions }) {
5640
5662
  const [resolveState, setResolveState] = useState({
5641
5663
  error: null,
5642
5664
  submitting: false,
@@ -5645,8 +5667,8 @@ function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentI
5645
5667
  const lastComment = comments.at(-1);
5646
5668
  const threadId = lastComment?.threadId;
5647
5669
  const threadResolved = comments.some((comment) => comment.isThreadResolved === true);
5648
- const canResolveThread = isPullRequest && threadId != null && comments.some((comment) => comment.canResolveThread === true);
5649
- const canReplyToThread = isPullRequest && threadId != null && comments.some((comment) => comment.isReadOnly) && !comments.some((comment) => !comment.isReadOnly) && !comments.some((comment) => comment.canReplyThread === false) && !threadResolved;
5670
+ const canResolveThread = supportsReviewCommentActions && threadId != null && comments.some((comment) => comment.canResolveThread === true);
5671
+ const canReplyToThread = supportsReviewCommentActions && threadId != null && comments.some((comment) => comment.isReadOnly) && !comments.some((comment) => !comment.isReadOnly) && !comments.some((comment) => comment.canReplyThread === false) && !threadResolved;
5650
5672
  const hasThreadActions = canReplyToThread || canResolveThread;
5651
5673
  const resolving = resolveState.threadId === threadId && resolveState.submitting;
5652
5674
  const resolveError = resolveState.threadId === threadId ? resolveState.error : null;
@@ -5693,7 +5715,6 @@ function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentI
5693
5715
  focusCommentId,
5694
5716
  focusEditorRef,
5695
5717
  identity,
5696
- isPullRequest,
5697
5718
  keymap,
5698
5719
  onAskCodex,
5699
5720
  onCommentBlur,
@@ -5702,7 +5723,8 @@ function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentI
5702
5723
  onDeleteComment,
5703
5724
  onSaveCommentEdit,
5704
5725
  onSubmitComment,
5705
- onUpdateComment
5726
+ onUpdateComment,
5727
+ supportsReviewCommentActions
5706
5728
  }, comment.id);
5707
5729
  }), hasThreadActions ? /* @__PURE__ */ jsxs("div", {
5708
5730
  className: "review-comment-thread-footer",
@@ -5727,7 +5749,7 @@ function ReviewCommentThreadGroup({ agentId, agentLabel, comments, focusCommentI
5727
5749
  }) : null]
5728
5750
  });
5729
5751
  }
5730
- function ReviewAnnotation({ agentId, agentLabel, annotation, comments, focusCommentId, focusCommentRequest, identity, isPullRequest, keymap, onAskCodex, onCommentBlur, onCommentDraftChange, onCommentFocus, onDeleteComment, onHeightChange, onReplyToThread, onResolveThread = noopResolveThread, onSaveCommentEdit, onSubmitComment, onUpdateComment }) {
5752
+ function ReviewAnnotation({ agentId, agentLabel, annotation, comments, focusCommentId, focusCommentRequest, identity, keymap, onAskCodex, onCommentBlur, onCommentDraftChange, onCommentFocus, onDeleteComment, onHeightChange, onReplyToThread, onResolveThread = noopResolveThread, onSaveCommentEdit, onSubmitComment, onUpdateComment, supportsReviewCommentActions }) {
5731
5753
  const focusEditorRef = useRef(null);
5732
5754
  const threadRef = useRef(null);
5733
5755
  const setFocusEditorRef = useCallback((node) => {
@@ -5767,7 +5789,6 @@ function ReviewAnnotation({ agentId, agentLabel, annotation, comments, focusComm
5767
5789
  focusCommentId,
5768
5790
  focusEditorRef: setFocusEditorRef,
5769
5791
  identity,
5770
- isPullRequest,
5771
5792
  keymap,
5772
5793
  onAskCodex,
5773
5794
  onCommentBlur,
@@ -5778,7 +5799,8 @@ function ReviewAnnotation({ agentId, agentLabel, annotation, comments, focusComm
5778
5799
  onResolveThread,
5779
5800
  onSaveCommentEdit,
5780
5801
  onSubmitComment,
5781
- onUpdateComment
5802
+ onUpdateComment,
5803
+ supportsReviewCommentActions
5782
5804
  }, group.key))
5783
5805
  });
5784
5806
  }
@@ -5966,7 +5988,7 @@ const getHunkSelectionRange = (hunk) => {
5966
5988
  } : null;
5967
5989
  };
5968
5990
  const isSameSelection = (a, b) => a.start === b.start && a.end === b.end && a.side === b.side;
5969
- function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedToggle = false, blocks, bottomInset = codeViewLayout.paddingBottom, codeQualityFindings = [], collapsed, comments, commitMetadata, diffLineHeight = 20, diffStyle, disableWorkerPool = false, expandedGenerated = emptyExpandedGenerated, files, focusCommentId, focusCommentRequest, forceExpandedPaths, gitIdentity, hunkNavigation, initialMarkdownPreviewSectionIds = emptyMarkdownPreviewSectionIds, isPullRequest, isReadOnly = false, itemVersionByKey, keymap, loadingSectionIds, onActiveBlockChange, onAskCodex, onCommentDraftChange, onCreateComment, onDeleteComment, onLoadImageContent, onLoadSection, onLoadSectionContents, onOpenFile, onRefreshMarkdown, onResolveThread = noopResolveThread, onSaveCommentEdit, onSelectPathFromScroll, onSubmitComment, onToggleCollapsed, onToggleViewed, onUpdateComment, onUpdateSourceDescription, onUpdateSourceTitle, onUploadSourceDescriptionAsset, reviewIdentityByPath, scrollTarget, searchQuery, selectedPath, showSourceDescription = true, showWhitespace, source, sourceDescriptionActions, sourceDescriptionFooter, theme = "system", viewed, walkthroughNotes, wordWrap }) {
5991
+ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedToggle = false, blocks, bottomInset = codeViewLayout.paddingBottom, codeQualityFindings = [], collapsed, comments, commitMetadata, diffLineHeight = 20, diffStyle, disableWorkerPool = false, expandedGenerated = emptyExpandedGenerated, files, focusCommentId, focusCommentRequest, forceExpandedPaths, gitIdentity, hunkNavigation, initialMarkdownPreviewSectionIds = emptyMarkdownPreviewSectionIds, isReadOnly = false, itemVersionByKey, keymap, loadingSectionIds, onActiveBlockChange, onAskCodex, onCommentDraftChange, onCreateComment, onDeleteComment, onLoadImageContent, onLoadSection, onLoadSectionContents, onOpenFile, onRefreshMarkdown, onResolveThread = noopResolveThread, onSaveCommentEdit, onSelectPathFromScroll, onSubmitComment, onToggleCollapsed, onToggleViewed, onUpdateComment, onUpdateSourceDescription, onUpdateSourceTitle, onUploadSourceDescriptionAsset, reviewIdentityByPath, scrollTarget, searchQuery, selectedPath, showSourceDescription = true, showWhitespace, source, sourceDescriptionActions, sourceDescriptionFooter, supportsReviewCommentActions, theme = "system", viewed, walkthroughNotes, wordWrap }) {
5970
5992
  const codeViewRef = useRef(null);
5971
5993
  const markdownEditorRefs = useRef(/* @__PURE__ */ new Map());
5972
5994
  const refreshingMarkdownSectionsRef = useRef(/* @__PURE__ */ new Set());
@@ -6885,7 +6907,6 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
6885
6907
  focusCommentId,
6886
6908
  focusCommentRequest,
6887
6909
  identity: gitIdentity,
6888
- isPullRequest,
6889
6910
  keymap,
6890
6911
  onAskCodex,
6891
6912
  onCommentBlur: blurComment,
@@ -6897,7 +6918,8 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
6897
6918
  onResolveThread,
6898
6919
  onSaveCommentEdit,
6899
6920
  onSubmitComment,
6900
- onUpdateComment
6921
+ onUpdateComment,
6922
+ supportsReviewCommentActions
6901
6923
  });
6902
6924
  }, [
6903
6925
  agentId,
@@ -6908,7 +6930,6 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
6908
6930
  focusCommentRequest,
6909
6931
  focusComment,
6910
6932
  gitIdentity,
6911
- isPullRequest,
6912
6933
  itemMetadata,
6913
6934
  keymap,
6914
6935
  markMarkdownPreviewLayoutReady,
@@ -6924,7 +6945,8 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
6924
6945
  renderComments,
6925
6946
  replyToThread,
6926
6947
  setMarkdownEditorRef,
6927
- source
6948
+ source,
6949
+ supportsReviewCommentActions
6928
6950
  ]);
6929
6951
  const handleScroll = useCallback((_scrollTop, viewer) => {
6930
6952
  onSelectPathFromScroll(viewer);
@@ -6980,6 +7002,69 @@ function ReviewCodeView({ activeSearchMatch, agentId, agentLabel, allowViewedTog
6980
7002
  });
6981
7003
  }
6982
7004
  //#endregion
7005
+ //#region app/components/ReviewTopBar.tsx
7006
+ function ReviewTopBar({ actions, context, leading, mode, modes, onModeChange, onToggleSidebar, repository, repositoryTooltip, sidebarCollapsed, toggleTitle }) {
7007
+ return /* @__PURE__ */ jsxs("header", {
7008
+ className: "review-top-bar workspace-top-bar",
7009
+ children: [
7010
+ /* @__PURE__ */ jsxs("div", {
7011
+ className: "review-top-bar-left",
7012
+ children: [
7013
+ leading,
7014
+ /* @__PURE__ */ jsx("button", {
7015
+ "aria-label": sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar",
7016
+ className: "sidebar-toggle-button",
7017
+ onClick: onToggleSidebar,
7018
+ title: toggleTitle,
7019
+ type: "button",
7020
+ children: /* @__PURE__ */ jsx(SidebarSimpleIcon, {
7021
+ "aria-hidden": true,
7022
+ size: 18,
7023
+ weight: "bold"
7024
+ })
7025
+ }),
7026
+ /* @__PURE__ */ jsx("div", {
7027
+ className: "review-top-bar-repository-slot",
7028
+ title: repositoryTooltip,
7029
+ children: repository
7030
+ })
7031
+ ]
7032
+ }),
7033
+ /* @__PURE__ */ jsx("div", {
7034
+ "aria-label": "Review mode",
7035
+ className: "review-mode-control",
7036
+ role: "tablist",
7037
+ children: modes.map((item) => /* @__PURE__ */ jsxs("button", {
7038
+ "aria-label": item.ariaLabel,
7039
+ "aria-selected": mode === item.value,
7040
+ onClick: () => onModeChange(item.value),
7041
+ role: "tab",
7042
+ title: item.title,
7043
+ type: "button",
7044
+ children: [
7045
+ item.icon,
7046
+ /* @__PURE__ */ jsx("span", {
7047
+ className: "review-mode-label",
7048
+ children: item.label
7049
+ }),
7050
+ item.indicator
7051
+ ]
7052
+ }, item.value))
7053
+ }),
7054
+ /* @__PURE__ */ jsxs("div", {
7055
+ className: "review-top-bar-right",
7056
+ children: [context ? /* @__PURE__ */ jsx("div", {
7057
+ className: "review-top-bar-context",
7058
+ children: context
7059
+ }) : null, actions ? /* @__PURE__ */ jsx("div", {
7060
+ className: "review-top-bar-actions",
7061
+ children: actions
7062
+ }) : null]
7063
+ })
7064
+ ]
7065
+ });
7066
+ }
7067
+ //#endregion
6983
7068
  //#region app/components/walkthrough/CommitView.tsx
6984
7069
  const TERMINAL_COLS = 80;
6985
7070
  const TERMINAL_ROWS = 12;
@@ -7528,27 +7613,6 @@ function SupportHeader({ current }) {
7528
7613
  }), /* @__PURE__ */ jsx(Narration, { prose: "Supporting changes grouped outside the main sequence." })]
7529
7614
  });
7530
7615
  }
7531
- function ChangedHeader({ current, onRegenerate, regenerateDisabled = false }) {
7532
- return /* @__PURE__ */ jsxs("div", {
7533
- className: `wt-stop-block wt-stop-block-header${current ? " current" : ""}`,
7534
- children: [/* @__PURE__ */ jsxs("div", {
7535
- className: "wt-stage-title-row",
7536
- children: [/* @__PURE__ */ jsx("h2", {
7537
- className: "wt-stage-title",
7538
- children: "Changed"
7539
- }), onRegenerate ? /* @__PURE__ */ jsxs("button", {
7540
- className: "wt-regenerate",
7541
- disabled: regenerateDisabled,
7542
- onClick: onRegenerate,
7543
- type: "button",
7544
- children: [/* @__PURE__ */ jsx(ArrowsClockwiseIcon, {
7545
- size: 13,
7546
- weight: "bold"
7547
- }), regenerateDisabled ? "Regenerating…" : "Regenerate walkthrough"]
7548
- }) : null]
7549
- }), /* @__PURE__ */ jsx(Narration, { prose: "These changes arrived after the walkthrough was generated and are not part of its narrative yet." })]
7550
- });
7551
- }
7552
7616
  const createWalkthroughBlocks = (files, walkthroughView, currentIndex) => {
7553
7617
  const blocks = [];
7554
7618
  const firstBlockIdByStop = [];
@@ -7600,7 +7664,7 @@ const getBlockReviewTarget = (blocks, blockId) => {
7600
7664
  reviewIdentity: block.reviewIdentity
7601
7665
  } : null;
7602
7666
  };
7603
- const createSupportBlocks = (files, selected, walkthroughView, showWhitespace, changedPaths, onRegenerateWalkthrough, regenerateDisabled) => {
7667
+ const createSupportBlocks = (files, selected, walkthroughView, showWhitespace) => {
7604
7668
  const blocks = [];
7605
7669
  for (const group of walkthroughView.supportByReason) for (const item of group.files) getFocusedRunDiffs(item, files).forEach(({ file, note, reviewIdentity }, runIndex) => {
7606
7670
  const blockId = `walkthrough:support:${item.id}:${runIndex}`;
@@ -7616,9 +7680,7 @@ const createSupportBlocks = (files, selected, walkthroughView, showWhitespace, c
7616
7680
  });
7617
7681
  });
7618
7682
  const uncoveredFiles = getUncoveredWalkthroughFiles(files, walkthroughView, showWhitespace);
7619
- const changedFiles = uncoveredFiles.filter((file) => changedPaths?.has(file.path));
7620
- const uncoveredSupportFiles = uncoveredFiles.filter((file) => !changedPaths?.has(file.path));
7621
- for (const file of uncoveredSupportFiles) {
7683
+ for (const file of uncoveredFiles) {
7622
7684
  const blockId = `walkthrough:uncovered:${file.path}`;
7623
7685
  const isFirstBlock = blocks.length === 0;
7624
7686
  blocks.push({
@@ -7634,25 +7696,6 @@ const createSupportBlocks = (files, selected, walkthroughView, showWhitespace, c
7634
7696
  }
7635
7697
  });
7636
7698
  }
7637
- changedFiles.forEach((file, fileIndex) => {
7638
- const blockId = `walkthrough:changed:${file.path}`;
7639
- blocks.push({
7640
- file,
7641
- header: fileIndex === 0 ? /* @__PURE__ */ jsx(ChangedHeader, {
7642
- current: selected,
7643
- onRegenerate: onRegenerateWalkthrough,
7644
- regenerateDisabled
7645
- }) : null,
7646
- headerSelected: selected,
7647
- id: blockId,
7648
- itemIdPrefix: blockId,
7649
- note: "Changed after the walkthrough was generated.",
7650
- reviewIdentity: {
7651
- fingerprint: file.fingerprint,
7652
- key: blockId
7653
- }
7654
- });
7655
- });
7656
7699
  return blocks;
7657
7700
  };
7658
7701
  function Arc({ committable, navigation, onShareWalkthrough, shareWalkthroughDisabled = false, supportAvailable, walkthroughView }) {
@@ -7815,7 +7858,7 @@ function Arc({ committable, navigation, onShareWalkthrough, shareWalkthroughDisa
7815
7858
  ]
7816
7859
  });
7817
7860
  }
7818
- function NarrativeWalkthroughView({ allowCommit = true, changedPaths, files, navigation, onActiveReviewTargetChange, onCommit, onCommitOutput, onRegenerateWalkthrough, onShareWalkthrough, onUpdateCommitMessage, regenerateDisabled, renderDiffBlocks, shareWalkthroughDisabled, showWhitespace, walkthrough }) {
7861
+ function NarrativeWalkthroughView({ allowCommit = true, files, navigation, onActiveReviewTargetChange, onCommit, onCommitOutput, onShareWalkthrough, onUpdateCommitMessage, renderDiffBlocks, shareWalkthroughDisabled, showWhitespace, walkthrough }) {
7819
7862
  const { walkthroughView } = navigation;
7820
7863
  const committable = allowCommit && isWalkthroughCommittable(walkthrough);
7821
7864
  const walkthroughBlocks = useMemo(() => walkthroughView ? createWalkthroughBlocks(files, walkthroughView, navigation.index) : emptyWalkthroughBlockSet, [
@@ -7823,12 +7866,9 @@ function NarrativeWalkthroughView({ allowCommit = true, changedPaths, files, nav
7823
7866
  navigation.index,
7824
7867
  walkthroughView
7825
7868
  ]);
7826
- const supportBlocks = useMemo(() => walkthroughView ? createSupportBlocks(files, navigation.mode === "support", walkthroughView, showWhitespace, changedPaths, onRegenerateWalkthrough, regenerateDisabled) : [], [
7827
- changedPaths,
7869
+ const supportBlocks = useMemo(() => walkthroughView ? createSupportBlocks(files, navigation.mode === "support", walkthroughView, showWhitespace) : [], [
7828
7870
  files,
7829
7871
  navigation.mode,
7830
- onRegenerateWalkthrough,
7831
- regenerateDisabled,
7832
7872
  showWhitespace,
7833
7873
  walkthroughView
7834
7874
  ]);
@@ -8712,8 +8752,16 @@ const disabledCommitMessage = async () => ({
8712
8752
  reason: "Shared walkthroughs are read-only.",
8713
8753
  status: "unavailable"
8714
8754
  });
8715
- function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMode, interactive, onModeChange, providerLabel = "provider", settingsBar, signInLabel = "Sign in to comment", snapshot, sourceDescriptionFooterAside, title }) {
8755
+ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMode, interactive, onDeleteShare, onModeChange, providerLabel = "provider", repositoryUrl, settingsBar, signInLabel = "Sign in to comment", snapshot, sourceDescriptionFooterAside, title }) {
8716
8756
  const canComment = commenting?.canComment ?? Boolean(interactive);
8757
+ const deleteShare = useCallback(async () => {
8758
+ if (!onDeleteShare || !window.confirm("Delete this shared walkthrough? This cannot be undone.")) return;
8759
+ try {
8760
+ await onDeleteShare();
8761
+ } catch (error) {
8762
+ window.alert(error instanceof Error ? error.message : String(error));
8763
+ }
8764
+ }, [onDeleteShare]);
8717
8765
  const submitReviewComment = commenting?.onSubmitComment ?? interactive?.onSubmitComment;
8718
8766
  const submitGeneralDiscussion = commenting?.onSubmitGeneralComment ?? interactive?.onSubmitGeneralComment;
8719
8767
  const updateReviewComment = commenting?.onUpdateComment ?? interactive?.onUpdateComment;
@@ -8727,10 +8775,22 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
8727
8775
  const keymap = useMemo(() => createDefaultConfig().keymap, []);
8728
8776
  const [fileSearchQuery, setFileSearchQuery] = useState("");
8729
8777
  const [uncontrolledSidebarMode, setUncontrolledSidebarMode] = useState(() => initialMode ?? (interactive ? "tree" : "walkthrough"));
8730
- const sidebarMode = Boolean(initialMode && onModeChange) ? initialMode : uncontrolledSidebarMode;
8778
+ const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
8779
+ useEffect(() => {
8780
+ const handleKeyDown = (event) => {
8781
+ if (event.repeat || !matchesShortcut(event, keymap, "toggleSidebar")) return;
8782
+ event.preventDefault();
8783
+ setSidebarCollapsed((current) => !current);
8784
+ };
8785
+ window.addEventListener("keydown", handleKeyDown);
8786
+ return () => window.removeEventListener("keydown", handleKeyDown);
8787
+ }, [keymap]);
8788
+ const sidebarMode = Boolean(initialMode && onModeChange) && initialMode ? initialMode : uncontrolledSidebarMode;
8731
8789
  const [treeScrollTarget, setTreeScrollTarget] = useState(null);
8732
8790
  const { bumpItemVersion, collapsed, expandedGenerated, itemVersionByKey, selectedPath, setSelectedPath, toggleCollapsed, toggleViewed, viewed } = useReviewFileState({ initialSelectedPath: snapshot.files[0]?.path ?? null });
8733
8791
  const { resizeSidebar, sidebarWidth } = useResizableSidebar({
8792
+ collapseThreshold: 80,
8793
+ onCollapse: () => setSidebarCollapsed(true),
8734
8794
  onWidthCommit: writeSharedSidebarWidth,
8735
8795
  readWidth: readSharedSidebarWidth
8736
8796
  });
@@ -8741,7 +8801,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
8741
8801
  body: editedReviewCommentBodies[comment.id]
8742
8802
  } : comment), [editedReviewCommentBodies, snapshotReviewComments]);
8743
8803
  const [localReviewComments, setLocalReviewComments] = useState(emptyReviewComments);
8744
- const reviewComments = useMemo(() => [...visibleSnapshotReviewComments, ...localReviewComments], [localReviewComments, visibleSnapshotReviewComments]);
8804
+ const reviewComments = useMemo(() => mergeReviewComments(visibleSnapshotReviewComments, localReviewComments), [localReviewComments, visibleSnapshotReviewComments]);
8745
8805
  const { activeReviewCommentDraftRef, activeReviewCommentDraftState, clearCommentFocus, createComment, deleteComment: deleteLocalComment, focusCommentId, focusCommentRequest, reviewCommentsRef, updateActiveReviewCommentDraft, updateComment } = useReviewCommentDrafts({
8746
8806
  canCreateComment: canComment,
8747
8807
  comments: reviewComments,
@@ -8751,6 +8811,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
8751
8811
  const generalCommentThreads = snapshot.repository.generalComments ?? emptyGeneralCommentThreads;
8752
8812
  const generalComments = useMemo(() => (snapshot.repository.generalComments ?? emptyGeneralCommentThreads).flatMap((thread) => thread.comments), [snapshot.repository.generalComments]);
8753
8813
  const generalCommentCount = generalComments.length;
8814
+ const showCommentsTab = Boolean(commenting || interactive || generalCommentCount > 0);
8754
8815
  const [generalCommentDraft, setGeneralCommentDraft] = useState("");
8755
8816
  const [generalCommentEditDraft, setGeneralCommentEditDraft] = useState("");
8756
8817
  const [editingGeneralCommentId, setEditingGeneralCommentId] = useState(null);
@@ -8823,6 +8884,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
8823
8884
  const comment = reviewCommentsRef.current.find((candidate) => candidate.id === commentId);
8824
8885
  if (comment?.isReadOnly && comment.canDelete && commenting?.onDeleteComment) {
8825
8886
  updateActiveReviewCommentDraft(null);
8887
+ setLocalReviewComments((current) => current.filter((candidate) => candidate.id !== commentId));
8826
8888
  commenting.onDeleteComment(commentId).catch((error) => {
8827
8889
  window.alert(error instanceof Error ? error.message : String(error));
8828
8890
  });
@@ -8843,9 +8905,12 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
8843
8905
  ...candidate,
8844
8906
  remoteSubmit: { status: "submitting" }
8845
8907
  } : candidate));
8846
- submitReviewComment(toPullRequestReviewComment(comment)).then(() => {
8908
+ submitReviewComment(toPullRequestReviewComment(comment, { includeSectionId: commenting != null })).then((submittedComment) => {
8847
8909
  clearCommentFocus(commentId);
8848
- setLocalReviewComments((current) => current.filter((candidate) => candidate.id !== commentId));
8910
+ setLocalReviewComments((current) => current.flatMap((candidate) => {
8911
+ if (candidate.id !== commentId) return [candidate];
8912
+ return [toSubmittedReviewComment(submittedComment, candidate)];
8913
+ }));
8849
8914
  bumpItemVersion(comment.filePath);
8850
8915
  }).catch((error) => {
8851
8916
  setLocalReviewComments((current) => current.map((candidate) => candidate.id === commentId ? {
@@ -8860,6 +8925,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
8860
8925
  }, [
8861
8926
  bumpItemVersion,
8862
8927
  clearCommentFocus,
8928
+ commenting,
8863
8929
  reviewCommentsRef,
8864
8930
  submitReviewComment,
8865
8931
  updateActiveReviewCommentDraft
@@ -8871,7 +8937,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
8871
8937
  if (event === "COMMENT" && pendingComments.length === 0 && !body?.trim()) return;
8872
8938
  const pendingIds = new Set(pendingComments.map((comment) => comment.id));
8873
8939
  setPullRequestReviewSubmitting(event);
8874
- const formattedComments = pendingComments.map(toPullRequestReviewComment);
8940
+ const formattedComments = pendingComments.map((comment) => toPullRequestReviewComment(comment));
8875
8941
  return (body ? interactive.onSubmitReview(event, formattedComments, body) : interactive.onSubmitReview(event, formattedComments)).then(() => {
8876
8942
  updateActiveReviewCommentDraft(null);
8877
8943
  setLocalReviewComments((current) => current.filter((comment) => !pendingIds.has(comment.id)));
@@ -9034,7 +9100,6 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
9034
9100
  gitIdentity,
9035
9101
  hunkNavigation: null,
9036
9102
  initialMarkdownPreviewSectionIds,
9037
- isPullRequest: snapshot.repository.source.type === "pull-request",
9038
9103
  isReadOnly: !canComment,
9039
9104
  itemVersionByKey,
9040
9105
  keymap,
@@ -9056,6 +9121,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
9056
9121
  searchQuery: "",
9057
9122
  showWhitespace: snapshot.preferences.showWhitespace,
9058
9123
  source: snapshot.repository.source,
9124
+ supportsReviewCommentActions: submitReviewComment != null,
9059
9125
  theme: snapshot.preferences.theme,
9060
9126
  viewed,
9061
9127
  wordWrap: snapshot.preferences.wordWrap
@@ -9114,8 +9180,10 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
9114
9180
  sourceDescriptionFooter
9115
9181
  });
9116
9182
  };
9117
- const sourceLabel = snapshot.repository.source.type === "working-tree" ? "" : ` · ${getSourceLabel(snapshot.repository.source)}`;
9118
- const rootLabel = `${compactPath(snapshot.repository.root)}${snapshot.branch ? ` (${snapshot.branch})` : ""}`;
9183
+ const sourceLabel = snapshot.repository.source.type === "working-tree" ? null : getSourceLabel(snapshot.repository.source);
9184
+ const rootLabel = repositoryUrl ? snapshot.repository.root : compactPath(snapshot.repository.root);
9185
+ const sourceExternalUrl = snapshot.repository.source.type === "pull-request" ? externalUrl ?? snapshot.repository.source.url : null;
9186
+ const repositoryLinkUrl = repositoryUrl ?? sourceExternalUrl;
9119
9187
  const walkthroughStatus = walkthroughRequestPending && interactive?.walkthroughStatus !== "ready" ? "generating" : interactive?.walkthroughStatus;
9120
9188
  const [walkthroughProgressRevision, setWalkthroughProgressRevision] = useState(0);
9121
9189
  const previousWalkthroughStatusRef = useRef(walkthroughStatus);
@@ -9131,57 +9199,120 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
9131
9199
  const requestWalkthrough = () => {
9132
9200
  startWalkthroughGeneration();
9133
9201
  };
9202
+ const reviewModes = [
9203
+ {
9204
+ icon: /* @__PURE__ */ jsx(PathIcon, {
9205
+ "aria-hidden": true,
9206
+ size: 14,
9207
+ weight: "bold"
9208
+ }),
9209
+ label: "Walkthrough",
9210
+ value: "walkthrough"
9211
+ },
9212
+ {
9213
+ icon: /* @__PURE__ */ jsx(TreeStructureIcon, {
9214
+ "aria-hidden": true,
9215
+ size: 14,
9216
+ weight: "bold"
9217
+ }),
9218
+ label: "Tree",
9219
+ value: "tree"
9220
+ },
9221
+ ...showCommentsTab ? [{
9222
+ ariaLabel: generalCommentCount > 0 ? `Comments (${generalCommentCount})` : "Comments",
9223
+ icon: /* @__PURE__ */ jsx(ChatCircleIcon, {
9224
+ "aria-hidden": true,
9225
+ size: 14,
9226
+ weight: "bold"
9227
+ }),
9228
+ indicator: generalCommentCount > 0 ? /* @__PURE__ */ jsx("span", {
9229
+ "aria-hidden": true,
9230
+ className: "review-mode-count",
9231
+ children: generalCommentCount
9232
+ }) : void 0,
9233
+ label: "Comments",
9234
+ title: generalCommentCount > 0 ? `${generalCommentCount} ${generalCommentCount === 1 ? "comment" : "comments"}` : "Comments",
9235
+ value: "comments"
9236
+ }] : []
9237
+ ];
9238
+ const topBarActions = onDeleteShare || settingsBar ? /* @__PURE__ */ jsxs(Fragment$1, { children: [onDeleteShare ? /* @__PURE__ */ jsx(Button, {
9239
+ action: deleteShare,
9240
+ "aria-label": "Delete shared walkthrough",
9241
+ pendingPlaceholder: "…",
9242
+ size: "icon",
9243
+ title: "Delete shared walkthrough",
9244
+ type: "button",
9245
+ variant: "destructive",
9246
+ children: /* @__PURE__ */ jsx(Trash2, {
9247
+ "aria-hidden": true,
9248
+ size: 16
9249
+ })
9250
+ }) : null, settingsBar ? /* @__PURE__ */ jsx("div", {
9251
+ className: "review-top-bar-settings",
9252
+ children: settingsBar
9253
+ }) : null] }) : void 0;
9134
9254
  return /* @__PURE__ */ jsxs("div", {
9135
- className: `app-shell share-shell${interactive ? " merge-request-shell" : ""}`,
9255
+ className: `app-shell share-shell${interactive ? " merge-request-shell" : ""}${sidebarCollapsed ? " sidebar-collapsed" : ""}`,
9136
9256
  "data-theme": shellTheme,
9137
- style: { gridTemplateColumns: `${sidebarWidth}px 0 minmax(0, 1fr)` },
9257
+ style: sidebarCollapsed ? void 0 : { gridTemplateColumns: `${sidebarWidth}px 0 minmax(0, 1fr)` },
9138
9258
  children: [
9259
+ /* @__PURE__ */ jsx(ReviewTopBar, {
9260
+ actions: topBarActions,
9261
+ context: /* @__PURE__ */ jsxs(Fragment$1, { children: [snapshot.branch ? /* @__PURE__ */ jsx("span", {
9262
+ className: "review-top-bar-branch",
9263
+ title: snapshot.branch,
9264
+ children: snapshot.branch
9265
+ }) : null, sourceLabel ? sourceExternalUrl ? /* @__PURE__ */ jsxs("a", {
9266
+ "aria-label": `Open ${sourceLabel} in ${providerLabel}`,
9267
+ className: "review-top-bar-source",
9268
+ href: sourceExternalUrl,
9269
+ rel: "noreferrer",
9270
+ target: "_blank",
9271
+ title: `Open ${sourceLabel} in ${providerLabel}`,
9272
+ children: [/* @__PURE__ */ jsx("span", { children: sourceLabel }), /* @__PURE__ */ jsx(ArrowSquareOutIcon, {
9273
+ "aria-hidden": true,
9274
+ size: 14,
9275
+ weight: "bold"
9276
+ })]
9277
+ }) : /* @__PURE__ */ jsx("span", {
9278
+ className: "review-top-bar-source",
9279
+ children: sourceLabel
9280
+ }) : null] }),
9281
+ leading: interactive ? /* @__PURE__ */ jsx("button", {
9282
+ "aria-label": "Back to Codiff",
9283
+ className: "merge-request-nav-button merge-request-home-button",
9284
+ onClick: interactive.onHome,
9285
+ title: "Back to Codiff",
9286
+ type: "button",
9287
+ children: /* @__PURE__ */ jsx("img", {
9288
+ alt: "",
9289
+ "aria-hidden": true,
9290
+ className: "merge-request-nav-icon",
9291
+ draggable: false,
9292
+ src: "/icon.png"
9293
+ })
9294
+ }) : void 0,
9295
+ mode: sidebarMode,
9296
+ modes: reviewModes,
9297
+ onModeChange: changeSidebarMode,
9298
+ onToggleSidebar: () => setSidebarCollapsed((current) => !current),
9299
+ repository: repositoryLinkUrl ? /* @__PURE__ */ jsx("a", {
9300
+ className: "review-top-bar-repository",
9301
+ href: repositoryLinkUrl,
9302
+ rel: "noreferrer",
9303
+ target: repositoryUrl ? void 0 : "_blank",
9304
+ children: rootLabel
9305
+ }) : /* @__PURE__ */ jsx("span", {
9306
+ className: "review-top-bar-repository",
9307
+ children: rootLabel
9308
+ }),
9309
+ repositoryTooltip: snapshot.repository.root,
9310
+ sidebarCollapsed,
9311
+ toggleTitle: `${sidebarCollapsed ? "Expand" : "Collapse"} sidebar`
9312
+ }),
9139
9313
  /* @__PURE__ */ jsxs("aside", {
9140
9314
  className: "squircle sidebar",
9141
9315
  children: [
9142
- /* @__PURE__ */ jsx("div", {
9143
- className: "sidebar-header",
9144
- children: /* @__PURE__ */ jsxs("div", {
9145
- className: "sidebar-path-row",
9146
- children: [
9147
- interactive ? /* @__PURE__ */ jsx("button", {
9148
- "aria-label": "Back to Codiff",
9149
- className: "merge-request-nav-button merge-request-home-button",
9150
- onClick: interactive.onHome,
9151
- title: "Back to Codiff",
9152
- type: "button",
9153
- children: /* @__PURE__ */ jsx("img", {
9154
- alt: "",
9155
- "aria-hidden": true,
9156
- className: "merge-request-nav-icon",
9157
- draggable: false,
9158
- src: "/icon.png"
9159
- })
9160
- }) : null,
9161
- /* @__PURE__ */ jsxs("div", {
9162
- className: "sidebar-path",
9163
- title: title ?? snapshot.repository.root,
9164
- children: [
9165
- title ? `${title} · ` : null,
9166
- rootLabel,
9167
- sourceLabel
9168
- ]
9169
- }),
9170
- externalUrl ? /* @__PURE__ */ jsx("a", {
9171
- "aria-label": `Open merge request in ${providerLabel}`,
9172
- className: "merge-request-nav-button",
9173
- href: externalUrl,
9174
- rel: "noreferrer",
9175
- target: "_blank",
9176
- title: `Open merge request in ${providerLabel}`,
9177
- children: /* @__PURE__ */ jsx(ExternalLink, {
9178
- "aria-hidden": true,
9179
- size: 16
9180
- })
9181
- }) : null
9182
- ]
9183
- })
9184
- }),
9185
9316
  /* @__PURE__ */ jsx("div", {
9186
9317
  className: "sidebar-search-row",
9187
9318
  children: /* @__PURE__ */ jsx("input", {
@@ -9194,40 +9325,6 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
9194
9325
  value: fileSearchQuery
9195
9326
  })
9196
9327
  }),
9197
- /* @__PURE__ */ jsxs("div", {
9198
- "aria-label": "Review order",
9199
- className: "sidebar-mode-toggle",
9200
- role: "tablist",
9201
- children: [
9202
- /* @__PURE__ */ jsx("button", {
9203
- "aria-selected": sidebarMode === "tree",
9204
- onClick: () => changeSidebarMode("tree"),
9205
- role: "tab",
9206
- type: "button",
9207
- children: "Tree"
9208
- }),
9209
- /* @__PURE__ */ jsx("button", {
9210
- "aria-selected": sidebarMode === "walkthrough",
9211
- onClick: () => changeSidebarMode("walkthrough"),
9212
- role: "tab",
9213
- type: "button",
9214
- children: "Walkthrough"
9215
- }),
9216
- commenting || interactive || generalCommentCount > 0 ? /* @__PURE__ */ jsxs("button", {
9217
- "aria-label": generalCommentCount > 0 ? `Comments (${generalCommentCount})` : "Comments",
9218
- "aria-selected": sidebarMode === "comments",
9219
- onClick: () => changeSidebarMode("comments"),
9220
- role: "tab",
9221
- title: generalCommentCount > 0 ? `${generalCommentCount} ${generalCommentCount === 1 ? "comment" : "comments"}` : "Comments",
9222
- type: "button",
9223
- children: [/* @__PURE__ */ jsx("span", { children: "Comments" }), generalCommentCount > 0 ? /* @__PURE__ */ jsx("span", {
9224
- "aria-hidden": true,
9225
- className: "sidebar-tab-count",
9226
- children: generalCommentCount
9227
- }) : null]
9228
- }) : null
9229
- ]
9230
- }),
9231
9328
  sidebarMode === "tree" ? /* @__PURE__ */ jsx(ReviewFileTree, {
9232
9329
  files: visibleFiles,
9233
9330
  onActivatePath: activateTreePath,
@@ -9255,13 +9352,13 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
9255
9352
  }), walkthroughStatusDescription ? /* @__PURE__ */ jsx("span", { children: walkthroughStatusDescription }) : null]
9256
9353
  })
9257
9354
  }),
9258
- settingsBar || showTotalLineCount ? /* @__PURE__ */ jsxs("div", {
9355
+ showTotalLineCount ? /* @__PURE__ */ jsx("div", {
9259
9356
  className: "sidebar-settings-bar",
9260
- children: [settingsBar, showTotalLineCount ? /* @__PURE__ */ jsx(DiffLineCountBadge, {
9357
+ children: /* @__PURE__ */ jsx(DiffLineCountBadge, {
9261
9358
  ariaLabelPrefix: "Total change",
9262
9359
  className: "sidebar-total-line-count sidebar-settings-line-count",
9263
9360
  lineCount: totalLineCount
9264
- }) : null]
9361
+ })
9265
9362
  }) : null
9266
9363
  ]
9267
9364
  }),
@@ -9340,7 +9437,7 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
9340
9437
  ]
9341
9438
  })
9342
9439
  }) : /* @__PURE__ */ jsx("div", {
9343
- className: "loading codex italic",
9440
+ className: "loading codex",
9344
9441
  children: /* @__PURE__ */ jsx(WalkthroughProgress, {
9345
9442
  phase: null,
9346
9443
  responseLabelIndex: 0,
@@ -9352,4 +9449,4 @@ function ReviewSurface({ commenting, externalUrl, gitIdentity = null, initialMod
9352
9449
  });
9353
9450
  }
9354
9451
  //#endregion
9355
- export { Button, PlanReviewSurface, ReadOnlyGeneralCommentCard, ReviewSurface, buttonVariants, getPlanCommentAffordancePosition, getPlanCommentColorIndex, getPlanCommentIdentityColorIndex, getSharedPlanDownloadContent };
9452
+ export { Button, PlanReviewSurface, ReadOnlyGeneralCommentCard, ReviewSurface, Thinking, buttonVariants, getPlanCommentAffordancePosition, getPlanCommentColorIndex, getPlanCommentIdentityColorIndex, getSharedPlanDownloadContent };