@alivault/pico 0.10.0 → 0.10.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.
@@ -1,8 +1,8 @@
1
1
  import { i as __toESM } from "../_runtime.mjs";
2
2
  import { $ as require_react, A as TabsTab, B as Input, C as MenuPositioner, D as MenuGroupLabel, E as MenuItem, F as DialogPopup, H as useRender, I as DialogDescription$1, L as DialogClose, M as DialogTitle$1, N as DialogRoot, O as MenuGroup, P as DialogPortal$1, Q as require_jsx_runtime, R as DialogBackdrop, S as MenuRoot, T as MenuPopup, V as Button, Y as mergeProps, _ as AccordionItem$1, a as ContextMenuRoot, b as MenuTrigger, c as CollapsibleRoot, d as PopoverPortal, f as PopoverTrigger$1, g as AccordionHeader, h as AccordionTrigger$1, i as ContextMenuTrigger$1, j as TabsRoot, k as TabsList$1, l as PopoverPopup, m as AccordionPanel, n as ToggleGroup, o as CollapsiblePanel, p as PopoverRoot, r as Toggle, s as CollapsibleTrigger$1, t as AlertDialogRoot, u as PopoverPositioner, v as AccordionRoot, w as MenuPortal, x as MenuSubmenuRoot, y as MenuSubmenuTrigger, z as Separator } from "../_libs/@base-ui/react+[...].mjs";
3
3
  import { t as cva } from "../_libs/class-variance-authority+clsx.mjs";
4
- import { A as Ellipsis, B as ChevronDown, C as ImagePlus, D as FolderPlus, E as Folder, H as ArrowUp, I as ChevronsUpDown, K as ArrowLeft, L as ChevronsDownUp, M as Download, N as Copy, O as FileDiff, P as Command, T as GitCommitHorizontal, U as ArrowUpToLine, V as Check, W as ArrowUpRight, _ as OctagonX, a as TriangleAlert, b as ListStart, c as SquarePen, d as Plus, f as PanelRight, h as PanelLeftClose, i as Undo2, j as EllipsisVertical, k as ExternalLink, l as Settings2, m as PanelLeftOpen, n as WandSparkles, o as Tag, p as PanelLeft, q as ArrowDown, r as Upload, s as Square, t as X, u as Search, v as Minimize2, w as GitBranch, x as ListEnd, y as Maximize2, z as ChevronRight } from "../_libs/lucide-react.mjs";
5
- import { $ as readStoredAutoScrollEnabled, C as THEME_COLOR_MODES, D as TitleTooltip, E as THEME_STORAGE_KEY, F as buildItemsFromSync, I as cn, J as normalizeStoredDirectoryList, K as normalizePromptImage, M as VIEWER_CONTEXT_STORAGE_KEY, N as appliedThemeClass, O as Tooltip$1, Q as promptDraftKey, R as createContextId, S as Spinner, T as THEME_FAMILIES, U as flattenTree, W as getSessionTitle, X as normalizeThemeFamily, Y as normalizeThemeColorMode, Z as previewUrlForImage, _ as RIGHT_SIDEBAR_WIDTHS_STORAGE_KEY, _t as sessionListEntryKey, a as COLLAPSED_DIRECTORIES_STORAGE_KEY, at as readStoredPromptDraft, b as SESSION_DONE_SOUND_ENABLED_STORAGE_KEY, bt as thinkingSummaryText, c as Kbd, ct as readStoredSessionDoneDesktopNotificationsEnabled, d as RECENT_DIRECTORIES_STORAGE_KEY, dt as readStoredTheme, et as readStoredCenterMessages, f as RIGHT_SIDEBAR_ACTIVE_TAB_STORAGE_KEY, ft as readStoredThemeColorMode, g as RIGHT_SIDEBAR_OPEN_STORAGE_KEY, gt as sameContextUsage, h as RIGHT_SIDEBAR_HISTORY_TAB_STORAGE_KEY, ht as safeLocalStorageSetItem, i as CENTER_MESSAGES_STORAGE_KEY, it as readStoredPinnedSessionKeys, j as TooltipTrigger, k as TooltipContent, l as KeyboardShortcutContent, lt as readStoredSessionDoneSoundEnabled, m as RIGHT_SIDEBAR_HISTORY_HEIGHT_STORAGE_KEY, mt as safeLocalStorageGetItem, n as APPLIED_THEME_STORAGE_KEY, nt as readStoredDraftDirectory, o as DRAFT_DIRECTORY_STORAGE_KEY, ot as readStoredRecentDirectories, p as RIGHT_SIDEBAR_FILE_TREE_WIDTH_STORAGE_KEY, pt as rememberStoredPromptDraft, q as normalizeSessionSelectionKeys, r as AUTO_SCROLL_ENABLED_STORAGE_KEY, rt as readStoredHideToolBlocks, s as HIDE_TOOL_BLOCKS_STORAGE_KEY, st as readStoredRightSidebarOpen, tt as readStoredCollapsedDirectories, u as PINNED_SESSIONS_STORAGE_KEY, ut as readStoredSidebarDirectories, v as Route, vt as themeColorModeLabel, w as THEME_COLOR_MODE_STORAGE_KEY, x as SIDEBAR_DIRECTORIES_STORAGE_KEY, y as SESSION_DONE_DESKTOP_NOTIFICATIONS_ENABLED_STORAGE_KEY, yt as themeFamilyLabel, z as createInitialSessionState } from "./routes-D6yf6dMU.mjs";
4
+ import { A as ExternalLink, B as ChevronRight, C as ImagePlus, D as Folder, E as GitCommitHorizontal, F as Command, G as ArrowUpRight, H as Check, J as ArrowDown, L as ChevronsUpDown, M as EllipsisVertical, N as Download, O as FolderPlus, P as Copy, R as ChevronsDownUp, T as GitBranch, U as ArrowUp, V as ChevronDown, W as ArrowUpToLine, _ as OctagonX, a as TriangleAlert, b as ListStart, c as SquarePen, d as Plus, f as PanelRight, h as PanelLeftClose, i as Undo2, j as Ellipsis, k as FileDiff, l as Settings2, m as PanelLeftOpen, n as WandSparkles, o as Tag, p as PanelLeft, q as ArrowLeft, r as Upload, s as Square, t as X, u as Search, v as Minimize2, w as GitPullRequestArrow, x as ListEnd, y as Maximize2 } from "../_libs/lucide-react.mjs";
5
+ import { $ as promptDraftKeyMatchesOwner, C as THEME_COLOR_MODES, D as TitleTooltip, E as THEME_STORAGE_KEY, F as buildItemsFromSync, I as cn, J as normalizeStoredDirectoryList, K as normalizePromptImage, M as VIEWER_CONTEXT_STORAGE_KEY, N as appliedThemeClass, O as Tooltip$1, Q as promptDraftKey, R as createContextId, S as Spinner, T as THEME_FAMILIES, U as flattenTree, W as getSessionTitle, X as normalizeThemeFamily, Y as normalizeThemeColorMode, Z as previewUrlForImage, _ as RIGHT_SIDEBAR_WIDTHS_STORAGE_KEY, _t as sameContextUsage, a as COLLAPSED_DIRECTORIES_STORAGE_KEY, at as readStoredPinnedSessionKeys, b as SESSION_DONE_SOUND_ENABLED_STORAGE_KEY, bt as themeFamilyLabel, c as Kbd, ct as readStoredRightSidebarOpen, d as RECENT_DIRECTORIES_STORAGE_KEY, dt as readStoredSidebarDirectories, et as readStoredAutoScrollEnabled, f as RIGHT_SIDEBAR_ACTIVE_TAB_STORAGE_KEY, ft as readStoredTheme, g as RIGHT_SIDEBAR_OPEN_STORAGE_KEY, gt as safeLocalStorageSetItem, h as RIGHT_SIDEBAR_HISTORY_TAB_STORAGE_KEY, ht as safeLocalStorageGetItem, i as CENTER_MESSAGES_STORAGE_KEY, it as readStoredHideToolBlocks, j as TooltipTrigger, k as TooltipContent, l as KeyboardShortcutContent, lt as readStoredSessionDoneDesktopNotificationsEnabled, m as RIGHT_SIDEBAR_HISTORY_HEIGHT_STORAGE_KEY, mt as rememberStoredPromptDraft, n as APPLIED_THEME_STORAGE_KEY, nt as readStoredCollapsedDirectories, o as DRAFT_DIRECTORY_STORAGE_KEY, ot as readStoredPromptDraft, p as RIGHT_SIDEBAR_FILE_TREE_WIDTH_STORAGE_KEY, pt as readStoredThemeColorMode, q as normalizeSessionSelectionKeys, r as AUTO_SCROLL_ENABLED_STORAGE_KEY, rt as readStoredDraftDirectory, s as HIDE_TOOL_BLOCKS_STORAGE_KEY, st as readStoredRecentDirectories, tt as readStoredCenterMessages, u as PINNED_SESSIONS_STORAGE_KEY, ut as readStoredSessionDoneSoundEnabled, v as Route, vt as sessionListEntryKey, w as THEME_COLOR_MODE_STORAGE_KEY, x as SIDEBAR_DIRECTORIES_STORAGE_KEY, xt as thinkingSummaryText, y as SESSION_DONE_DESKTOP_NOTIFICATIONS_ENABLED_STORAGE_KEY, yt as themeColorModeLabel, z as createInitialSessionState } from "./routes-DbGwM-bj.mjs";
6
6
  import { i as useQuery, n as useIsMutating, o as useQueryClient, r as useIsFetching, t as useMutation } from "../_libs/tanstack__react-query.mjs";
7
7
  import { t as useHotkeys } from "../_libs/tanstack__react-hotkeys.mjs";
8
8
  import { a as matchesKeyboardEvent, i as batch, o as formatForDisplay, r as createStore } from "../_libs/@tanstack/hotkeys+[...].mjs";
@@ -22,7 +22,7 @@ import { t as remarkGfm } from "../_libs/remark-gfm.mjs";
22
22
  import { a as getBuiltInSpriteSheet, i as getBuiltInFileIconColor, n as FileTree, r as createFileTreeIconResolver, t as useFileTree } from "../_libs/@pierre/trees+[...].mjs";
23
23
  import { a as closestCenter, c as useDraggable, d as useSensors, f as CSS$1, i as PointerSensor, n as DragOverlay, t as DndContext, u as useSensor } from "../_libs/@dnd-kit/core+[...].mjs";
24
24
  import { a as verticalListSortingStrategy, i as useSortable, n as arrayMove, r as horizontalListSortingStrategy, t as SortableContext } from "../_libs/dnd-kit__sortable.mjs";
25
- //#region node_modules/.nitro/vite/services/ssr/assets/routes-CSvcOSyF.js
25
+ //#region node_modules/.nitro/vite/services/ssr/assets/routes-NB2wmfpS.js
26
26
  var import_react = /* @__PURE__ */ __toESM(require_react());
27
27
  var import_jsx_runtime = require_jsx_runtime();
28
28
  var MOBILE_BREAKPOINT = 768;
@@ -5286,8 +5286,8 @@ function AppShellTreeDialogController({ ref, openStateRef, viewerContextId, sess
5286
5286
  })
5287
5287
  });
5288
5288
  },
5289
- onSuccess: async () => {
5290
- await queryClient.invalidateQueries({
5289
+ onSuccess: () => {
5290
+ queryClient.invalidateQueries({
5291
5291
  queryKey,
5292
5292
  exact: true,
5293
5293
  refetchType: "active"
@@ -5318,6 +5318,7 @@ function AppShellTreeDialogController({ ref, openStateRef, viewerContextId, sess
5318
5318
  };
5319
5319
  const navigateTreeNode = async (targetId, options) => {
5320
5320
  if (!viewerContextId) return;
5321
+ setOpenState(false);
5321
5322
  try {
5322
5323
  const response = await navigateTreeNodeMutation.mutateAsync({
5323
5324
  targetId,
@@ -5332,7 +5333,6 @@ function AppShellTreeDialogController({ ref, openStateRef, viewerContextId, sess
5332
5333
  toast.info("Tree navigation cancelled");
5333
5334
  return;
5334
5335
  }
5335
- setOpenState(false);
5336
5336
  toast.success(options?.summarize ? "Continued from summarized branch" : "Moved session tree cursor");
5337
5337
  } catch (error) {
5338
5338
  toast.error(error instanceof Error ? error.message : "Failed to navigate tree");
@@ -9409,6 +9409,24 @@ function formatComposerSkillName(skillName = "") {
9409
9409
  return [`${part.charAt(0).toUpperCase()}${part.slice(1)}`];
9410
9410
  }).join(" ");
9411
9411
  }
9412
+ function formatComposerDiffLineCommentReference(comment) {
9413
+ const sideLabel = comment.side === "deletions" ? "old" : "new";
9414
+ return `${comment.path}:L${comment.lineNumber} (${sideLabel})`;
9415
+ }
9416
+ function formatComposerDiffLineCommentForPrompt(comment) {
9417
+ const sourceLabel = comment.commit === "working-tree" ? "working tree changes" : comment.shortHash;
9418
+ return `- ${formatComposerDiffLineCommentReference(comment)} in ${sourceLabel}: ${comment.text}`;
9419
+ }
9420
+ function buildComposerPromptMessage({ diffLineComments, skillName, text }) {
9421
+ const baseMessage = serializeComposerDraft({
9422
+ text,
9423
+ skillName
9424
+ }).trim();
9425
+ const comments = diffLineComments?.filter((comment) => comment.text.trim().length > 0);
9426
+ if (!comments || comments.length === 0) return baseMessage;
9427
+ const commentBlock = `Diff comments:\n${comments.map(formatComposerDiffLineCommentForPrompt).join("\n")}`;
9428
+ return baseMessage ? `${baseMessage}\n\n${commentBlock}` : commentBlock;
9429
+ }
9412
9430
  function parseSlashCommandInput(value = "") {
9413
9431
  const rawValue = typeof value === "string" ? value : "";
9414
9432
  const trimmedStart = rawValue.trimStart();
@@ -12074,7 +12092,7 @@ function getClipboardImageFiles(data) {
12074
12092
  if (itemFiles.length > 0) return itemFiles;
12075
12093
  return Array.from(data.files).filter((file) => file.type.startsWith("image/"));
12076
12094
  }
12077
- function ComposerPanel({ activeSessionId, currentPendingMessages, composerImages, composerText, composerSkill, composerSyncNonce, centerMessages, contextUsageStore, displaySettingsStore, sessionStore, isSubmitting, isStreaming, awaitingFirstTurn, disabled = false, flush = false, topContent, viewerContextId, fileInputRef, onComposerTextChange, onPickImages, onRemoveComposerImage, onSubmitPrompt, onAbort, onEditPendingMessage, onRemovePendingMessage, onReorderPending, onRunBuiltinSlashCommand, onSelectModel, onSelectThinkingLevel, requestPathCompletions, requestFileCompletions, ref }) {
12095
+ function ComposerPanel({ activeSessionId, currentPendingMessages, composerDiffLineComments, composerImages, composerText, composerSkill, composerSyncNonce, centerMessages, contextUsageStore, displaySettingsStore, sessionStore, isSubmitting, isStreaming, awaitingFirstTurn, disabled = false, flush = false, topContent, viewerContextId, fileInputRef, onComposerTextChange, onPickImages, onRemoveComposerDiffLineComment, onRemoveComposerImage, onSubmitPrompt, onAbort, onEditPendingMessage, onRemovePendingMessage, onReorderPending, onRunBuiltinSlashCommand, onSelectModel, onSelectThinkingLevel, requestPathCompletions, requestFileCompletions, ref }) {
12078
12096
  const promptRef = import_react.useRef(null);
12079
12097
  const [pickerState, dispatchPickerState] = import_react.useReducer(composerPickerReducer, INITIAL_COMPOSER_PICKER_STATE);
12080
12098
  const { modelPickerOpen, thinkingPickerOpen, modelQuery } = pickerState;
@@ -12108,6 +12126,7 @@ function ComposerPanel({ activeSessionId, currentPendingMessages, composerImages
12108
12126
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12109
12127
  className: "overflow-visible rounded-[18px] border bg-card",
12110
12128
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ComposerPromptEditor, {
12129
+ composerDiffLineComments,
12111
12130
  composerImages,
12112
12131
  composerText,
12113
12132
  composerSkill,
@@ -12122,6 +12141,7 @@ function ComposerPanel({ activeSessionId, currentPendingMessages, composerImages
12122
12141
  sessionStore,
12123
12142
  onComposerTextChange,
12124
12143
  onPickImages,
12144
+ onRemoveComposerDiffLineComment,
12125
12145
  onRemoveComposerImage,
12126
12146
  onSubmitPrompt,
12127
12147
  onAbort,
@@ -12195,7 +12215,7 @@ var ComposerAttachments = import_react.memo(function ComposerAttachments({ image
12195
12215
  }, promptImageKey(image)))
12196
12216
  });
12197
12217
  });
12198
- var ComposerPromptEditor = import_react.memo(function ComposerPromptEditor({ composerImages, composerText, composerSkill, composerSyncNonce, isSubmitting, isStreaming, awaitingFirstTurn, disabled = false, fileInputRef, promptRef, displaySettingsStore, sessionStore, onComposerTextChange, onPickImages, onRemoveComposerImage, onSubmitPrompt, onAbort, onRunBuiltinSlashCommand, requestPathCompletions, requestFileCompletions }) {
12218
+ var ComposerPromptEditor = import_react.memo(function ComposerPromptEditor({ composerDiffLineComments, composerImages, composerText, composerSkill, composerSyncNonce, isSubmitting, isStreaming, awaitingFirstTurn, disabled = false, fileInputRef, promptRef, displaySettingsStore, sessionStore, onComposerTextChange, onPickImages, onRemoveComposerDiffLineComment, onRemoveComposerImage, onSubmitPrompt, onAbort, onRunBuiltinSlashCommand, requestPathCompletions, requestFileCompletions }) {
12199
12219
  const slashCommands = useComposerSlashCommands({
12200
12220
  displaySettingsStore,
12201
12221
  sessionStore
@@ -12282,8 +12302,9 @@ var ComposerPromptEditor = import_react.memo(function ComposerPromptEditor({ com
12282
12302
  dismissMenus,
12283
12303
  promptRef
12284
12304
  ]);
12285
- const hasSubmittableContent = hasDraftText || composerImages.length > 0;
12286
- const acceptFollowUps = isStreaming || awaitingFirstTurn;
12305
+ const hasSubmittableContent = hasDraftText || composerDiffLineComments.length > 0 || composerImages.length > 0;
12306
+ const responseInFlight = isStreaming || awaitingFirstTurn || isSubmitting;
12307
+ const acceptFollowUps = responseInFlight;
12287
12308
  const setAssistPointerSelectionSuppressedValue = (next) => {
12288
12309
  assistPointerSelectionSuppressedRef.current = next;
12289
12310
  setAssistPointerSelectionSuppressed(next);
@@ -12358,6 +12379,11 @@ var ComposerPromptEditor = import_react.memo(function ComposerPromptEditor({ com
12358
12379
  applyDraft("", void 0, { immediate: true });
12359
12380
  return;
12360
12381
  }
12382
+ if (event.key === "Backspace" && !draftText && !currentDraftSkill && composerDiffLineComments.length > 0) {
12383
+ const lastComment = composerDiffLineComments.at(-1);
12384
+ if (lastComment) onRemoveComposerDiffLineComment(lastComment.id);
12385
+ return;
12386
+ }
12361
12387
  if (visibleCompletion && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
12362
12388
  event.preventDefault();
12363
12389
  setAssistPointerSelectionSuppressedValue(true);
@@ -12416,7 +12442,7 @@ var ComposerPromptEditor = import_react.memo(function ComposerPromptEditor({ com
12416
12442
  dismissMenus();
12417
12443
  return;
12418
12444
  }
12419
- if (isStreaming && !event.repeat) {
12445
+ if (responseInFlight && !event.repeat) {
12420
12446
  event.preventDefault();
12421
12447
  event.stopPropagation();
12422
12448
  onAbort();
@@ -12463,39 +12489,62 @@ var ComposerPromptEditor = import_react.memo(function ComposerPromptEditor({ com
12463
12489
  className: "min-w-0",
12464
12490
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
12465
12491
  className: "flex min-w-0 flex-wrap items-start gap-2",
12466
- children: [draftSkill ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
12467
- className: "inline-flex h-7 max-w-[45%] shrink-0 items-center gap-0 overflow-hidden rounded-full bg-primary/10 pr-0.5 pl-2 text-sm font-medium text-primary",
12468
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
12469
- className: "truncate",
12470
- children: ["Skill: ", formatComposerSkillName(draftSkill)]
12471
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
12472
- size: "icon-sm",
12473
- variant: "ghost",
12474
- className: "ml-1 rounded-full text-primary hover:bg-primary/10 hover:text-primary",
12475
- "aria-label": `Remove skill ${formatComposerSkillName(draftSkill)}`,
12492
+ children: [
12493
+ draftSkill ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
12494
+ className: "inline-flex h-7 max-w-[45%] shrink-0 items-center gap-0 overflow-hidden rounded-full bg-primary/10 pr-0.5 pl-2 text-sm font-medium text-primary",
12495
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
12496
+ className: "truncate",
12497
+ children: ["Skill: ", formatComposerSkillName(draftSkill)]
12498
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
12499
+ size: "icon-sm",
12500
+ variant: "ghost",
12501
+ className: "ml-1 rounded-full text-primary hover:bg-primary/10 hover:text-primary",
12502
+ "aria-label": `Remove skill ${formatComposerSkillName(draftSkill)}`,
12503
+ disabled,
12504
+ onClick: () => applyDraft(draftTextRef.current, void 0, { immediate: true }),
12505
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "size-3.5" })
12506
+ })]
12507
+ }) : null,
12508
+ composerDiffLineComments.map((comment) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
12509
+ title: `${formatComposerDiffLineCommentReference(comment)}: ${comment.text}`,
12510
+ className: "inline-flex h-7 max-w-[45%] shrink-0 items-center gap-1 overflow-hidden rounded-full bg-amber-500/10 pr-0.5 pl-2 text-sm font-medium text-amber-700 dark:text-amber-300",
12511
+ children: [
12512
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitPullRequestArrow, { className: "size-3.5 shrink-0" }),
12513
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
12514
+ className: "truncate",
12515
+ children: ["Diff: ", formatComposerDiffLineCommentReference(comment)]
12516
+ }),
12517
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
12518
+ size: "icon-sm",
12519
+ variant: "ghost",
12520
+ className: "ml-1 rounded-full text-amber-700 hover:bg-amber-500/10 hover:text-amber-700 dark:text-amber-300 dark:hover:text-amber-300",
12521
+ "aria-label": `Remove diff comment for ${formatComposerDiffLineCommentReference(comment)}`,
12522
+ disabled,
12523
+ onClick: () => onRemoveComposerDiffLineComment(comment.id),
12524
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "size-3.5" })
12525
+ })
12526
+ ]
12527
+ }, comment.id)),
12528
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Textarea, {
12529
+ ref: promptRef,
12530
+ name: "prompt",
12531
+ rows: 1,
12532
+ autoComplete: "off",
12533
+ "data-1p-ignore": "true",
12534
+ "data-form-type": "other",
12535
+ "data-lpignore": "true",
12536
+ defaultValue: composerText,
12537
+ onChange: handleTextChange,
12538
+ onClick: syncSelection,
12539
+ onKeyUp: syncSelection,
12540
+ onSelect: syncSelection,
12541
+ onKeyDown: handleKeyDown,
12542
+ onPaste: handlePaste,
12476
12543
  disabled,
12477
- onClick: () => applyDraft(draftTextRef.current, void 0, { immediate: true }),
12478
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "size-3.5" })
12479
- })]
12480
- }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Textarea, {
12481
- ref: promptRef,
12482
- name: "prompt",
12483
- rows: 1,
12484
- autoComplete: "off",
12485
- "data-1p-ignore": "true",
12486
- "data-form-type": "other",
12487
- "data-lpignore": "true",
12488
- defaultValue: composerText,
12489
- onChange: handleTextChange,
12490
- onClick: syncSelection,
12491
- onKeyUp: syncSelection,
12492
- onSelect: syncSelection,
12493
- onKeyDown: handleKeyDown,
12494
- onPaste: handlePaste,
12495
- disabled,
12496
- placeholder: acceptFollowUps ? "Add a message to the queue..." : draftSkill ? `Ask with ${formatComposerSkillName(draftSkill)}…` : "Ask anything…",
12497
- className: "max-h-[min(40dvh,18rem)] min-h-[22px] max-w-full min-w-0 flex-1 basis-[min(240px,100%)] resize-none overflow-y-auto rounded-none border-0 bg-transparent p-0 text-base shadow-none ring-0 focus-visible:border-transparent focus-visible:ring-0 disabled:cursor-text disabled:bg-transparent disabled:opacity-60 md:text-sm dark:bg-transparent dark:disabled:bg-transparent"
12498
- })]
12544
+ placeholder: acceptFollowUps ? "Add a message to the queue..." : draftSkill ? `Ask with ${formatComposerSkillName(draftSkill)}…` : "Ask anything…",
12545
+ className: "max-h-[min(40dvh,18rem)] min-h-[22px] max-w-full min-w-0 flex-1 basis-[min(240px,100%)] resize-none overflow-y-auto rounded-none border-0 bg-transparent p-0 text-base shadow-none ring-0 focus-visible:border-transparent focus-visible:ring-0 disabled:cursor-text disabled:bg-transparent disabled:opacity-60 md:text-sm dark:bg-transparent dark:disabled:bg-transparent"
12546
+ })
12547
+ ]
12499
12548
  })
12500
12549
  })]
12501
12550
  }),
@@ -12550,7 +12599,7 @@ var ComposerPromptEditor = import_react.memo(function ComposerPromptEditor({ com
12550
12599
  onClick: () => runPrimaryComposerAction("steer"),
12551
12600
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ListStart, { "data-icon": "inline-start" }), "Steer"]
12552
12601
  }),
12553
- isStreaming ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
12602
+ responseInFlight ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TitleTooltip, {
12554
12603
  title: "Abort",
12555
12604
  kbd: "Esc",
12556
12605
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
@@ -12929,6 +12978,7 @@ var AppShellComposerController = import_react.memo(function AppShellComposerCont
12929
12978
  ref: composerPanelRef,
12930
12979
  activeSessionId: snapshot.activeSessionId,
12931
12980
  currentPendingMessages: snapshot.currentPendingMessages,
12981
+ composerDiffLineComments: snapshot.composerDiffLineComments,
12932
12982
  composerImages: snapshot.composerImages,
12933
12983
  composerText: snapshot.composerText,
12934
12984
  composerSkill: snapshot.composerSkill,
@@ -12947,6 +12997,7 @@ var AppShellComposerController = import_react.memo(function AppShellComposerCont
12947
12997
  fileInputRef,
12948
12998
  onComposerTextChange,
12949
12999
  onPickImages,
13000
+ onRemoveComposerDiffLineComment: actionsRef.current.removeDiffLineComment,
12950
13001
  onRemoveComposerImage,
12951
13002
  onSubmitPrompt,
12952
13003
  onAbort,
@@ -13973,9 +14024,11 @@ function AppShellConversationFrame({ autoScrollEnabled, children, conversationIt
13973
14024
  }
13974
14025
  function AppShellWorkingIndicatorLabel({ fallbackLabel, hiddenThinkingPreviewStore, useHiddenThinkingPreview }) {
13975
14026
  const hiddenThinkingPreview = useSelector(hiddenThinkingPreviewStore);
14027
+ const visibleLabel = useHiddenThinkingPreview && hiddenThinkingPreview ? hiddenThinkingPreview : fallbackLabel;
14028
+ if (!visibleLabel.trim()) return null;
13976
14029
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
13977
14030
  className: "font-medium text-foreground",
13978
- children: useHiddenThinkingPreview && hiddenThinkingPreview ? hiddenThinkingPreview : fallbackLabel
14031
+ children: visibleLabel
13979
14032
  });
13980
14033
  }
13981
14034
  function AppShellMessagesWorkingIndicator({ hiddenThinkingPreviewStore, onCancel, state, useHiddenThinkingPreview }) {
@@ -14113,7 +14166,7 @@ function AppShellConversationEmptyState({ awaitingFirstTurn, conversationItemsSt
14113
14166
  const conversationLoadingLabel = isSessionViewLoading ? "Loading session…" : displayedWorkingState && !displayedWorkingState.done ? displayedWorkingState.label : "Loading…";
14114
14167
  if (showConversationLoadingState) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
14115
14168
  className: "flex flex-1 flex-col items-center justify-center gap-3 py-10 text-sm text-muted-foreground",
14116
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: conversationLoadingLabel })]
14169
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}), conversationLoadingLabel ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: conversationLoadingLabel }) : null]
14117
14170
  });
14118
14171
  if (hasMessages) return null;
14119
14172
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Empty, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(EmptyHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyTitle, { children: draft ? "New session" : "Start a new conversation" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EmptyDescription, { children: draft ? void 0 : "This is the native Pico session view backed by the new TypeScript runtime." })] }), draft ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(EmptyContent, {
@@ -16327,7 +16380,7 @@ var GIT_GRAPH_LANE_COLORS = [
16327
16380
  ];
16328
16381
  var GIT_GRAPH_NULL_VERTEX_ID = -1;
16329
16382
  var GIT_GRAPH_ROW_HEIGHT = 32;
16330
- var GIT_GRAPH_COMMIT_DETAILS_HEIGHT = 300;
16383
+ var GIT_GRAPH_COMMIT_DETAILS_HEIGHT = 200;
16331
16384
  var GIT_GRAPH_LANE_WIDTH = 14;
16332
16385
  var GIT_GRAPH_OFFSET_X = 12;
16333
16386
  var initialGitCommitRowState = {
@@ -17361,7 +17414,7 @@ function useGitCommitRowView({ viewerContextId, cwd, graphWidth, line, value, on
17361
17414
  }, contextMenuKey)
17362
17415
  }),
17363
17416
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AccordionContent, {
17364
- className: "relative h-[300px] border-b border-border/70 bg-muted/50 p-0",
17417
+ className: "relative h-[200px] border-b border-border/70 bg-muted/50 p-0",
17365
17418
  children: [
17366
17419
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
17367
17420
  ref: commitBodyScrollRef,
@@ -17767,6 +17820,124 @@ function GitFileDiff({ file }) {
17767
17820
  }) : null]
17768
17821
  });
17769
17822
  }
17823
+ function reviewDiffCommentSideLabel(side) {
17824
+ return side === "deletions" ? "old" : "new";
17825
+ }
17826
+ function reviewDiffCommentLineFromRange(range) {
17827
+ return Math.min(range.start, range.end);
17828
+ }
17829
+ function reviewDiffCommentSideFromRange(range) {
17830
+ return range.side || range.endSide || "additions";
17831
+ }
17832
+ function reviewDiffCommentPath(file, side) {
17833
+ return side === "deletions" ? file.previousPath || file.path : file.path;
17834
+ }
17835
+ function reviewDiffCommentTabKey(file) {
17836
+ return `changes:${reviewFileValue(file)}`;
17837
+ }
17838
+ function reviewDiffLineCommentAnnotations({ comments, pendingComment }) {
17839
+ const annotations = comments.map((comment) => ({
17840
+ lineNumber: comment.lineNumber,
17841
+ metadata: comment,
17842
+ side: comment.side
17843
+ }));
17844
+ if (pendingComment) annotations.push({
17845
+ lineNumber: pendingComment.lineNumber,
17846
+ metadata: pendingComment,
17847
+ side: pendingComment.side
17848
+ });
17849
+ return annotations;
17850
+ }
17851
+ function ReviewDiffPendingLineComment({ comment }) {
17852
+ const [text, setText] = import_react.useState("");
17853
+ const textareaRef = import_react.useRef(null);
17854
+ import_react.useEffect(() => {
17855
+ textareaRef.current?.focus();
17856
+ }, []);
17857
+ const submit = () => {
17858
+ const normalizedText = text.trim();
17859
+ if (!normalizedText) return;
17860
+ comment.onSubmit(normalizedText);
17861
+ };
17862
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", {
17863
+ className: "m-2 max-w-[min(38rem,calc(100%-1rem))] rounded-lg border border-border/70 bg-background p-2 shadow-sm",
17864
+ onPointerDown: (event) => event.stopPropagation(),
17865
+ onSubmit: (event) => {
17866
+ event.preventDefault();
17867
+ submit();
17868
+ },
17869
+ children: [
17870
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
17871
+ className: "mb-2 flex min-w-0 items-center justify-between gap-2 text-xs text-muted-foreground",
17872
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
17873
+ className: "min-w-0 truncate",
17874
+ children: [
17875
+ "Comment on ",
17876
+ comment.path,
17877
+ ":L",
17878
+ comment.lineNumber,
17879
+ " (",
17880
+ reviewDiffCommentSideLabel(comment.side),
17881
+ ")"
17882
+ ]
17883
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
17884
+ type: "button",
17885
+ variant: "ghost",
17886
+ size: "icon-sm",
17887
+ "aria-label": "Cancel line comment",
17888
+ onClick: comment.onCancel,
17889
+ children: "×"
17890
+ })]
17891
+ }),
17892
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Textarea, {
17893
+ ref: textareaRef,
17894
+ value: text,
17895
+ rows: 2,
17896
+ placeholder: "Add a note for the prompt…",
17897
+ className: "min-h-16 resize-none text-sm",
17898
+ onChange: (event) => setText(event.target.value),
17899
+ onKeyDown: (event) => {
17900
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
17901
+ event.preventDefault();
17902
+ submit();
17903
+ }
17904
+ if (event.key === "Escape") {
17905
+ event.preventDefault();
17906
+ comment.onCancel();
17907
+ }
17908
+ }
17909
+ }),
17910
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
17911
+ className: "mt-2 flex justify-end gap-2",
17912
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
17913
+ type: "button",
17914
+ variant: "ghost",
17915
+ onClick: comment.onCancel,
17916
+ children: "Cancel"
17917
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button$1, {
17918
+ type: "submit",
17919
+ disabled: !text.trim(),
17920
+ children: "Add to prompt"
17921
+ })]
17922
+ })
17923
+ ]
17924
+ });
17925
+ }
17926
+ function ReviewDiffLineAnnotation({ annotation }) {
17927
+ const comment = annotation.metadata;
17928
+ if (!comment) return null;
17929
+ if (comment.kind === "pending") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReviewDiffPendingLineComment, { comment });
17930
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
17931
+ className: "my-1 ml-2 inline-flex max-w-[min(32rem,90%)] items-center gap-1 rounded-full border border-amber-500/20 bg-amber-500/10 px-2 py-1 text-[11px] text-amber-800 dark:text-amber-200",
17932
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
17933
+ className: "shrink-0",
17934
+ children: "💬"
17935
+ }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
17936
+ className: "truncate",
17937
+ children: comment.text
17938
+ })]
17939
+ });
17940
+ }
17770
17941
  var GIT_HISTORY_PANEL_MIN_HEIGHT = 160;
17771
17942
  var GIT_HISTORY_PANEL_MAX_STORED_HEIGHT = 1600;
17772
17943
  function clampStoredGitHistoryPanelHeight(height) {
@@ -17866,7 +18037,7 @@ function gitFileShouldPreviewPatch(file) {
17866
18037
  function FileReviewContent(props) {
17867
18038
  return useFileReviewContentView(props);
17868
18039
  }
17869
- function useFileReviewContentView({ viewerContextId, cwd, active, onMaximizeHistory, onOpenCommitDiff, onOpenFile, showEmbeddedHistory = true }) {
18040
+ function useFileReviewContentView({ viewerContextId, cwd, active, diffLineComments = [], onAddDiffLineComment, onMaximizeHistory, onOpenCommitDiff, onOpenFile, showEmbeddedHistory = true }) {
17870
18041
  const normalizedCwd = normalizeCwd(cwd);
17871
18042
  const isMobile = useIsMobile();
17872
18043
  const [state, dispatch] = import_react.useReducer(fileReviewReducer, initialFileReviewState, (initialState) => ({
@@ -18102,10 +18273,12 @@ function useFileReviewContentView({ viewerContextId, cwd, active, onMaximizeHist
18102
18273
  viewerContextId,
18103
18274
  cwd: normalizedCwd,
18104
18275
  active,
18276
+ diffLineComments,
18105
18277
  diffStyle,
18106
18278
  file,
18107
18279
  open: openFiles.includes(reviewFileValue(file)),
18108
18280
  stuck: stickyReviewFileValue === reviewFileValue(file),
18281
+ onAddDiffLineComment,
18109
18282
  onOpenFile
18110
18283
  }, reviewFileValue(file)))
18111
18284
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitSectionNote, {
@@ -18174,10 +18347,12 @@ function useFileReviewContentView({ viewerContextId, cwd, active, onMaximizeHist
18174
18347
  ]
18175
18348
  });
18176
18349
  }
18177
- function ReviewFileAccordionItem({ viewerContextId, cwd, active, diffStyle, file, open, stuck, onOpenFile }) {
18350
+ function ReviewFileAccordionItem({ viewerContextId, cwd, active, diffLineComments = [], diffStyle, file, open, stuck, onAddDiffLineComment, onOpenFile }) {
18178
18351
  const normalizedCwd = normalizeCwd(cwd);
18179
18352
  const queryClient = useQueryClient();
18180
18353
  const value = reviewFileValue(file);
18354
+ const commentTabKey = reviewDiffCommentTabKey(file);
18355
+ const [commentTarget, setCommentTarget] = import_react.useState(null);
18181
18356
  const [fullContextRequested, setFullContextRequested] = import_react.useState(false);
18182
18357
  const previewPatch = gitFileShouldPreviewPatch(file) && !fullContextRequested;
18183
18358
  const diffQuery = useQuery({
@@ -18210,6 +18385,40 @@ function ReviewFileAccordionItem({ viewerContextId, cwd, active, diffStyle, file
18210
18385
  const oldContent = reviewQuery.data?.oldContent ?? "";
18211
18386
  const newContent = reviewQuery.data?.newContent ?? "";
18212
18387
  const patch = diffQuery.data?.patch ?? "";
18388
+ const commentsForFile = diffLineComments.filter((comment) => comment.cwd === normalizedCwd && comment.tabKey === commentTabKey);
18389
+ const startLineComment = (range) => {
18390
+ if (!onAddDiffLineComment) return;
18391
+ const side = reviewDiffCommentSideFromRange(range);
18392
+ setCommentTarget({
18393
+ commit: "working-tree",
18394
+ lineNumber: reviewDiffCommentLineFromRange(range),
18395
+ mode: "head",
18396
+ path: reviewDiffCommentPath(file, side),
18397
+ shortHash: "working tree",
18398
+ side,
18399
+ tabKey: commentTabKey,
18400
+ tabTitle: "Working tree changes"
18401
+ });
18402
+ };
18403
+ const lineAnnotations = reviewDiffLineCommentAnnotations({
18404
+ comments: commentsForFile,
18405
+ pendingComment: commentTarget ? {
18406
+ ...commentTarget,
18407
+ kind: "pending",
18408
+ onCancel: () => setCommentTarget(null),
18409
+ onSubmit: (text) => {
18410
+ onAddDiffLineComment?.({
18411
+ ...commentTarget,
18412
+ text
18413
+ });
18414
+ setCommentTarget(null);
18415
+ }
18416
+ } : null
18417
+ });
18418
+ const commentableOptions = onAddDiffLineComment ? {
18419
+ enableGutterUtility: true,
18420
+ onGutterUtilityClick: startLineComment
18421
+ } : {};
18213
18422
  const [indexStatus, worktreeStatus] = gitFileStatusCharacters(file.status);
18214
18423
  const canStage = worktreeStatus !== " " || indexStatus === "?";
18215
18424
  const invalidateChangedFileQueries = async () => {
@@ -18267,6 +18476,15 @@ function ReviewFileAccordionItem({ viewerContextId, cwd, active, diffStyle, file
18267
18476
  }
18268
18477
  });
18269
18478
  const actionPending = stageMutation.isPending || discardMutation.isPending;
18479
+ import_react.useEffect(() => {
18480
+ setCommentTarget(null);
18481
+ }, [
18482
+ value,
18483
+ previewPatch,
18484
+ patch,
18485
+ oldContent,
18486
+ newContent
18487
+ ]);
18270
18488
  const openFile = (event) => {
18271
18489
  event?.stopPropagation();
18272
18490
  onOpenFile?.(file.path, { pin: true });
@@ -18375,9 +18593,12 @@ function ReviewFileAccordionItem({ viewerContextId, cwd, active, diffStyle, file
18375
18593
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PatchDiff, {
18376
18594
  patch,
18377
18595
  disableWorkerPool: true,
18596
+ lineAnnotations,
18597
+ renderAnnotation: (annotation) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReviewDiffLineAnnotation, { annotation }),
18378
18598
  options: {
18379
18599
  diffStyle,
18380
18600
  disableFileHeader: true,
18601
+ ...commentableOptions,
18381
18602
  overflow: "wrap"
18382
18603
  }
18383
18604
  })] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -18402,9 +18623,12 @@ function ReviewFileAccordionItem({ viewerContextId, cwd, active, diffStyle, file
18402
18623
  contents: newContent
18403
18624
  },
18404
18625
  disableWorkerPool: true,
18626
+ lineAnnotations,
18627
+ renderAnnotation: (annotation) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReviewDiffLineAnnotation, { annotation }),
18405
18628
  options: {
18406
18629
  diffStyle,
18407
18630
  disableFileHeader: true,
18631
+ ...commentableOptions,
18408
18632
  overflow: "wrap"
18409
18633
  }
18410
18634
  }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -18414,6 +18638,7 @@ function ReviewFileAccordionItem({ viewerContextId, cwd, active, diffStyle, file
18414
18638
  })]
18415
18639
  });
18416
18640
  }
18641
+ var EMPTY_RIGHT_SIDEBAR_DIFF_LINE_COMMENTS = [];
18417
18642
  var EMPTY_RIGHT_SIDEBAR_FILE_TABS = [];
18418
18643
  function readStoredHistoryAsTab() {
18419
18644
  return safeLocalStorageGetItem(RIGHT_SIDEBAR_HISTORY_TAB_STORAGE_KEY) === "1";
@@ -18535,7 +18760,159 @@ function commitFileDiffLineCounts(fileDiff) {
18535
18760
  deletions: 0
18536
18761
  });
18537
18762
  }
18538
- function CommitFileDiffAccordionItem({ diffStyle, fileDiff, index, stuck }) {
18763
+ function commitDiffCommentSideLabel(side) {
18764
+ return side === "deletions" ? "old" : "new";
18765
+ }
18766
+ function commitDiffLineCommentAnnotations({ comments, pendingComment }) {
18767
+ const annotations = comments.map((comment) => ({
18768
+ lineNumber: comment.lineNumber,
18769
+ metadata: comment,
18770
+ side: comment.side
18771
+ }));
18772
+ if (pendingComment) annotations.push({
18773
+ lineNumber: pendingComment.lineNumber,
18774
+ metadata: pendingComment,
18775
+ side: pendingComment.side
18776
+ });
18777
+ return annotations;
18778
+ }
18779
+ function assignClassName(element, className) {
18780
+ for (const classItem of className.split(/\s+/)) if (classItem) element.classList.add(classItem);
18781
+ }
18782
+ function renderPendingCommitDiffLineComment(pendingComment) {
18783
+ const form = document.createElement("form");
18784
+ assignClassName(form, "m-2 max-w-[min(38rem,calc(100%-1rem))] rounded-lg border border-border/70 bg-background p-2 shadow-sm");
18785
+ const header = document.createElement("div");
18786
+ assignClassName(header, "mb-2 flex min-w-0 items-center justify-between gap-2 text-xs text-muted-foreground");
18787
+ const label = document.createElement("span");
18788
+ label.textContent = `Comment on ${pendingComment.path}:L${pendingComment.lineNumber} (${commitDiffCommentSideLabel(pendingComment.side)})`;
18789
+ assignClassName(label, "min-w-0 truncate");
18790
+ const cancelButton = document.createElement("button");
18791
+ cancelButton.type = "button";
18792
+ cancelButton.textContent = "×";
18793
+ cancelButton.setAttribute("aria-label", "Cancel line comment");
18794
+ assignClassName(cancelButton, "inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground");
18795
+ cancelButton.addEventListener("click", () => pendingComment.onCancel());
18796
+ header.append(label, cancelButton);
18797
+ const textarea = document.createElement("textarea");
18798
+ textarea.rows = 2;
18799
+ textarea.placeholder = "Add a note for the prompt…";
18800
+ assignClassName(textarea, "flex min-h-16 w-full resize-none rounded-lg border border-input bg-transparent px-2.5 py-2 text-sm outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50");
18801
+ textarea.addEventListener("keydown", (event) => {
18802
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
18803
+ event.preventDefault();
18804
+ submit();
18805
+ }
18806
+ if (event.key === "Escape") {
18807
+ event.preventDefault();
18808
+ pendingComment.onCancel();
18809
+ }
18810
+ });
18811
+ const footer = document.createElement("div");
18812
+ assignClassName(footer, "mt-2 flex justify-end gap-2");
18813
+ const secondaryCancelButton = document.createElement("button");
18814
+ secondaryCancelButton.type = "button";
18815
+ secondaryCancelButton.textContent = "Cancel";
18816
+ assignClassName(secondaryCancelButton, "inline-flex h-8 items-center rounded-md px-3 text-sm hover:bg-muted");
18817
+ secondaryCancelButton.addEventListener("click", () => pendingComment.onCancel());
18818
+ const submitButton = document.createElement("button");
18819
+ submitButton.type = "submit";
18820
+ submitButton.textContent = "Add to prompt";
18821
+ assignClassName(submitButton, "inline-flex h-8 items-center rounded-md bg-primary px-3 text-sm font-medium text-primary-foreground disabled:pointer-events-none disabled:opacity-50");
18822
+ function syncSubmitDisabled() {
18823
+ submitButton.disabled = textarea.value.trim().length === 0;
18824
+ }
18825
+ function submit() {
18826
+ const text = textarea.value.trim();
18827
+ if (!text) return;
18828
+ pendingComment.onSubmit(text);
18829
+ }
18830
+ textarea.addEventListener("input", syncSubmitDisabled);
18831
+ form.addEventListener("submit", (event) => {
18832
+ event.preventDefault();
18833
+ submit();
18834
+ });
18835
+ footer.append(secondaryCancelButton, submitButton);
18836
+ form.append(header, textarea, footer);
18837
+ syncSubmitDisabled();
18838
+ window.setTimeout(() => textarea.focus(), 0);
18839
+ return form;
18840
+ }
18841
+ function renderCommitDiffLineAnnotation(annotation) {
18842
+ const comment = annotation.metadata;
18843
+ if (!comment) return void 0;
18844
+ if (comment.kind === "pending") return renderPendingCommitDiffLineComment(comment);
18845
+ const savedComment = comment;
18846
+ const wrapper = document.createElement("div");
18847
+ assignClassName(wrapper, "my-1 ml-2 inline-flex max-w-[min(32rem,90%)] items-center gap-1 rounded-full border border-amber-500/20 bg-amber-500/10 px-2 py-1 text-[11px] text-amber-800 dark:text-amber-200");
18848
+ const icon = document.createElement("span");
18849
+ icon.textContent = "💬";
18850
+ assignClassName(icon, "shrink-0");
18851
+ const text = document.createElement("span");
18852
+ text.textContent = savedComment.text;
18853
+ assignClassName(text, "truncate");
18854
+ wrapper.append(icon, text);
18855
+ return wrapper;
18856
+ }
18857
+ function CommitDiffFileRenderer({ comments, diffStyle, disableFileHeader = false, fileDiff, onStartComment, pendingComment }) {
18858
+ const [container, setContainer] = import_react.useState(null);
18859
+ const instanceRef = import_react.useRef(null);
18860
+ const lineAnnotations = commitDiffLineCommentAnnotations({
18861
+ comments,
18862
+ pendingComment
18863
+ });
18864
+ const options = {
18865
+ diffStyle,
18866
+ disableFileHeader,
18867
+ enableGutterUtility: true,
18868
+ lineDiffType: "word-alt",
18869
+ lineHoverHighlight: "both",
18870
+ maxLineDiffLength: 1e3,
18871
+ onGutterUtilityClick: (range) => {
18872
+ onStartComment({
18873
+ lineNumber: Math.min(range.start, range.end),
18874
+ path: fileDiff.name,
18875
+ side: range.side || range.endSide || "additions"
18876
+ });
18877
+ },
18878
+ onLineNumberClick: (props) => {
18879
+ onStartComment({
18880
+ lineNumber: props.lineNumber,
18881
+ path: fileDiff.name,
18882
+ side: props.annotationSide
18883
+ });
18884
+ },
18885
+ overflow: "wrap",
18886
+ renderAnnotation: renderCommitDiffLineAnnotation
18887
+ };
18888
+ import_react.useLayoutEffect(() => {
18889
+ if (!container) return;
18890
+ const existing = instanceRef.current;
18891
+ if (existing) {
18892
+ existing.setOptions(options);
18893
+ existing.render({
18894
+ containerWrapper: container,
18895
+ fileDiff,
18896
+ forceRender: true,
18897
+ lineAnnotations
18898
+ });
18899
+ return;
18900
+ }
18901
+ const instance = new FileDiff$1(options, void 0, false);
18902
+ instanceRef.current = instance;
18903
+ instance.render({
18904
+ containerWrapper: container,
18905
+ fileDiff,
18906
+ lineAnnotations
18907
+ });
18908
+ });
18909
+ import_react.useLayoutEffect(() => () => {
18910
+ instanceRef.current?.cleanUp();
18911
+ instanceRef.current = null;
18912
+ }, []);
18913
+ return import_react.createElement("div", { ref: setContainer });
18914
+ }
18915
+ function CommitFileDiffAccordionItem({ comments, diffStyle, fileDiff, index, onStartComment, pendingComment, stuck }) {
18539
18916
  const value = commitFileDiffValue(fileDiff, index);
18540
18917
  const counts = commitFileDiffLineCounts(fileDiff);
18541
18918
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AccordionItem, {
@@ -18586,21 +18963,18 @@ function CommitFileDiffAccordionItem({ diffStyle, fileDiff, index, stuck }) {
18586
18963
  })
18587
18964
  }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AccordionContent, {
18588
18965
  className: "bg-background p-0",
18589
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileDiff$1, {
18966
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitDiffFileRenderer, {
18967
+ comments,
18968
+ diffStyle,
18969
+ disableFileHeader: true,
18590
18970
  fileDiff,
18591
- disableWorkerPool: true,
18592
- options: {
18593
- diffStyle,
18594
- disableFileHeader: true,
18595
- lineDiffType: "word-alt",
18596
- maxLineDiffLength: 1e3,
18597
- overflow: "wrap"
18598
- }
18971
+ onStartComment,
18972
+ pendingComment
18599
18973
  })
18600
18974
  })]
18601
18975
  });
18602
18976
  }
18603
- function CommitSingleFileDiff({ diffStyle, patch }) {
18977
+ function CommitSingleFileDiff({ comments, diffStyle, onStartComment, patch, pendingComment }) {
18604
18978
  const fileDiff = parsePatchFiles(patch).flatMap((parsed) => parsed.files)[0];
18605
18979
  if (!fileDiff) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18606
18980
  className: "p-4",
@@ -18609,19 +18983,16 @@ function CommitSingleFileDiff({ diffStyle, patch }) {
18609
18983
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18610
18984
  className: "min-h-full text-xs",
18611
18985
  style: { "--diffs-font-family": "var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace)" },
18612
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileDiff$1, {
18986
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitDiffFileRenderer, {
18987
+ comments,
18988
+ diffStyle,
18613
18989
  fileDiff,
18614
- disableWorkerPool: true,
18615
- options: {
18616
- diffStyle,
18617
- lineDiffType: "word-alt",
18618
- maxLineDiffLength: 1e3,
18619
- overflow: "wrap"
18620
- }
18990
+ onStartComment,
18991
+ pendingComment
18621
18992
  })
18622
18993
  });
18623
18994
  }
18624
- function CommitPatchDiffs({ diffStyle, patch, stuckFileValue }) {
18995
+ function CommitPatchDiffs({ comments, diffStyle, onStartComment, patch, pendingComment, stuckFileValue }) {
18625
18996
  const fileDiffs = parsePatchFiles(patch).flatMap((parsed) => parsed.files);
18626
18997
  const firstValue = fileDiffs[0] ? commitFileDiffValue(fileDiffs[0], 0) : void 0;
18627
18998
  if (fileDiffs.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
@@ -18636,9 +19007,12 @@ function CommitPatchDiffs({ diffStyle, patch, stuckFileValue }) {
18636
19007
  defaultValue: firstValue ? [firstValue] : [],
18637
19008
  className: "border-b border-border/80",
18638
19009
  children: fileDiffs.map((fileDiff, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitFileDiffAccordionItem, {
19010
+ comments: comments.filter((comment) => comment.path === fileDiff.name),
18639
19011
  diffStyle,
18640
19012
  fileDiff,
18641
19013
  index,
19014
+ onStartComment,
19015
+ pendingComment: pendingComment?.path === fileDiff.name ? pendingComment : null,
18642
19016
  stuck: stuckFileValue === commitFileDiffValue(fileDiff, index)
18643
19017
  }, commitFileDiffValue(fileDiff, index)))
18644
19018
  })
@@ -18684,9 +19058,10 @@ function CommitDiffHeaderTitle({ tab }) {
18684
19058
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitDiffPathBreadcrumb, { path: tab.path })
18685
19059
  });
18686
19060
  }
18687
- function CommitDiffContent({ viewerContextId, cwd, active, tab }) {
19061
+ function CommitDiffContent({ viewerContextId, cwd, active, diffLineComments, onAddDiffLineComment, tab }) {
18688
19062
  const [diffStyle, setDiffStyle] = import_react.useState("unified");
18689
19063
  const [stickyCommitFileValue, setStickyCommitFileValue] = import_react.useState("");
19064
+ const [commentTarget, setCommentTarget] = import_react.useState(null);
18690
19065
  const diffScrollRef = import_react.useRef(null);
18691
19066
  const updateStickyCommitFileHeader = (scrollElement) => {
18692
19067
  setDerivedScrollState(setStickyCommitFileValue, getStuckScrollTriggerValue({
@@ -18712,7 +19087,34 @@ function CommitDiffContent({ viewerContextId, cwd, active, tab }) {
18712
19087
  "error"
18713
19088
  ]
18714
19089
  });
19090
+ const commentsForTab = tab ? diffLineComments.filter((comment) => comment.tabKey === tab.key && comment.commit === tab.commit && comment.mode === tab.mode) : EMPTY_RIGHT_SIDEBAR_DIFF_LINE_COMMENTS;
19091
+ const startLineComment = (lineTarget) => {
19092
+ if (!tab || !onAddDiffLineComment) return;
19093
+ setCommentTarget({
19094
+ commit: tab.commit,
19095
+ lineNumber: lineTarget.lineNumber,
19096
+ mode: tab.mode,
19097
+ path: lineTarget.path,
19098
+ shortHash: tab.shortHash,
19099
+ side: lineTarget.side,
19100
+ tabKey: tab.key,
19101
+ tabTitle: tab.title
19102
+ });
19103
+ };
19104
+ const pendingComment = commentTarget ? {
19105
+ ...commentTarget,
19106
+ kind: "pending",
19107
+ onCancel: () => setCommentTarget(null),
19108
+ onSubmit: (text) => {
19109
+ onAddDiffLineComment?.({
19110
+ ...commentTarget,
19111
+ text
19112
+ });
19113
+ setCommentTarget(null);
19114
+ }
19115
+ } : null;
18715
19116
  import_react.useEffect(() => {
19117
+ setCommentTarget(null);
18716
19118
  setDerivedScrollState(setStickyCommitFileValue, "");
18717
19119
  updateStickyCommitFileHeader(diffScrollRef.current);
18718
19120
  }, [tab?.key, diffQuery.data?.patch]);
@@ -18755,11 +19157,20 @@ function CommitDiffContent({ viewerContextId, cwd, active, tab }) {
18755
19157
  children: getErrorMessage(diffQuery.error, "Failed to load commit diff")
18756
19158
  })
18757
19159
  }) : diffQuery.data?.patch && tab.path ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitSingleFileDiff, {
19160
+ comments: commentsForTab.filter((comment) => {
19161
+ if (!tab.path) return true;
19162
+ return comment.path === tab.path || comment.path === tab.previousPath;
19163
+ }),
18758
19164
  diffStyle,
18759
- patch: diffQuery.data.patch
19165
+ onStartComment: startLineComment,
19166
+ patch: diffQuery.data.patch,
19167
+ pendingComment
18760
19168
  }) : diffQuery.data?.patch ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitPatchDiffs, {
19169
+ comments: commentsForTab,
18761
19170
  diffStyle,
19171
+ onStartComment: startLineComment,
18762
19172
  patch: diffQuery.data.patch,
19173
+ pendingComment,
18763
19174
  stuckFileValue: stickyCommitFileValue
18764
19175
  }, `${diffQuery.data.commit}:${diffQuery.data.mode}`) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18765
19176
  className: "p-4",
@@ -18768,7 +19179,7 @@ function CommitDiffContent({ viewerContextId, cwd, active, tab }) {
18768
19179
  })]
18769
19180
  });
18770
19181
  }
18771
- function RightSidebar({ viewerContextId, cwd, active, activeFilePath = "", activeTab: controlledActiveTab, filePreviewPath = "", fileTabs = EMPTY_RIGHT_SIDEBAR_FILE_TABS, fileTreeCollapsed = false, onActiveFileChange, onActiveTabChange, onCloseAllFiles, onCloseFile, onCloseFilesToRight, onCloseOtherFiles, onFileTreeCollapsedChange, onOpenFile, onReorderFiles, showToolbar = true }) {
19182
+ function RightSidebar({ viewerContextId, cwd, active, activeFilePath = "", activeTab: controlledActiveTab, diffLineComments = EMPTY_RIGHT_SIDEBAR_DIFF_LINE_COMMENTS, filePreviewPath = "", fileTabs = EMPTY_RIGHT_SIDEBAR_FILE_TABS, fileTreeCollapsed = false, onActiveFileChange, onActiveTabChange, onAddDiffLineComment, onCloseAllFiles, onCloseFile, onCloseFilesToRight, onCloseOtherFiles, onFileTreeCollapsedChange, onOpenFile, onReorderFiles, showToolbar = true }) {
18772
19183
  const normalizedCwd = normalizeCwd(cwd);
18773
19184
  const isMobile = useIsMobile();
18774
19185
  const [localState, dispatchLocal] = import_react.useReducer(rightSidebarLocalReducer, initialRightSidebarLocalState);
@@ -18912,96 +19323,88 @@ function RightSidebar({ viewerContextId, cwd, active, activeFilePath = "", activ
18912
19323
  showHistory: historyAsTab,
18913
19324
  showReview: true
18914
19325
  }),
18915
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.Activity, {
18916
- mode: activeTab === "review" ? "visible" : "hidden",
18917
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18918
- className: "min-h-0 flex-1 overflow-hidden",
18919
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileReviewContent, {
18920
- viewerContextId,
18921
- cwd: normalizedCwd,
18922
- active: active && activeTab === "review",
18923
- onMaximizeHistory: () => {
18924
- setHistoryAsTab(true);
18925
- setActiveTab("history");
18926
- },
18927
- onOpenCommitDiff: openCommitDiff,
18928
- onOpenFile: openFile,
18929
- showEmbeddedHistory: !historyAsTab
18930
- })
19326
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19327
+ className: activeTab === "review" ? "min-h-0 flex-1 overflow-hidden" : "hidden",
19328
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileReviewContent, {
19329
+ viewerContextId,
19330
+ cwd: normalizedCwd,
19331
+ active: active && activeTab === "review",
19332
+ diffLineComments,
19333
+ onMaximizeHistory: () => {
19334
+ setHistoryAsTab(true);
19335
+ setActiveTab("history");
19336
+ },
19337
+ onAddDiffLineComment,
19338
+ onOpenCommitDiff: openCommitDiff,
19339
+ onOpenFile: openFile,
19340
+ showEmbeddedHistory: !historyAsTab
18931
19341
  })
18932
19342
  }),
18933
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.Activity, {
18934
- mode: activeTab === "history" ? "visible" : "hidden",
18935
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18936
- className: "min-h-0 flex-1 overflow-hidden",
18937
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitCommitsSection, {
18938
- viewerContextId,
18939
- cwd: normalizedCwd,
18940
- active: active && activeTab === "history",
18941
- flush: true,
18942
- onOpenCommitDiff: openCommitDiff,
18943
- onRestoreEmbedded: () => {
18944
- setHistoryAsTab(false);
18945
- setActiveTab("review");
18946
- }
18947
- })
19343
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19344
+ className: activeTab === "history" ? "min-h-0 flex-1 overflow-hidden" : "hidden",
19345
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GitCommitsSection, {
19346
+ viewerContextId,
19347
+ cwd: normalizedCwd,
19348
+ active: active && activeTab === "history",
19349
+ flush: true,
19350
+ onOpenCommitDiff: openCommitDiff,
19351
+ onRestoreEmbedded: () => {
19352
+ setHistoryAsTab(false);
19353
+ setActiveTab("review");
19354
+ }
18948
19355
  })
18949
19356
  }),
18950
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.Activity, {
18951
- mode: activeTab === "commit-diff" ? "visible" : "hidden",
18952
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18953
- className: "min-h-0 flex-1 overflow-hidden",
18954
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitDiffContent, {
18955
- viewerContextId,
18956
- cwd: normalizedCwd,
18957
- active: active && activeTab === "commit-diff",
18958
- tab: activeCommitDiffTab
18959
- })
19357
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19358
+ className: activeTab === "commit-diff" ? "min-h-0 flex-1 overflow-hidden" : "hidden",
19359
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CommitDiffContent, {
19360
+ viewerContextId,
19361
+ cwd: normalizedCwd,
19362
+ active: active && activeTab === "commit-diff",
19363
+ diffLineComments,
19364
+ onAddDiffLineComment,
19365
+ tab: activeCommitDiffTab
18960
19366
  })
18961
19367
  }),
18962
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.Activity, {
18963
- mode: activeTab === "files" ? "visible" : "hidden",
18964
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18965
- className: "min-h-0 flex-1 overflow-hidden",
18966
- children: previewMode === "external" && hasOpenFileTabs ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18967
- className: "flex h-full min-h-0 min-w-0 flex-col overflow-hidden",
18968
- children: [currentFilePath ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilePathBreadcrumb, {
18969
- path: currentFilePath,
18970
- fileTreeCollapsed,
18971
- onFileTreeCollapsedChange
18972
- }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
18973
- className: "flex min-h-0 flex-1 overflow-hidden",
18974
- children: [fileTreeCollapsed ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProjectFileTreePane, {
19368
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19369
+ className: activeTab === "files" ? "min-h-0 flex-1 overflow-hidden" : "hidden",
19370
+ children: previewMode === "external" && hasOpenFileTabs ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19371
+ className: "flex h-full min-h-0 min-w-0 flex-col overflow-hidden",
19372
+ children: [currentFilePath ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilePathBreadcrumb, {
19373
+ path: currentFilePath,
19374
+ fileTreeCollapsed,
19375
+ onFileTreeCollapsedChange
19376
+ }) : null, /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
19377
+ className: "flex min-h-0 flex-1 overflow-hidden",
19378
+ children: [fileTreeCollapsed ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProjectFileTreePane, {
19379
+ viewerContextId,
19380
+ cwd: normalizedCwd,
19381
+ active: active && activeTab === "files",
19382
+ activeFilePath: currentFilePath,
19383
+ onOpenFile: openFile,
19384
+ previewMode
19385
+ }), currentFilePath ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
19386
+ className: "min-h-0 min-w-0 flex-1 overflow-hidden",
19387
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProjectFileContent, {
18975
19388
  viewerContextId,
18976
19389
  cwd: normalizedCwd,
18977
19390
  active: active && activeTab === "files",
18978
- activeFilePath: currentFilePath,
18979
- onOpenFile: openFile,
18980
- previewMode
18981
- }), currentFilePath ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
18982
- className: "min-h-0 min-w-0 flex-1 overflow-hidden",
18983
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProjectFileContent, {
18984
- viewerContextId,
18985
- cwd: normalizedCwd,
18986
- active: active && activeTab === "files",
18987
- path: currentFilePath
18988
- })
18989
- }) : null]
18990
- })]
18991
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProjectFilesWorkspace, {
18992
- viewerContextId,
18993
- cwd: normalizedCwd,
18994
- active: active && activeTab === "files",
18995
- activeFilePath: currentFilePath,
18996
- onCloseFile: () => {
18997
- dispatchLocal({
18998
- type: "set-inline-active-file",
18999
- path: ""
19000
- });
19001
- },
19002
- onOpenFile: openFile,
19003
- previewMode
19004
- })
19391
+ path: currentFilePath
19392
+ })
19393
+ }) : null]
19394
+ })]
19395
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProjectFilesWorkspace, {
19396
+ viewerContextId,
19397
+ cwd: normalizedCwd,
19398
+ active: active && activeTab === "files",
19399
+ activeFilePath: currentFilePath,
19400
+ onCloseFile: () => {
19401
+ dispatchLocal({
19402
+ type: "set-inline-active-file",
19403
+ path: ""
19404
+ });
19405
+ },
19406
+ onOpenFile: openFile,
19407
+ previewMode
19005
19408
  })
19006
19409
  }),
19007
19410
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ProjectOpenFileDialog, {
@@ -19151,13 +19554,14 @@ function AppShellTabsList({ sessionStore, viewerContextId }) {
19151
19554
  })]
19152
19555
  });
19153
19556
  }
19154
- var AppShellGitPanelController = import_react.memo(function AppShellGitPanelController({ active, onCloseAllFiles, onCloseFile, onCloseFilesToRight, onCloseOtherFiles, onOpenFile, onReorderFiles, rightSidebarStore, sessionStore, viewerContextId }) {
19557
+ var AppShellGitPanelController = import_react.memo(function AppShellGitPanelController({ active, composerDiffLineComments, onAddDiffLineComment, onCloseAllFiles, onCloseFile, onCloseFilesToRight, onCloseOtherFiles, onOpenFile, onReorderFiles, rightSidebarStore, sessionStore, viewerContextId }) {
19155
19558
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RightSidebar, {
19156
19559
  viewerContextId,
19157
19560
  cwd: useSelector(sessionStore, (sessionState) => sessionState.cwd),
19158
19561
  active,
19159
19562
  activeFilePath: useSelector(rightSidebarStore, (state) => state.fileActivePath),
19160
19563
  activeTab: useSelector(rightSidebarStore, (state) => state.activeTab),
19564
+ diffLineComments: composerDiffLineComments,
19161
19565
  filePreviewPath: useSelector(rightSidebarStore, (state) => state.filePreviewPath),
19162
19566
  fileTabs: useSelector(rightSidebarStore, selectRightSidebarVisibleFileTabs, { compare: sameStringArray$3 }),
19163
19567
  fileTreeCollapsed: useSelector(rightSidebarStore, (state) => state.fileTreeCollapsed),
@@ -19167,6 +19571,7 @@ var AppShellGitPanelController = import_react.memo(function AppShellGitPanelCont
19167
19571
  onActiveTabChange: (tab) => {
19168
19572
  setRightSidebarActiveTab(rightSidebarStore, tab);
19169
19573
  },
19574
+ onAddDiffLineComment,
19170
19575
  onCloseAllFiles,
19171
19576
  onCloseFile,
19172
19577
  onCloseFilesToRight,
@@ -19178,7 +19583,7 @@ var AppShellGitPanelController = import_react.memo(function AppShellGitPanelCont
19178
19583
  onReorderFiles
19179
19584
  });
19180
19585
  });
19181
- function AppShellDesktopGitPanel({ active, onCloseAllFiles, onCloseFile, onCloseFilesToRight, onCloseOtherFiles, onOpenFile, onReorderFiles, rightSidebarStore, sessionStore, viewerContextId }) {
19586
+ function AppShellDesktopGitPanel({ active, composerDiffLineComments, onAddDiffLineComment, onCloseAllFiles, onCloseFile, onCloseFilesToRight, onCloseOtherFiles, onOpenFile, onReorderFiles, rightSidebarStore, sessionStore, viewerContextId }) {
19182
19587
  const cwd = useSelector(sessionStore, (sessionState) => sessionState.cwd);
19183
19588
  const activeFilePath = useSelector(rightSidebarStore, (state) => state.fileActivePath);
19184
19589
  const activeTab = useSelector(rightSidebarStore, (state) => state.activeTab);
@@ -19196,6 +19601,7 @@ function AppShellDesktopGitPanel({ active, onCloseAllFiles, onCloseFile, onClose
19196
19601
  active,
19197
19602
  activeFilePath,
19198
19603
  activeTab,
19604
+ diffLineComments: composerDiffLineComments,
19199
19605
  filePreviewPath,
19200
19606
  fileTabs,
19201
19607
  fileTreeCollapsed,
@@ -19205,6 +19611,7 @@ function AppShellDesktopGitPanel({ active, onCloseAllFiles, onCloseFile, onClose
19205
19611
  onActiveTabChange: (tab) => {
19206
19612
  setRightSidebarActiveTab(rightSidebarStore, tab);
19207
19613
  },
19614
+ onAddDiffLineComment,
19208
19615
  onCloseAllFiles,
19209
19616
  onCloseFile,
19210
19617
  onCloseFilesToRight,
@@ -19383,7 +19790,7 @@ function AppShellDesktopResizeHandle({ label, max, min, onResize, onResizeEnd, o
19383
19790
  function AppShellTabsController(props) {
19384
19791
  return useAppShellTabsControllerView(props);
19385
19792
  }
19386
- function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTurn, composerPanelRef, contextUsageStore, conversationFrameRef, conversationItemsStore, defaultNewSessionDirectory, displaySettingsStore, fileInputRef, gitPanelOpen, hiddenThinkingPreviewStore, isSessionViewLoading, isSubmitting, isMobile, newSessionDirectoryOptions, onCancelCompaction, onCreateSession, onOpenAddDirectoryDialog, onCloseAllFileViewTabs, onCloseFileViewTab, onCloseFileViewTabsToRight, onCloseOtherFileViewTabs, onOpenFileViewTab, onReorderFileViewTabs, onValueChange, rightSidebarStore, sessionStore, store, viewerContextId, workingStateStore }) {
19793
+ function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTurn, composerDiffLineComments, composerPanelRef, contextUsageStore, conversationFrameRef, conversationItemsStore, defaultNewSessionDirectory, displaySettingsStore, fileInputRef, gitPanelOpen, hiddenThinkingPreviewStore, isSessionViewLoading, isSubmitting, isMobile, newSessionDirectoryOptions, onCancelCompaction, onCreateSession, onOpenAddDirectoryDialog, onCloseAllFileViewTabs, onCloseFileViewTab, onCloseFileViewTabsToRight, onCloseOtherFileViewTabs, onAddDiffLineComment, onOpenFileViewTab, onReorderFileViewTabs, onValueChange, rightSidebarStore, sessionStore, store, viewerContextId, workingStateStore }) {
19387
19794
  const currentTab = useSelector(appUiStore, (state) => state.currentTab);
19388
19795
  const isDraftSession = useSelector(sessionStore, (sessionState) => sessionState.draft);
19389
19796
  const showTabsList = isMobile || !isDraftSession || isSessionViewLoading;
@@ -19461,7 +19868,9 @@ function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTu
19461
19868
  viewerContextId,
19462
19869
  sessionStore,
19463
19870
  active: currentTab === "git",
19871
+ composerDiffLineComments,
19464
19872
  rightSidebarStore,
19873
+ onAddDiffLineComment,
19465
19874
  onCloseAllFiles: onCloseAllFileViewTabs,
19466
19875
  onCloseFile: onCloseFileViewTab,
19467
19876
  onCloseFilesToRight: onCloseFileViewTabsToRight,
@@ -19564,7 +19973,9 @@ function useAppShellTabsControllerView({ actionsRef, appUiStore, awaitingFirstTu
19564
19973
  viewerContextId,
19565
19974
  sessionStore,
19566
19975
  active: desktopGitPanelOpen,
19976
+ composerDiffLineComments,
19567
19977
  rightSidebarStore,
19978
+ onAddDiffLineComment,
19568
19979
  onCloseAllFiles: onCloseAllFileViewTabs,
19569
19980
  onCloseFile: onCloseFileViewTab,
19570
19981
  onCloseFilesToRight: onCloseFileViewTabsToRight,
@@ -21626,8 +22037,30 @@ function movePendingDraftFollowUpMessage(messages, pendingId, direction) {
21626
22037
  next.splice(targetIndex, 0, movedItem);
21627
22038
  return next;
21628
22039
  }
22040
+ var EMPTY_COMPOSER_DIFF_LINE_COMMENTS = [];
21629
22041
  var EMPTY_COMPOSER_IMAGES = [];
21630
22042
  var EMPTY_COMPOSER_PENDING_MESSAGES = [];
22043
+ function sameComposerDiffLineComments(left, right) {
22044
+ if (left === right) return true;
22045
+ if (left.length !== right.length) return false;
22046
+ for (let index = 0; index < left.length; index += 1) {
22047
+ const leftComment = left[index];
22048
+ const rightComment = right[index];
22049
+ if (!leftComment || !rightComment) return false;
22050
+ if (leftComment.id !== rightComment.id) return false;
22051
+ if (leftComment.cwd !== rightComment.cwd) return false;
22052
+ if (leftComment.path !== rightComment.path) return false;
22053
+ if (leftComment.lineNumber !== rightComment.lineNumber) return false;
22054
+ if (leftComment.side !== rightComment.side) return false;
22055
+ if (leftComment.text !== rightComment.text) return false;
22056
+ if (leftComment.tabKey !== rightComment.tabKey) return false;
22057
+ if (leftComment.tabTitle !== rightComment.tabTitle) return false;
22058
+ if (leftComment.commit !== rightComment.commit) return false;
22059
+ if (leftComment.shortHash !== rightComment.shortHash) return false;
22060
+ if (leftComment.mode !== rightComment.mode) return false;
22061
+ }
22062
+ return true;
22063
+ }
21631
22064
  function sameComposerPromptImages(left, right) {
21632
22065
  if (left === right) return true;
21633
22066
  if (left.length !== right.length) return false;
@@ -21656,13 +22089,14 @@ function sameComposerPendingMessages(left, right) {
21656
22089
  return true;
21657
22090
  }
21658
22091
  function sameAppShellComposerSnapshot(left, right) {
21659
- return left.activeSessionId === right.activeSessionId && left.awaitingFirstTurn === right.awaitingFirstTurn && left.centerMessages === right.centerMessages && left.composerSkill === right.composerSkill && left.composerSyncNonce === right.composerSyncNonce && left.composerText === right.composerText && left.disabled === right.disabled && left.isStreaming === right.isStreaming && left.isSubmitting === right.isSubmitting && left.viewerContextId === right.viewerContextId && sameComposerPromptImages(left.composerImages, right.composerImages) && sameComposerPendingMessages(left.currentPendingMessages, right.currentPendingMessages);
22092
+ return left.activeSessionId === right.activeSessionId && left.awaitingFirstTurn === right.awaitingFirstTurn && left.centerMessages === right.centerMessages && sameComposerDiffLineComments(left.composerDiffLineComments, right.composerDiffLineComments) && left.composerSkill === right.composerSkill && left.composerSyncNonce === right.composerSyncNonce && left.composerText === right.composerText && left.disabled === right.disabled && left.isStreaming === right.isStreaming && left.isSubmitting === right.isSubmitting && left.viewerContextId === right.viewerContextId && sameComposerPromptImages(left.composerImages, right.composerImages) && sameComposerPendingMessages(left.currentPendingMessages, right.currentPendingMessages);
21660
22093
  }
21661
22094
  function createInitialAppShellComposerSnapshot(viewerContextId) {
21662
22095
  return {
21663
22096
  activeSessionId: void 0,
21664
22097
  awaitingFirstTurn: false,
21665
22098
  centerMessages: false,
22099
+ composerDiffLineComments: EMPTY_COMPOSER_DIFF_LINE_COMMENTS,
21666
22100
  composerImages: EMPTY_COMPOSER_IMAGES,
21667
22101
  composerSkill: void 0,
21668
22102
  composerSyncNonce: 0,
@@ -21732,10 +22166,23 @@ function createPicoLatestThrottler(options) {
21732
22166
  };
21733
22167
  }
21734
22168
  function samePromptMutationSessionSnapshot(left, right) {
21735
- return left.cwd === right.cwd && left.draft === right.draft && left.sessionFile === right.sessionFile && left.sessionId === right.sessionId && left.sessionKey === right.sessionKey && left.streaming === right.streaming;
22169
+ return left.assistantOutputSignature === right.assistantOutputSignature && left.cwd === right.cwd && left.draft === right.draft && left.sessionFile === right.sessionFile && left.sessionId === right.sessionId && left.sessionKey === right.sessionKey && left.streaming === right.streaming;
22170
+ }
22171
+ function sessionAssistantOutputSignature(items) {
22172
+ let outputCount = 0;
22173
+ let lastOutputKey = "";
22174
+ for (let index = 0; index < items.length; index += 1) {
22175
+ const item = items[index];
22176
+ if (!item || item.kind !== "assistant") continue;
22177
+ if (!item.blocks.some((block) => block.type === "text" && block.text.trim())) continue;
22178
+ outputCount += 1;
22179
+ lastOutputKey = item.itemKey || item.renderKey || `index:${index}`;
22180
+ }
22181
+ return `${outputCount}:${lastOutputKey}`;
21736
22182
  }
21737
22183
  function usePromptMutationSessionSnapshot(store) {
21738
22184
  return useSelector(store, (sessionState) => ({
22185
+ assistantOutputSignature: sessionAssistantOutputSignature(sessionState.items),
21739
22186
  cwd: sessionState.cwd,
21740
22187
  draft: sessionState.draft,
21741
22188
  sessionFile: sessionState.sessionFile,
@@ -21753,7 +22200,8 @@ function createLocalPendingId() {
21753
22200
  }
21754
22201
  function buildCurrentSessionRequestUrl(path, { contextId, sessionState, fallbackSessionId }) {
21755
22202
  const sessionKey = sessionState.sessionKey && !sessionState.sessionKey.startsWith("optimistic:") ? sessionState.sessionKey : void 0;
21756
- const shouldUseSessionKey = Boolean(sessionKey && (sessionState.draft || !sessionState.sessionId));
22203
+ const hasFallbackSession = Boolean(fallbackSessionId && fallbackSessionId !== sessionState.sessionId);
22204
+ const shouldUseSessionKey = Boolean(sessionKey && !hasFallbackSession && (sessionState.draft || !sessionState.sessionId));
21757
22205
  return buildRequestUrl(path, {
21758
22206
  contextId,
21759
22207
  sessionId: shouldUseSessionKey ? void 0 : sessionState.sessionId ?? fallbackSessionId,
@@ -21809,13 +22257,14 @@ function movePendingComposerMessage(messages, pendingId, direction) {
21809
22257
  next.splice(targetIndex, 0, movedItem);
21810
22258
  return next;
21811
22259
  }
21812
- function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultNewSessionDirectory, sessionStore, sessionStateRef, draftSessionLoadingOwnerKey, pendingDraftPrompt, pendingDraftFollowUps, awaitingFirstTurn, pendingMessages, composerImagesRef, composerTextRef, composerSkillRef, replaceComposerDraft, lastSyncedEditorTextRef, rememberRecentDirectory, prefetchDirectorySessionsIndex, addOptimisticUserMessage, removeOptimisticUserMessage, setSidebarDirectories, setStoredDraftDirectory, setDraftSessionLoadingOwnerKey, setPendingDraftPrompt, setPendingDraftFollowUps, setPendingMessages, setAwaitingFirstTurn, setIsSubmitting, setComposerImages }) {
22260
+ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultNewSessionDirectory, sessionStore, sessionStateRef, draftSessionLoadingOwnerKey, pendingDraftPrompt, pendingDraftFollowUps, awaitingFirstTurn, pendingMessages, composerDiffLineCommentsRef, composerImagesRef, composerTextRef, composerSkillRef, replaceComposerDraft, lastSyncedEditorTextRef, rememberRecentDirectory, prefetchDirectorySessionsIndex, addOptimisticUserMessage, removeOptimisticUserMessage, setSidebarDirectories, setStoredDraftDirectory, setDraftSessionLoadingOwnerKey, setPendingDraftPrompt, setPendingDraftFollowUps, setPendingMessages, setAwaitingFirstTurn, setIsSubmitting, setComposerDiffLineComments, setComposerImages }) {
21813
22261
  const queryClient = useQueryClient();
21814
22262
  const draftSessionLoadingOwnerKeyRef = import_react.useRef(draftSessionLoadingOwnerKey);
21815
22263
  const pendingDraftPromptRef = import_react.useRef(pendingDraftPrompt);
21816
22264
  const pendingDraftFollowUpsRef = import_react.useRef(pendingDraftFollowUps);
21817
22265
  const pendingMessagesRef = import_react.useRef(pendingMessages);
21818
22266
  const awaitingFirstTurnRef = import_react.useRef(awaitingFirstTurn);
22267
+ const awaitingFirstTurnAssistantOutputSignatureRef = import_react.useRef(null);
21819
22268
  const sessionSnapshot = usePromptMutationSessionSnapshot(sessionStore);
21820
22269
  const sessionStreamingRef = import_react.useRef(sessionSnapshot.streaming);
21821
22270
  import_react.useEffect(() => {
@@ -21893,6 +22342,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
21893
22342
  pendingDraftFollowUpsRef.current = [];
21894
22343
  setPendingDraftPrompt(null);
21895
22344
  setPendingDraftFollowUps([]);
22345
+ awaitingFirstTurnAssistantOutputSignatureRef.current = null;
21896
22346
  awaitingFirstTurnRef.current = false;
21897
22347
  setAwaitingFirstTurn(false);
21898
22348
  return applyPendingDraftPromptToComposer(nextPrompt);
@@ -21977,9 +22427,10 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
21977
22427
  const queuePendingDraftPrompt = import_react.useCallback((streamingBehavior) => {
21978
22428
  const ownerKey = draftSessionLoadingOwnerKeyRef.current;
21979
22429
  if (!ownerKey) return false;
21980
- const message = serializeComposerDraft({
22430
+ const message = buildComposerPromptMessage({
21981
22431
  text: composerTextRef.current,
21982
- skillName: composerSkillRef.current
22432
+ skillName: composerSkillRef.current,
22433
+ diffLineComments: composerDiffLineCommentsRef.current
21983
22434
  }).trim();
21984
22435
  const images = composerImagesRef.current.map((image) => ({ ...image }));
21985
22436
  if (!message && images.length === 0) return false;
@@ -22012,17 +22463,20 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
22012
22463
  setPendingDraftFollowUps(nextFollowUps);
22013
22464
  }
22014
22465
  replaceComposerDraft("", void 0, { forceSync: true });
22466
+ setComposerDiffLineComments([]);
22015
22467
  setComposerImages([]);
22016
22468
  lastSyncedEditorTextRef.current = "";
22017
22469
  return true;
22018
22470
  }, [
22019
22471
  addOptimisticUserMessage,
22472
+ composerDiffLineCommentsRef,
22020
22473
  composerImagesRef,
22021
22474
  composerSkillRef,
22022
22475
  composerTextRef,
22023
22476
  lastSyncedEditorTextRef,
22024
22477
  replaceComposerDraft,
22025
22478
  setAwaitingFirstTurn,
22479
+ setComposerDiffLineComments,
22026
22480
  setComposerImages,
22027
22481
  setPendingDraftFollowUps,
22028
22482
  setPendingDraftPrompt
@@ -22056,15 +22510,22 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
22056
22510
  const submitPrompt = import_react.useCallback(async (streamingBehavior, options) => {
22057
22511
  if (!viewerContextId) return false;
22058
22512
  if (draftSessionLoadingOwnerKeyRef.current) return queuePendingDraftPrompt(streamingBehavior);
22059
- const message = serializeComposerDraft({
22513
+ const submittedDiffLineComments = composerDiffLineCommentsRef.current.map((comment) => ({ ...comment }));
22514
+ const message = buildComposerPromptMessage({
22060
22515
  text: composerTextRef.current,
22061
- skillName: composerSkillRef.current
22516
+ skillName: composerSkillRef.current,
22517
+ diffLineComments: submittedDiffLineComments
22062
22518
  }).trim();
22063
22519
  if (!message && composerImagesRef.current.length === 0) return false;
22064
22520
  const treatAsQueuedPrompt = options?.forceFirstPrompt ? false : Boolean(sessionStreamingRef.current || awaitingFirstTurnRef.current);
22065
22521
  const normalizedStreamingBehavior = treatAsQueuedPrompt ? normalizeQueuedStreamingBehavior(streamingBehavior) : void 0;
22066
22522
  const submittedImages = composerImagesRef.current.map((image) => ({ ...image }));
22067
22523
  const queuedPendingId = treatAsQueuedPrompt ? createLocalPendingId() : void 0;
22524
+ if (!treatAsQueuedPrompt) {
22525
+ awaitingFirstTurnAssistantOutputSignatureRef.current = sessionAssistantOutputSignature(sessionStateRef.current.items);
22526
+ awaitingFirstTurnRef.current = true;
22527
+ setAwaitingFirstTurn(true);
22528
+ }
22068
22529
  const optimisticId = options?.optimisticId ?? (treatAsQueuedPrompt ? void 0 : addOptimisticUserMessage({
22069
22530
  message,
22070
22531
  images: submittedImages,
@@ -22076,12 +22537,9 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
22076
22537
  images: submittedImages.map((image) => ({ ...image })),
22077
22538
  streamingBehavior: normalizedStreamingBehavior
22078
22539
  });
22079
- if (!treatAsQueuedPrompt) {
22080
- awaitingFirstTurnRef.current = true;
22081
- setAwaitingFirstTurn(true);
22082
- }
22083
22540
  setIsSubmitting(true);
22084
22541
  replaceComposerDraft("", void 0, { forceSync: true });
22542
+ setComposerDiffLineComments([]);
22085
22543
  setComposerImages([]);
22086
22544
  lastSyncedEditorTextRef.current = "";
22087
22545
  try {
@@ -22098,14 +22556,16 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
22098
22556
  if (queuedPendingId) removeOptimisticPendingMessage(queuedPendingId);
22099
22557
  removeOptimisticUserMessage(optimisticId);
22100
22558
  if (!treatAsQueuedPrompt) {
22559
+ awaitingFirstTurnAssistantOutputSignatureRef.current = null;
22101
22560
  awaitingFirstTurnRef.current = false;
22102
22561
  setAwaitingFirstTurn(false);
22103
22562
  }
22104
22563
  if (!serializeComposerDraft({
22105
22564
  text: composerTextRef.current,
22106
22565
  skillName: composerSkillRef.current
22107
- }).trim()) {
22566
+ }).trim() && composerDiffLineCommentsRef.current.length === 0) {
22108
22567
  replaceComposerDraft(message);
22568
+ setComposerDiffLineComments((current) => current.length === 0 ? submittedDiffLineComments.map((comment) => ({ ...comment })) : current);
22109
22569
  setComposerImages((current) => current.length === 0 ? submittedImages.map((image) => ({ ...image })) : current);
22110
22570
  }
22111
22571
  toast.error(error instanceof Error ? error.message : "Failed to submit prompt");
@@ -22116,6 +22576,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
22116
22576
  }, [
22117
22577
  addOptimisticPendingMessage,
22118
22578
  addOptimisticUserMessage,
22579
+ composerDiffLineCommentsRef,
22119
22580
  composerImagesRef,
22120
22581
  composerSkillRef,
22121
22582
  composerTextRef,
@@ -22127,6 +22588,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
22127
22588
  removeOptimisticUserMessage,
22128
22589
  replaceComposerDraft,
22129
22590
  setAwaitingFirstTurn,
22591
+ setComposerDiffLineComments,
22130
22592
  setComposerImages,
22131
22593
  setIsSubmitting,
22132
22594
  viewerContextId
@@ -22205,7 +22667,7 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
22205
22667
  const currentSessionState = sessionStateRef.current;
22206
22668
  if (currentSessionState.sessionKey?.startsWith("optimistic:")) return;
22207
22669
  const currentOwnerKey = promptDraftKey(currentSessionState);
22208
- if (!currentSessionState.draft || currentOwnerKey !== draftSessionLoadingOwnerKey) return;
22670
+ if (!currentSessionState.draft || !promptDraftKeyMatchesOwner(currentOwnerKey, draftSessionLoadingOwnerKey)) return;
22209
22671
  draftSessionLoadingOwnerKeyRef.current = null;
22210
22672
  setDraftSessionLoadingOwnerKey(null);
22211
22673
  if (pendingDraftPromptRef.current?.ownerKey === draftSessionLoadingOwnerKey) flushPendingDraftPrompt(draftSessionLoadingOwnerKey);
@@ -22218,15 +22680,17 @@ function useAppShellPromptMutations({ viewerContextId, activeSessionId, defaultN
22218
22680
  ]);
22219
22681
  import_react.useEffect(() => {
22220
22682
  if (!awaitingFirstTurn) return;
22221
- const currentSessionState = sessionStateRef.current;
22222
- const hasAssistantOutput = currentSessionState.items.some((item) => item.kind === "assistant" && item.blocks.some((block) => block.type === "text" && block.text.trim()));
22223
- if (currentSessionState.streaming || hasAssistantOutput || pendingMessages.length > 0) {
22683
+ const baselineAssistantOutputSignature = awaitingFirstTurnAssistantOutputSignatureRef.current;
22684
+ const assistantOutputStarted = baselineAssistantOutputSignature !== null && sessionSnapshot.assistantOutputSignature !== baselineAssistantOutputSignature;
22685
+ if (sessionStateRef.current.streaming || assistantOutputStarted || pendingMessages.length > 0) {
22686
+ awaitingFirstTurnAssistantOutputSignatureRef.current = null;
22224
22687
  awaitingFirstTurnRef.current = false;
22225
22688
  setAwaitingFirstTurn(false);
22226
22689
  }
22227
22690
  }, [
22228
22691
  awaitingFirstTurn,
22229
22692
  pendingMessages.length,
22693
+ sessionSnapshot.assistantOutputSignature,
22230
22694
  sessionSnapshot.streaming,
22231
22695
  sessionStateRef,
22232
22696
  setAwaitingFirstTurn
@@ -23169,20 +23633,28 @@ function shouldPublishSessionState(previous, next) {
23169
23633
  if (previous === next) return false;
23170
23634
  return !sameSessionStateExceptConversation(previous, next) || !samePublishableUiState(previous.uiState, next.uiState) || previous.items.length === 0 !== (next.items.length === 0);
23171
23635
  }
23172
- function stateSyncPayloadConfirmsPendingDraft(payload, ownerKey) {
23173
- if (!ownerKey || payload.draft !== true) return false;
23636
+ function stateSyncPayloadDraftKey(payload) {
23174
23637
  return promptDraftKey({
23175
23638
  cwd: typeof payload.cwd === "string" ? payload.cwd : void 0,
23176
23639
  sessionFile: typeof payload.sessionFile === "string" ? payload.sessionFile : void 0,
23177
23640
  sessionId: typeof payload.sessionId === "string" ? payload.sessionId : void 0
23178
- }) === ownerKey;
23641
+ });
23642
+ }
23643
+ function stateSyncPayloadConfirmsPendingDraft(payload, ownerKey) {
23644
+ if (!ownerKey || payload.draft !== true) return false;
23645
+ return promptDraftKeyMatchesOwner(stateSyncPayloadDraftKey(payload), ownerKey);
23646
+ }
23647
+ function stateSyncPayloadConfirmsOptimisticDraft(payload, previousState) {
23648
+ if (payload.draft !== true) return false;
23649
+ return promptDraftKeyMatchesOwner(stateSyncPayloadDraftKey(payload), promptDraftKey(previousState));
23179
23650
  }
23180
23651
  function shouldIgnoreOptimisticDraftStateSync(options) {
23181
23652
  const previousSessionKey = options.previousState.sessionKey || "";
23182
23653
  const payloadSessionKey = typeof options.payload.sessionKey === "string" ? options.payload.sessionKey : "";
23183
23654
  if (!previousSessionKey.startsWith("optimistic:")) return false;
23184
23655
  if (!payloadSessionKey || payloadSessionKey === previousSessionKey) return false;
23185
- return !stateSyncPayloadConfirmsPendingDraft(options.payload, options.ownerKey);
23656
+ if (stateSyncPayloadConfirmsPendingDraft(options.payload, options.ownerKey) || stateSyncPayloadConfirmsOptimisticDraft(options.payload, options.previousState)) return false;
23657
+ return true;
23186
23658
  }
23187
23659
  function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadingOwnerKey, bootstrapSidebarDirectories, hideToolBlocksRef, sessionStore, sessionStateRef, composerTextRef, composerSkillRef, replaceComposerDraftRef, handleSelectSessionRef, pendingRouteSessionIdRef, pendingRouteSessionPathRef, setSessionState, setConversationItems, setHiddenThinkingPreview, setWorkingState, setCompactRunningState, setComposerContextUsage, setComposerStreaming, setSessionsEvent, setSessionDoneEvents, applySidebarSessionStatusRef, setComposerImages, setPendingMessages, pendingUiRequestHandlerRef, lastSyncedEditorTextRef }) {
23188
23660
  const queryClient = useQueryClient();
@@ -23191,6 +23663,7 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
23191
23663
  const draftSessionLoadingOwnerKeyRef = import_react.useRef(draftSessionLoadingOwnerKey);
23192
23664
  const currentSourceRef = import_react.useRef(null);
23193
23665
  const hasReceivedStateSyncRef = import_react.useRef(false);
23666
+ const [hasReceivedStateSync, setHasReceivedStateSync] = import_react.useState(false);
23194
23667
  const backgroundedAtRef = import_react.useRef(null);
23195
23668
  const wasBackgroundedRef = import_react.useRef(false);
23196
23669
  const [eventsReconnectNonce, setEventsReconnectNonce] = import_react.useState(0);
@@ -23205,8 +23678,8 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
23205
23678
  initialEventsSessionIdRef.current = sessionId;
23206
23679
  }, [sessionId]);
23207
23680
  const syncedSessionId = useSelector(sessionStore, (sessionState) => sessionState.sessionId);
23208
- const syncedSessionIdRef = import_react.useRef(syncedSessionId);
23209
23681
  const latestActivationRevisionRef = import_react.useRef(0);
23682
+ const sessionSelectNonceRef = import_react.useRef(Date.now() * 1e3);
23210
23683
  const observedRouteSessionIdRef = import_react.useRef(void 0);
23211
23684
  const syncedSessionDraft = useSelector(sessionStore, (sessionState) => sessionState.draft);
23212
23685
  import_react.useEffect(() => {
@@ -23320,7 +23793,10 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
23320
23793
  const activationRevision = payload.activationRevision;
23321
23794
  if (typeof activationRevision === "number" && activationRevision < latestActivationRevisionRef.current) return;
23322
23795
  if (typeof activationRevision === "number") latestActivationRevisionRef.current = activationRevision;
23323
- hasReceivedStateSyncRef.current = true;
23796
+ if (!hasReceivedStateSyncRef.current) {
23797
+ hasReceivedStateSyncRef.current = true;
23798
+ setHasReceivedStateSync(true);
23799
+ }
23324
23800
  const previousState = sessionStateRef.current;
23325
23801
  if (shouldIgnoreOptimisticDraftStateSync({
23326
23802
  ownerKey: draftSessionLoadingOwnerKeyRef.current,
@@ -23339,7 +23815,7 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
23339
23815
  const currentDraftOwnerKey = draftSessionLoadingOwnerKeyRef.current;
23340
23816
  const confirmedDraft = nextState.draft && !nextState.sessionKey?.startsWith("optimistic:");
23341
23817
  const previousWasOptimisticDraft = previousState.draft && previousState.sessionKey?.startsWith("optimistic:");
23342
- const draftMatchesPendingOwner = currentDraftOwnerKey ? promptDraftKey(nextState) === currentDraftOwnerKey : false;
23818
+ const draftMatchesPendingOwner = promptDraftKeyMatchesOwner(promptDraftKey(nextState), currentDraftOwnerKey);
23343
23819
  const shouldClearDraftRoute = Boolean(currentRouteSessionId && !pendingRouteSessionIdRef.current && confirmedDraft && (draftMatchesPendingOwner || previousWasOptimisticDraft));
23344
23820
  const preserveLocalPrompt = !sessionChanged && localPromptText !== previousEditorText;
23345
23821
  const nextPromptText = preserveLocalPrompt ? localPromptText : readStoredPromptDraft(nextState) ?? nextState.uiState.editorText ?? "";
@@ -23455,6 +23931,8 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
23455
23931
  import_react.useEffect(() => {
23456
23932
  if (!viewerContextId) return;
23457
23933
  hasReceivedStateSyncRef.current = false;
23934
+ setHasReceivedStateSync(false);
23935
+ latestActivationRevisionRef.current = 0;
23458
23936
  const gitInvalidationBatcher = new Batcher((items) => {
23459
23937
  const scopesByCwd = /* @__PURE__ */ new Map();
23460
23938
  for (const item of items) {
@@ -23528,21 +24006,23 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
23528
24006
  queryClient,
23529
24007
  viewerContextId
23530
24008
  ]);
23531
- import_react.useEffect(() => {
23532
- syncedSessionIdRef.current = syncedSessionId;
23533
- }, [syncedSessionId]);
23534
24009
  import_react.useEffect(() => {
23535
24010
  if (!viewerContextId || !sessionId) return;
23536
24011
  if (draftSessionLoadingOwnerKey) return;
23537
- if (!hasReceivedStateSyncRef.current) return;
23538
- if (sessionId === syncedSessionIdRef.current) return;
24012
+ if (!hasReceivedStateSync) return;
24013
+ if (sessionId === syncedSessionId) return;
23539
24014
  if (pendingRouteSessionIdRef.current !== sessionId) return;
23540
24015
  const abortController = new AbortController();
23541
24016
  const sessionPath = pendingRouteSessionPathRef.current;
24017
+ const selectionNonce = Math.max(sessionSelectNonceRef.current + 1, Date.now() * 1e3);
24018
+ sessionSelectNonceRef.current = selectionNonce;
23542
24019
  fetchJson(buildRequestUrl("/api/session/select", {
23543
24020
  contextId: viewerContextId,
23544
24021
  sessionId,
23545
- searchParams: { sessionPath }
24022
+ searchParams: {
24023
+ selectionNonce,
24024
+ sessionPath
24025
+ }
23546
24026
  }), {
23547
24027
  method: "POST",
23548
24028
  signal: abortController.signal
@@ -23555,8 +24035,10 @@ function useAppShellSessionSync({ viewerContextId, sessionId, draftSessionLoadin
23555
24035
  };
23556
24036
  }, [
23557
24037
  draftSessionLoadingOwnerKey,
24038
+ hasReceivedStateSync,
23558
24039
  pendingRouteSessionIdRef,
23559
24040
  pendingRouteSessionPathRef,
24041
+ syncedSessionId,
23560
24042
  viewerContextId,
23561
24043
  sessionId
23562
24044
  ]);
@@ -23965,6 +24447,8 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
23965
24447
  if (!composerImagesStoreRef.current) composerImagesStoreRef.current = createPicoStore([]);
23966
24448
  const composerImagesStore = composerImagesStoreRef.current;
23967
24449
  const composerImagesRef = import_react.useRef([]);
24450
+ const composerDiffLineCommentsRef = import_react.useRef([]);
24451
+ const [composerDiffLineComments, setComposerDiffLineCommentsState] = import_react.useState([]);
23968
24452
  const displaySettingsStoreRef = import_react.useRef(null);
23969
24453
  if (!displaySettingsStoreRef.current) displaySettingsStoreRef.current = createPicoStore({
23970
24454
  autoScrollEnabled: true,
@@ -24070,6 +24554,18 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
24070
24554
  const composerStoreRef = import_react.useRef(null);
24071
24555
  if (!composerStoreRef.current) composerStoreRef.current = createPicoStore(createInitialAppShellComposerSnapshot(viewerContextId), sameAppShellComposerSnapshot);
24072
24556
  const composerStore = composerStoreRef.current;
24557
+ const setComposerDiffLineComments = import_react.useCallback((action) => {
24558
+ const current = composerDiffLineCommentsRef.current;
24559
+ const next = applyStoreAction(current, action);
24560
+ if (next === current) return;
24561
+ composerDiffLineCommentsRef.current = next;
24562
+ setComposerDiffLineCommentsState(next);
24563
+ const currentComposerSnapshot = composerStore.state;
24564
+ setStoreState(composerStore, {
24565
+ ...currentComposerSnapshot,
24566
+ composerDiffLineComments: currentComposerSnapshot.disabled ? [] : next
24567
+ });
24568
+ }, [composerStore]);
24073
24569
  const contextUsageStoreRef = import_react.useRef(null);
24074
24570
  if (!contextUsageStoreRef.current) contextUsageStoreRef.current = createPicoStore(void 0);
24075
24571
  const contextUsageStore = contextUsageStoreRef.current;
@@ -24217,8 +24713,8 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
24217
24713
  ...currentComposerSnapshot,
24218
24714
  awaitingFirstTurn: currentComposerSnapshot.disabled ? false : next
24219
24715
  });
24220
- if (next && !sessionStateRef.current.streaming) setStoreState(workingStateStore, { label: "Waiting for first response…" });
24221
- else if (!next && workingStateStore.state?.label === "Waiting for first response…") setStoreState(workingStateStore, null);
24716
+ if (next && !sessionStateRef.current.streaming) setStoreState(workingStateStore, { label: "" });
24717
+ else if (!next && workingStateStore.state?.label === "") setStoreState(workingStateStore, null);
24222
24718
  });
24223
24719
  }, [
24224
24720
  awaitingFirstTurnStore,
@@ -24999,6 +25495,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
24999
25495
  pendingDraftFollowUps,
25000
25496
  awaitingFirstTurn,
25001
25497
  pendingMessages,
25498
+ composerDiffLineCommentsRef,
25002
25499
  composerImagesRef,
25003
25500
  composerTextRef,
25004
25501
  composerSkillRef,
@@ -25016,6 +25513,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25016
25513
  setPendingMessages,
25017
25514
  setAwaitingFirstTurn,
25018
25515
  setIsSubmitting,
25516
+ setComposerDiffLineComments,
25019
25517
  setComposerImages
25020
25518
  });
25021
25519
  const switchEmptyDraftDirectory = import_react.useCallback((directory) => {
@@ -25096,6 +25594,22 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25096
25594
  setPendingMessages,
25097
25595
  setSessionState
25098
25596
  ]);
25597
+ const createDiffLineCommentId = () => {
25598
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return `diff-comment:${crypto.randomUUID()}`;
25599
+ return `diff-comment:${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
25600
+ };
25601
+ const addComposerDiffLineComment = (comment) => {
25602
+ const text = comment.text.trim();
25603
+ const cwd = sessionStateRef.current.cwd?.trim() || "";
25604
+ if (!text || !cwd) return;
25605
+ setComposerDiffLineComments((current) => [...current, {
25606
+ ...comment,
25607
+ id: createDiffLineCommentId(),
25608
+ cwd,
25609
+ text
25610
+ }]);
25611
+ composerPanelRef.current?.focusPrompt({ preventScroll: true });
25612
+ };
25099
25613
  const onPickImages = async (files) => {
25100
25614
  if (!files || files.length === 0) return;
25101
25615
  const imageFiles = Array.from(files).filter((file) => file.type.startsWith("image/"));
@@ -25113,6 +25627,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25113
25627
  }));
25114
25628
  const currentPendingMessages = pendingDraftFollowUpMessages.length > 0 || pendingMessages.length > 0 ? [...pendingDraftFollowUpMessages, ...pendingMessages] : EMPTY_COMPOSER_PENDING_MESSAGES;
25115
25629
  const composerDisabled = isSessionViewLoading;
25630
+ const displayedDiffLineComments = composerDisabled ? EMPTY_COMPOSER_DIFF_LINE_COMMENTS : composerDiffLineComments;
25116
25631
  const displayedPendingMessages = composerDisabled ? EMPTY_COMPOSER_PENDING_MESSAGES : currentPendingMessages;
25117
25632
  const displayedComposerImages = composerDisabled ? EMPTY_COMPOSER_IMAGES : composerImages;
25118
25633
  const displayedComposerText = composerDisabled ? "" : composerDraftSeed.text;
@@ -25405,6 +25920,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25405
25920
  activeSessionId,
25406
25921
  awaitingFirstTurn: composerDisabled ? false : awaitingFirstTurn,
25407
25922
  centerMessages: displaySettingsRef.current.centerMessages,
25923
+ composerDiffLineComments: displayedDiffLineComments,
25408
25924
  composerImages: displayedComposerImages,
25409
25925
  composerSkill: displayedComposerSkill,
25410
25926
  composerSyncNonce: composerDraftSeed.syncNonce,
@@ -25421,6 +25937,9 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
25421
25937
  const composerActionsRef = useLatestRef({
25422
25938
  abortSession,
25423
25939
  onPickImages,
25940
+ removeDiffLineComment: (id) => {
25941
+ setComposerDiffLineComments((current) => current.filter((comment) => comment.id !== id));
25942
+ },
25424
25943
  onRemoveComposerImage: (index) => {
25425
25944
  setComposerImages((current) => current.filter((_, imageIndex) => imageIndex !== index));
25426
25945
  },
@@ -26012,6 +26531,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26012
26531
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppShellTabsController, {
26013
26532
  actionsRef: composerActionsRef,
26014
26533
  appUiStore,
26534
+ composerDiffLineComments: displayedDiffLineComments,
26015
26535
  composerPanelRef,
26016
26536
  contextUsageStore,
26017
26537
  conversationFrameRef,
@@ -26034,6 +26554,7 @@ function useAppShellSessionWorkspaceView({ viewerContextId, sessionId, onSelectS
26034
26554
  onCloseFileViewTab: closeFileViewTab,
26035
26555
  onCloseFileViewTabsToRight: closeFileViewTabsToRight,
26036
26556
  onCloseOtherFileViewTabs: closeOtherFileViewTabs,
26557
+ onAddDiffLineComment: addComposerDiffLineComment,
26037
26558
  onOpenFileViewTab: openFileViewTab,
26038
26559
  onReorderFileViewTabs: reorderFileViewTabs,
26039
26560
  rightSidebarStore,