@ottocode/web-sdk 0.1.295 → 0.1.296

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.
Files changed (170) hide show
  1. package/dist/components/branch/BranchModal.d.ts +1 -1
  2. package/dist/components/branch/BranchModal.d.ts.map +1 -1
  3. package/dist/components/browser/BrowserViewerPanel.d.ts +1 -1
  4. package/dist/components/browser/BrowserViewerPanel.d.ts.map +1 -1
  5. package/dist/components/chat/CommandSuggestionsPopup.d.ts +1 -1
  6. package/dist/components/chat/CommandSuggestionsPopup.d.ts.map +1 -1
  7. package/dist/components/chat/ConfigModal.d.ts +1 -1
  8. package/dist/components/chat/ConfigModal.d.ts.map +1 -1
  9. package/dist/components/chat/ConfigSelector.d.ts +1 -1
  10. package/dist/components/chat/ConfigSelector.d.ts.map +1 -1
  11. package/dist/components/chat/FileMentionPopup.d.ts +1 -1
  12. package/dist/components/chat/FileMentionPopup.d.ts.map +1 -1
  13. package/dist/components/chat/LiveWaveform.d.ts +1 -1
  14. package/dist/components/chat/LiveWaveform.d.ts.map +1 -1
  15. package/dist/components/chat/ReasoningTabs.d.ts +1 -1
  16. package/dist/components/chat/ReasoningTabs.d.ts.map +1 -1
  17. package/dist/components/chat/ShortcutsModal.d.ts +1 -1
  18. package/dist/components/chat/ShortcutsModal.d.ts.map +1 -1
  19. package/dist/components/chat/SkillMentionPopup.d.ts +1 -1
  20. package/dist/components/chat/SkillMentionPopup.d.ts.map +1 -1
  21. package/dist/components/chat/StopButton.d.ts +1 -1
  22. package/dist/components/chat/StopButton.d.ts.map +1 -1
  23. package/dist/components/common/FileTypeIcon.d.ts +1 -1
  24. package/dist/components/common/FileTypeIcon.d.ts.map +1 -1
  25. package/dist/components/dashboard/UsageDashboard.d.ts +1 -1
  26. package/dist/components/dashboard/UsageDashboard.d.ts.map +1 -1
  27. package/dist/components/git/GitBranchSwitcher.d.ts +1 -1
  28. package/dist/components/git/GitBranchSwitcher.d.ts.map +1 -1
  29. package/dist/components/git/GitCommitModal.d.ts +1 -1
  30. package/dist/components/git/GitCommitModal.d.ts.map +1 -1
  31. package/dist/components/git/GitCreateBranchModal.d.ts +1 -1
  32. package/dist/components/git/GitCreateBranchModal.d.ts.map +1 -1
  33. package/dist/components/git/GitDiffViewer.d.ts +1 -1
  34. package/dist/components/git/GitDiffViewer.d.ts.map +1 -1
  35. package/dist/components/git/GitFileItem.d.ts +1 -1
  36. package/dist/components/git/GitFileItem.d.ts.map +1 -1
  37. package/dist/components/git/GitFileList.d.ts +1 -1
  38. package/dist/components/git/GitFileList.d.ts.map +1 -1
  39. package/dist/components/git/GitFileTree.d.ts +1 -1
  40. package/dist/components/git/GitFileTree.d.ts.map +1 -1
  41. package/dist/components/index.js +988 -528
  42. package/dist/components/index.js.map +16 -16
  43. package/dist/components/messages/ActionToolBox.d.ts +1 -1
  44. package/dist/components/messages/ActionToolBox.d.ts.map +1 -1
  45. package/dist/components/messages/AssistantMessageGroup.d.ts +1 -0
  46. package/dist/components/messages/AssistantMessageGroup.d.ts.map +1 -1
  47. package/dist/components/messages/CompactActivityGroup.d.ts +1 -1
  48. package/dist/components/messages/CompactActivityGroup.d.ts.map +1 -1
  49. package/dist/components/messages/MessageThread.d.ts.map +1 -1
  50. package/dist/components/messages/renderers/ApplyPatchRenderer.d.ts +1 -1
  51. package/dist/components/messages/renderers/ApplyPatchRenderer.d.ts.map +1 -1
  52. package/dist/components/messages/renderers/BashRenderer.d.ts +1 -1
  53. package/dist/components/messages/renderers/BashRenderer.d.ts.map +1 -1
  54. package/dist/components/messages/renderers/CopyAttachmentRenderer.d.ts +1 -1
  55. package/dist/components/messages/renderers/CopyAttachmentRenderer.d.ts.map +1 -1
  56. package/dist/components/messages/renderers/CopyIntoRenderer.d.ts +1 -1
  57. package/dist/components/messages/renderers/CopyIntoRenderer.d.ts.map +1 -1
  58. package/dist/components/messages/renderers/DatabaseToolRenderer.d.ts +1 -1
  59. package/dist/components/messages/renderers/DatabaseToolRenderer.d.ts.map +1 -1
  60. package/dist/components/messages/renderers/DebugRenderer.d.ts +1 -1
  61. package/dist/components/messages/renderers/DebugRenderer.d.ts.map +1 -1
  62. package/dist/components/messages/renderers/DiffView.d.ts +1 -1
  63. package/dist/components/messages/renderers/DiffView.d.ts.map +1 -1
  64. package/dist/components/messages/renderers/ErrorRenderer.d.ts +2 -1
  65. package/dist/components/messages/renderers/ErrorRenderer.d.ts.map +1 -1
  66. package/dist/components/messages/renderers/FinishRenderer.d.ts +1 -1
  67. package/dist/components/messages/renderers/FinishRenderer.d.ts.map +1 -1
  68. package/dist/components/messages/renderers/GenericRenderer.d.ts +1 -1
  69. package/dist/components/messages/renderers/GenericRenderer.d.ts.map +1 -1
  70. package/dist/components/messages/renderers/GitCommitRenderer.d.ts +1 -1
  71. package/dist/components/messages/renderers/GitCommitRenderer.d.ts.map +1 -1
  72. package/dist/components/messages/renderers/GitDiffRenderer.d.ts +1 -1
  73. package/dist/components/messages/renderers/GitDiffRenderer.d.ts.map +1 -1
  74. package/dist/components/messages/renderers/GitStatusRenderer.d.ts +1 -1
  75. package/dist/components/messages/renderers/GitStatusRenderer.d.ts.map +1 -1
  76. package/dist/components/messages/renderers/ListRenderer.d.ts +1 -1
  77. package/dist/components/messages/renderers/ListRenderer.d.ts.map +1 -1
  78. package/dist/components/messages/renderers/LoadMcpToolsRenderer.d.ts +1 -1
  79. package/dist/components/messages/renderers/LoadMcpToolsRenderer.d.ts.map +1 -1
  80. package/dist/components/messages/renderers/LoadToolsRenderer.d.ts +1 -1
  81. package/dist/components/messages/renderers/LoadToolsRenderer.d.ts.map +1 -1
  82. package/dist/components/messages/renderers/McpToolRenderer.d.ts +1 -1
  83. package/dist/components/messages/renderers/McpToolRenderer.d.ts.map +1 -1
  84. package/dist/components/messages/renderers/ProgressUpdateRenderer.d.ts +1 -1
  85. package/dist/components/messages/renderers/ProgressUpdateRenderer.d.ts.map +1 -1
  86. package/dist/components/messages/renderers/ReadImageRenderer.d.ts +1 -1
  87. package/dist/components/messages/renderers/ReadImageRenderer.d.ts.map +1 -1
  88. package/dist/components/messages/renderers/ReadRenderer.d.ts +1 -1
  89. package/dist/components/messages/renderers/ReadRenderer.d.ts.map +1 -1
  90. package/dist/components/messages/renderers/ReasoningRenderer.d.ts +1 -1
  91. package/dist/components/messages/renderers/ReasoningRenderer.d.ts.map +1 -1
  92. package/dist/components/messages/renderers/SearchRenderer.d.ts +1 -1
  93. package/dist/components/messages/renderers/SearchRenderer.d.ts.map +1 -1
  94. package/dist/components/messages/renderers/SimulatorRenderer.d.ts +1 -1
  95. package/dist/components/messages/renderers/SimulatorRenderer.d.ts.map +1 -1
  96. package/dist/components/messages/renderers/SkillRenderer.d.ts +1 -1
  97. package/dist/components/messages/renderers/SkillRenderer.d.ts.map +1 -1
  98. package/dist/components/messages/renderers/TerminalRenderer.d.ts +1 -1
  99. package/dist/components/messages/renderers/TerminalRenderer.d.ts.map +1 -1
  100. package/dist/components/messages/renderers/TodosRenderer.d.ts +1 -1
  101. package/dist/components/messages/renderers/TodosRenderer.d.ts.map +1 -1
  102. package/dist/components/messages/renderers/ToolErrorDisplay.d.ts +1 -1
  103. package/dist/components/messages/renderers/ToolErrorDisplay.d.ts.map +1 -1
  104. package/dist/components/messages/renderers/TreeRenderer.d.ts +1 -1
  105. package/dist/components/messages/renderers/TreeRenderer.d.ts.map +1 -1
  106. package/dist/components/messages/renderers/WebSearchRenderer.d.ts +1 -1
  107. package/dist/components/messages/renderers/WebSearchRenderer.d.ts.map +1 -1
  108. package/dist/components/messages/renderers/WriteRenderer.d.ts +1 -1
  109. package/dist/components/messages/renderers/WriteRenderer.d.ts.map +1 -1
  110. package/dist/components/messages/renderers/index.d.ts +1 -1
  111. package/dist/components/messages/renderers/index.d.ts.map +1 -1
  112. package/dist/components/messages/renderers/shared/CopyButton.d.ts +1 -1
  113. package/dist/components/messages/renderers/shared/CopyButton.d.ts.map +1 -1
  114. package/dist/components/messages/renderers/shared/ImagePreview.d.ts +1 -1
  115. package/dist/components/messages/renderers/shared/ImagePreview.d.ts.map +1 -1
  116. package/dist/components/messages/renderers/shared/ToolContentBox.d.ts +1 -1
  117. package/dist/components/messages/renderers/shared/ToolContentBox.d.ts.map +1 -1
  118. package/dist/components/messages/renderers/shared/ToolHeader.d.ts +6 -6
  119. package/dist/components/messages/renderers/shared/ToolHeader.d.ts.map +1 -1
  120. package/dist/components/messages/threadDensity.d.ts +1 -1
  121. package/dist/components/messages/threadDensity.d.ts.map +1 -1
  122. package/dist/components/sessions/EditableTitle.d.ts +1 -1
  123. package/dist/components/sessions/EditableTitle.d.ts.map +1 -1
  124. package/dist/components/sessions/LeanHeader.d.ts +1 -1
  125. package/dist/components/sessions/LeanHeader.d.ts.map +1 -1
  126. package/dist/components/sessions/SessionHeader.d.ts +1 -1
  127. package/dist/components/sessions/SessionHeader.d.ts.map +1 -1
  128. package/dist/components/sessions/SessionItem.d.ts +1 -0
  129. package/dist/components/sessions/SessionItem.d.ts.map +1 -1
  130. package/dist/components/sessions/SessionListContainer.d.ts.map +1 -1
  131. package/dist/components/ui/CodeMirrorViewer.d.ts +1 -1
  132. package/dist/components/ui/CodeMirrorViewer.d.ts.map +1 -1
  133. package/dist/components/ui/ConfirmationDialog.d.ts +1 -1
  134. package/dist/components/ui/ConfirmationDialog.d.ts.map +1 -1
  135. package/dist/components/ui/Modal.d.ts +1 -1
  136. package/dist/components/ui/Modal.d.ts.map +1 -1
  137. package/dist/components/ui/SidebarHeader.d.ts +1 -1
  138. package/dist/components/ui/SidebarHeader.d.ts.map +1 -1
  139. package/dist/components/ui/StableSpinner.d.ts +1 -1
  140. package/dist/components/ui/StableSpinner.d.ts.map +1 -1
  141. package/dist/components/ui/Toaster.d.ts +1 -1
  142. package/dist/components/ui/Toaster.d.ts.map +1 -1
  143. package/dist/components/ui/ToolApprovalDialog.d.ts +1 -1
  144. package/dist/components/ui/ToolApprovalDialog.d.ts.map +1 -1
  145. package/dist/components/workspace/ToolActivityToggle.d.ts +1 -1
  146. package/dist/components/workspace/ToolActivityToggle.d.ts.map +1 -1
  147. package/dist/components/workspace/ToolPreviewPanel.d.ts +1 -1
  148. package/dist/components/workspace/ToolPreviewPanel.d.ts.map +1 -1
  149. package/dist/components/workspace/ViewerStatusBar.d.ts +4 -4
  150. package/dist/components/workspace/ViewerStatusBar.d.ts.map +1 -1
  151. package/dist/hooks/index.js +76 -39
  152. package/dist/hooks/index.js.map +8 -8
  153. package/dist/hooks/useFileUpload.d.ts.map +1 -1
  154. package/dist/hooks/useSessions.d.ts +4 -0
  155. package/dist/hooks/useSessions.d.ts.map +1 -1
  156. package/dist/hooks/useSimulator.d.ts.map +1 -1
  157. package/dist/index.js +1005 -544
  158. package/dist/index.js.map +16 -16
  159. package/dist/lib/api-client/index.d.ts.map +1 -1
  160. package/dist/lib/api-client/secure-input.d.ts.map +1 -1
  161. package/dist/lib/api-client/sessions.d.ts.map +1 -1
  162. package/dist/lib/api-client/usage.d.ts +9 -2
  163. package/dist/lib/api-client/usage.d.ts.map +1 -1
  164. package/dist/lib/api-client/utils.d.ts +12 -1
  165. package/dist/lib/api-client/utils.d.ts.map +1 -1
  166. package/dist/lib/index.js +40 -28
  167. package/dist/lib/index.js.map +6 -6
  168. package/dist/types/api.d.ts +2 -0
  169. package/dist/types/api.d.ts.map +1 -1
  170. package/package.json +5 -4
@@ -1336,6 +1336,8 @@ import {
1336
1336
  getSessionQueue as apiGetSessionQueue,
1337
1337
  removeFromQueue as apiRemoveFromQueue,
1338
1338
  retryMessage as apiRetryMessage,
1339
+ createSessionHandoff as apiCreateSessionHandoff,
1340
+ sendQueuedMessageNow as apiSendQueuedMessageNow,
1339
1341
  buildSessionStreamUrl
1340
1342
  } from "@ottocode/api";
1341
1343
 
@@ -1482,7 +1484,8 @@ function convertSession(apiSession) {
1482
1484
  title: apiSession.title ?? null,
1483
1485
  createdAt: typeof apiSession.createdAt === "string" ? new Date(apiSession.createdAt).getTime() : apiSession.createdAt,
1484
1486
  lastActiveAt: typeof apiSession.lastActiveAt === "string" ? new Date(apiSession.lastActiveAt).getTime() : apiSession.lastActiveAt,
1485
- lastViewedAt: typeof apiSession.lastViewedAt === "string" ? new Date(apiSession.lastViewedAt).getTime() : apiSession.lastViewedAt
1487
+ lastViewedAt: typeof apiSession.lastViewedAt === "string" ? new Date(apiSession.lastViewedAt).getTime() : apiSession.lastViewedAt,
1488
+ pinnedAt: typeof apiSession.pinnedAt === "string" ? new Date(apiSession.pinnedAt).getTime() : apiSession.pinnedAt
1486
1489
  };
1487
1490
  }
1488
1491
  function convertMessage(apiMessage) {
@@ -1545,10 +1548,10 @@ var sessionsMixin = {
1545
1548
  return response.data;
1546
1549
  },
1547
1550
  async createHandoff(sessionId) {
1548
- const response = await fetch(`${getBaseUrl()}/v1/sessions/${encodeURIComponent(sessionId)}/handoff`, { method: "POST" });
1549
- const data = await response.json().catch(() => null);
1550
- if (!response.ok)
1551
- throw new Error(extractErrorMessage(data));
1551
+ const response = await apiCreateSessionHandoff({ path: { sessionId } });
1552
+ if (response.error)
1553
+ throw new Error(extractErrorMessage(response.error));
1554
+ const data = response.data;
1552
1555
  if (!data?.session || !data?.sessionId) {
1553
1556
  throw new Error("No data returned from handoff");
1554
1557
  }
@@ -1560,14 +1563,18 @@ var sessionsMixin = {
1560
1563
  };
1561
1564
  },
1562
1565
  async abortSession(sessionId) {
1563
- const response = await apiAbortSession({ path: { sessionId } });
1566
+ const response = await apiAbortSession({
1567
+ path: { sessionId },
1568
+ body: {}
1569
+ });
1564
1570
  if (response.error)
1565
1571
  throw new Error(extractErrorMessage(response.error));
1566
1572
  return response.data;
1567
1573
  },
1568
1574
  async abortMessage(sessionId, _messageId) {
1569
1575
  const response = await apiAbortSession({
1570
- path: { sessionId }
1576
+ path: { sessionId },
1577
+ body: {}
1571
1578
  });
1572
1579
  if (response.error)
1573
1580
  throw new Error("Failed to abort message");
@@ -1588,11 +1595,12 @@ var sessionsMixin = {
1588
1595
  return response.data;
1589
1596
  },
1590
1597
  async sendQueuedMessageNow(sessionId, messageId) {
1591
- const response = await fetch(`${getBaseUrl()}/v1/sessions/${encodeURIComponent(sessionId)}/queue/${encodeURIComponent(messageId)}/send-now`, { method: "POST" });
1592
- const data = await response.json().catch(() => null);
1593
- if (!response.ok)
1594
- throw new Error(extractErrorMessage(data));
1595
- return data;
1598
+ const response = await apiSendQueuedMessageNow({
1599
+ path: { sessionId, messageId }
1600
+ });
1601
+ if (response.error)
1602
+ throw new Error(extractErrorMessage(response.error));
1603
+ return response.data;
1596
1604
  },
1597
1605
  async getMessages(sessionId) {
1598
1606
  const response = await apiListMessages({ path: { id: sessionId } });
@@ -2430,32 +2438,36 @@ var dictationMixin = {
2430
2438
  };
2431
2439
 
2432
2440
  // src/lib/api-client/secure-input.ts
2441
+ import {
2442
+ listPendingSecureInputs as apiListPendingSecureInputs,
2443
+ resolveSecureInput as apiResolveSecureInput
2444
+ } from "@ottocode/api";
2433
2445
  var secureInputMixin = {
2434
2446
  async submitSecureInput(sessionId, promptId, value) {
2435
- const response = await fetch(`${getBaseUrl()}/v1/sessions/${encodeURIComponent(sessionId)}/secure-input`, {
2436
- method: "POST",
2437
- headers: { "Content-Type": "application/json" },
2438
- body: JSON.stringify({ promptId, value })
2447
+ const response = await apiResolveSecureInput({
2448
+ path: { id: sessionId },
2449
+ body: { promptId, value }
2439
2450
  });
2440
- if (!response.ok)
2451
+ if (response.error)
2441
2452
  throw new Error("Failed to submit secure input");
2442
- return response.json();
2453
+ return response.data;
2443
2454
  },
2444
2455
  async cancelSecureInput(sessionId, promptId) {
2445
- const response = await fetch(`${getBaseUrl()}/v1/sessions/${encodeURIComponent(sessionId)}/secure-input`, {
2446
- method: "POST",
2447
- headers: { "Content-Type": "application/json" },
2448
- body: JSON.stringify({ promptId, cancelled: true })
2456
+ const response = await apiResolveSecureInput({
2457
+ path: { id: sessionId },
2458
+ body: { promptId, cancelled: true }
2449
2459
  });
2450
- if (!response.ok)
2460
+ if (response.error)
2451
2461
  throw new Error("Failed to cancel secure input");
2452
- return response.json();
2462
+ return response.data;
2453
2463
  },
2454
2464
  async getPendingSecureInputs(sessionId) {
2455
- const response = await fetch(`${getBaseUrl()}/v1/sessions/${encodeURIComponent(sessionId)}/secure-input/pending`);
2456
- if (!response.ok)
2465
+ const response = await apiListPendingSecureInputs({
2466
+ path: { id: sessionId }
2467
+ });
2468
+ if (response.error)
2457
2469
  throw new Error("Failed to get pending secure inputs");
2458
- return response.json();
2470
+ return response.data;
2459
2471
  }
2460
2472
  };
2461
2473
 
@@ -5989,6 +6001,32 @@ function useUpdateSession(sessionId) {
5989
6001
  }
5990
6002
  });
5991
6003
  }
6004
+ function useSetSessionPinned() {
6005
+ const queryClient = useQueryClient3();
6006
+ return useMutation3({
6007
+ mutationFn: ({
6008
+ sessionId,
6009
+ isPinned
6010
+ }) => apiClient.updateSession(sessionId, { isPinned }),
6011
+ onSuccess: async (updatedSession) => {
6012
+ queryClient.setQueryData(sessionsQueryKey, (old) => {
6013
+ if (!old)
6014
+ return old;
6015
+ return {
6016
+ ...old,
6017
+ pages: old.pages.map((page) => ({
6018
+ ...page,
6019
+ items: page.items.map((session) => session.id === updatedSession.id ? { ...session, ...updatedSession } : session)
6020
+ }))
6021
+ };
6022
+ });
6023
+ await queryClient.invalidateQueries({ queryKey: sessionsQueryKey });
6024
+ await queryClient.invalidateQueries({
6025
+ queryKey: ["session", updatedSession.id]
6026
+ });
6027
+ }
6028
+ });
6029
+ }
5992
6030
  function useMarkSessionViewed() {
5993
6031
  const queryClient = useQueryClient3();
5994
6032
  return useMutation3({
@@ -8149,6 +8187,7 @@ import {
8149
8187
  useCallback as useCallback16,
8150
8188
  useEffect as useEffect15
8151
8189
  } from "react";
8190
+ import { uploadAttachment } from "@ottocode/api";
8152
8191
  var IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
8153
8192
  var PDF_TYPES = ["application/pdf"];
8154
8193
  var TEXT_TYPES = [
@@ -8253,19 +8292,16 @@ async function fileToPreview(file) {
8253
8292
  });
8254
8293
  }
8255
8294
  async function uploadOriginalFile(file, sessionId) {
8256
- const form = new FormData;
8257
- form.set("file", file);
8258
- if (sessionId)
8259
- form.set("sessionId", sessionId);
8260
- const response = await fetch(`${getBaseUrl()}/v1/attachments`, {
8261
- method: "POST",
8262
- body: form
8295
+ const response = await uploadAttachment({
8296
+ body: {
8297
+ file,
8298
+ ...sessionId ? { sessionId } : {}
8299
+ }
8263
8300
  });
8264
- const data = await response.json().catch(() => null);
8265
- if (!response.ok) {
8266
- throw new Error(extractErrorMessage(data));
8301
+ if (response.error) {
8302
+ throw new Error(extractErrorMessage(response.error));
8267
8303
  }
8268
- return data;
8304
+ return response.data;
8269
8305
  }
8270
8306
  function useFileUpload(options = {}) {
8271
8307
  const {
@@ -10115,13 +10151,28 @@ var NewSessionLanding = memo11(forwardRef9(function NewSessionLanding2({ onSessi
10115
10151
  });
10116
10152
  }));
10117
10153
  // src/components/messages/MessageThread.tsx
10118
- import { useEffect as useEffect31, useRef as useRef20, useState as useState37, useMemo as useMemo20, memo as memo19, useCallback as useCallback23 } from "react";
10154
+ import {
10155
+ useEffect as useEffect32,
10156
+ useRef as useRef20,
10157
+ useState as useState37,
10158
+ useMemo as useMemo20,
10159
+ memo as memo19,
10160
+ useCallback as useCallback23,
10161
+ useLayoutEffect as useLayoutEffect4
10162
+ } from "react";
10119
10163
  import { ArrowDown } from "lucide-react";
10120
10164
  import { useQueryClient as useQueryClient12 } from "@tanstack/react-query";
10165
+ import { Virtuoso } from "react-virtuoso";
10121
10166
 
10122
10167
  // src/components/messages/AssistantMessageGroup.tsx
10123
10168
  import { AnimatePresence, motion as motion2 } from "motion/react";
10124
- import { memo as memo14, useState as useState32, useCallback as useCallback19, useMemo as useMemo16 } from "react";
10169
+ import {
10170
+ memo as memo14,
10171
+ useState as useState32,
10172
+ useCallback as useCallback19,
10173
+ useMemo as useMemo16,
10174
+ useEffect as useEffect27
10175
+ } from "react";
10125
10176
  import {
10126
10177
  Sparkles as Sparkles4,
10127
10178
  GitBranch as GitBranch5,
@@ -16701,6 +16752,7 @@ function CompactActivityGroup({
16701
16752
  const summaryText = [summaryTitle, ...summary.details].join(" · ");
16702
16753
  const hasReasoning = entries.some((e) => e.toolName === "reasoning");
16703
16754
  const lastEntry = entries.length > 0 ? entries[entries.length - 1] : null;
16755
+ const shouldRenderLiveEntries = !showSummary;
16704
16756
  useEffect25(() => {
16705
16757
  if (!collapsed) {
16706
16758
  setLatched(false);
@@ -16839,7 +16891,7 @@ function CompactActivityGroup({
16839
16891
  onMouseLeave: () => {
16840
16892
  hoveredRef.current = false;
16841
16893
  },
16842
- children: /* @__PURE__ */ jsx70("div", {
16894
+ children: shouldRenderLiveEntries && /* @__PURE__ */ jsx70("div", {
16843
16895
  ref: contentMeasureRef,
16844
16896
  className: "pt-2.5",
16845
16897
  children: entries.map((entry, i) => {
@@ -17665,7 +17717,7 @@ function BranchModal({
17665
17717
  }
17666
17718
 
17667
17719
  // src/components/messages/AssistantMessageGroup.tsx
17668
- import { jsx as jsx73, jsxs as jsxs62, Fragment as Fragment32 } from "react/jsx-runtime";
17720
+ import { jsx as jsx73, jsxs as jsxs62, Fragment as Fragment33 } from "react/jsx-runtime";
17669
17721
  var loadingMessages = [
17670
17722
  "Generating...",
17671
17723
  "Cooking up something...",
@@ -17685,9 +17737,106 @@ var STATUS_LINE_TOOL_NAMES = new Set([
17685
17737
  "update_status",
17686
17738
  "update_todos"
17687
17739
  ]);
17740
+ var AUTO_COMPACT_COMPLETED_PART_THRESHOLD = 60;
17741
+ var PART_WINDOW_RENDER_THRESHOLD = 90;
17742
+ var PART_WINDOW_HEAD_COUNT = 20;
17743
+ var PART_WINDOW_TAIL_COUNT = 48;
17688
17744
  function isStatusLineTool(toolName) {
17689
17745
  return STATUS_LINE_TOOL_NAMES.has(toolName || "");
17690
17746
  }
17747
+ function compareMessageParts(a, b) {
17748
+ const indexDiff = (a.index ?? 0) - (b.index ?? 0);
17749
+ if (indexDiff !== 0)
17750
+ return indexDiff;
17751
+ const stepDiff = (a.stepIndex ?? 0) - (b.stepIndex ?? 0);
17752
+ if (stepDiff !== 0)
17753
+ return stepDiff;
17754
+ return (a.startedAt ?? 0) - (b.startedAt ?? 0);
17755
+ }
17756
+ function areMessagePartsOrdered(parts) {
17757
+ for (let index = 1;index < parts.length; index++) {
17758
+ if (compareMessageParts(parts[index - 1], parts[index]) > 0) {
17759
+ return false;
17760
+ }
17761
+ }
17762
+ return true;
17763
+ }
17764
+ function getVisibleRenderItems(renderItems, showAllParts, messageStatus) {
17765
+ if (showAllParts || messageStatus === "pending" || renderItems.length <= PART_WINDOW_RENDER_THRESHOLD) {
17766
+ return {
17767
+ visibleRenderItems: renderItems.map((item, renderIndex) => ({
17768
+ item,
17769
+ renderIndex
17770
+ })),
17771
+ omittedRenderItemCount: 0
17772
+ };
17773
+ }
17774
+ const tailStart = Math.max(PART_WINDOW_HEAD_COUNT, renderItems.length - PART_WINDOW_TAIL_COUNT);
17775
+ const visibleRenderItems = [];
17776
+ for (let renderIndex = 0;renderIndex < PART_WINDOW_HEAD_COUNT; renderIndex++) {
17777
+ const item = renderItems[renderIndex];
17778
+ if (item)
17779
+ visibleRenderItems.push({ item, renderIndex });
17780
+ }
17781
+ for (let renderIndex = tailStart;renderIndex < renderItems.length; renderIndex++) {
17782
+ const item = renderItems[renderIndex];
17783
+ if (item)
17784
+ visibleRenderItems.push({ item, renderIndex });
17785
+ }
17786
+ return {
17787
+ visibleRenderItems,
17788
+ omittedRenderItemCount: Math.max(0, tailStart - PART_WINDOW_HEAD_COUNT)
17789
+ };
17790
+ }
17791
+ function getRenderItemKey(item) {
17792
+ return item.kind === "group" ? item.id : item.part.id;
17793
+ }
17794
+ function HiddenAssistantStepsRow({
17795
+ count,
17796
+ onShowAll,
17797
+ compact
17798
+ }) {
17799
+ const isCompactThread = useIsCompactThread();
17800
+ const isCompact = Boolean(compact || isCompactThread);
17801
+ return /* @__PURE__ */ jsxs62("div", {
17802
+ className: `flex ${isCompact ? "gap-1.5" : "gap-3"} pb-1.5 relative max-w-full overflow-hidden`,
17803
+ children: [
17804
+ /* @__PURE__ */ jsx73("div", {
17805
+ className: `flex-shrink-0 ${isCompact ? "w-4" : "w-6"} flex items-start justify-center relative`,
17806
+ children: /* @__PURE__ */ jsx73("div", {
17807
+ className: "absolute left-1/2 top-0 bottom-[-0.375rem] -translate-x-1/2 w-[2px] bg-border z-0",
17808
+ "aria-hidden": "true"
17809
+ })
17810
+ }),
17811
+ /* @__PURE__ */ jsx73("div", {
17812
+ className: "flex-1 min-w-0",
17813
+ children: /* @__PURE__ */ jsxs62("button", {
17814
+ type: "button",
17815
+ onClick: onShowAll,
17816
+ className: "inline-flex max-w-full items-center gap-1.5 py-0.5 text-xs text-muted-foreground/75 transition-colors hover:text-foreground",
17817
+ title: `Show ${count} hidden assistant steps`,
17818
+ children: [
17819
+ /* @__PURE__ */ jsx73("span", {
17820
+ className: "text-muted-foreground/45",
17821
+ children: "⋯"
17822
+ }),
17823
+ /* @__PURE__ */ jsxs62("span", {
17824
+ className: "truncate leading-5",
17825
+ children: [
17826
+ count,
17827
+ " earlier assistant steps collapsed"
17828
+ ]
17829
+ }),
17830
+ /* @__PURE__ */ jsx73("span", {
17831
+ className: "text-foreground/80 underline decoration-border underline-offset-2",
17832
+ children: "Show"
17833
+ })
17834
+ ]
17835
+ })
17836
+ })
17837
+ ]
17838
+ });
17839
+ }
17691
17840
  var AssistantMessageGroup = memo14(function AssistantMessageGroup2({
17692
17841
  sessionId,
17693
17842
  message,
@@ -17698,14 +17847,22 @@ var AssistantMessageGroup = memo14(function AssistantMessageGroup2({
17698
17847
  showBranchButton = true,
17699
17848
  onNavigateToSession,
17700
17849
  onRetry,
17701
- onCompact
17850
+ onCompact,
17851
+ isThreadScrolling = false
17702
17852
  }) {
17703
17853
  const { isQueued } = useMessageQueuePosition(sessionId, message.id);
17704
17854
  const isCompactDensity = useIsCompactThread();
17705
17855
  const isCompactThread = Boolean(compact || isCompactDensity);
17706
17856
  const [isHovered, setIsHovered] = useState32(false);
17857
+ const effectiveHovered = isHovered && !isThreadScrolling;
17707
17858
  const [showBranchModal, setShowBranchModal] = useState32(false);
17708
17859
  const [copied, setCopied] = useState32(false);
17860
+ const [showAllParts, setShowAllParts] = useState32(false);
17861
+ useEffect27(() => {
17862
+ if (!message.id)
17863
+ return;
17864
+ setShowAllParts(false);
17865
+ }, [message.id]);
17709
17866
  const { pendingApprovals, removePendingApproval } = useToolApprovalStore();
17710
17867
  const handleApprove = useCallback19(async (callId) => {
17711
17868
  if (!sessionId)
@@ -17744,16 +17901,10 @@ var AssistantMessageGroup = memo14(function AssistantMessageGroup2({
17744
17901
  }, [sessionId, messagePendingApprovals, removePendingApproval]);
17745
17902
  const parts = useMemo16(() => {
17746
17903
  const rawParts = message.parts || [];
17747
- return [...rawParts].sort((a, b) => {
17748
- const indexDiff = (a.index ?? 0) - (b.index ?? 0);
17749
- if (indexDiff !== 0)
17750
- return indexDiff;
17751
- const stepDiff = (a.stepIndex ?? 0) - (b.stepIndex ?? 0);
17752
- if (stepDiff !== 0)
17753
- return stepDiff;
17754
- return (a.startedAt ?? 0) - (b.startedAt ?? 0);
17755
- });
17904
+ return areMessagePartsOrdered(rawParts) ? rawParts : [...rawParts].sort(compareMessageParts);
17756
17905
  }, [message.parts]);
17906
+ const autoCompactActivity = message.status !== "pending" && parts.length >= AUTO_COMPACT_COMPLETED_PART_THRESHOLD;
17907
+ const shouldCompactActivity = Boolean(compact || autoCompactActivity);
17757
17908
  const hasFinish = parts.some((part) => part.toolName === "finish");
17758
17909
  const latestProgressUpdateIndex = parts.reduce((lastIndex, part, index) => part.type === "tool_result" && part.toolName === "progress_update" ? index : lastIndex, -1);
17759
17910
  const latestProgressUpdatePart = latestProgressUpdateIndex >= 0 ? parts[latestProgressUpdateIndex] : null;
@@ -17826,7 +17977,7 @@ var AssistantMessageGroup = memo14(function AssistantMessageGroup2({
17826
17977
  flushCompactBuffer();
17827
17978
  continue;
17828
17979
  }
17829
- if (compact && isCompactActivityPart(part)) {
17980
+ if (shouldCompactActivity && isCompactActivityPart(part)) {
17830
17981
  if (compactBuffer.length === 0) {
17831
17982
  bufferStartIndex = index;
17832
17983
  }
@@ -17838,7 +17989,8 @@ var AssistantMessageGroup = memo14(function AssistantMessageGroup2({
17838
17989
  }
17839
17990
  flushCompactBuffer();
17840
17991
  return items;
17841
- }, [parts, compact]);
17992
+ }, [parts, shouldCompactActivity]);
17993
+ const { visibleRenderItems, omittedRenderItemCount } = useMemo16(() => getVisibleRenderItems(renderItems, showAllParts, message.status), [renderItems, showAllParts, message.status]);
17842
17994
  const hasVisibleNonProgressParts = renderItems.length > 0;
17843
17995
  const firstVisiblePartIndex = parts.findIndex((part) => !isStatusLineTool(part.toolName));
17844
17996
  const shouldShowStatusLineToolCall = message.status === "pending" && !hasFinish && Boolean(latestStatusLineToolCallPart);
@@ -17878,12 +18030,67 @@ var AssistantMessageGroup = memo14(function AssistantMessageGroup2({
17878
18030
  exit: { opacity: 0, y: -6, filter: "blur(2px)" },
17879
18031
  transition: { duration: 0.16, ease: "easeOut" }
17880
18032
  };
18033
+ const renderAssistantRenderItem = (item, renderIndex) => {
18034
+ const hasFollowingContent = renderIndex < renderItems.length - 1 || hasNextAssistantMessage || shouldShowStatusLineToolCall || shouldShowProgressUpdate || shouldShowLoadingFallback;
18035
+ if (item.kind === "group") {
18036
+ return /* @__PURE__ */ jsx73(CompactActivityGroup, {
18037
+ entries: item.entries,
18038
+ titleOverride: item.titleOverride,
18039
+ showLine: hasFollowingContent,
18040
+ collapsed: message.status !== "pending" || renderIndex < renderItems.length - 1,
18041
+ compact: compact || autoCompactActivity
18042
+ });
18043
+ }
18044
+ const { part, index } = item;
18045
+ const isLastPart = index === parts.length - 1;
18046
+ const isActionTool = part.ephemeral && (part.type === "tool_call" || part.type === "tool_result") && [
18047
+ "shell",
18048
+ "bash",
18049
+ "edit",
18050
+ "multiedit",
18051
+ "write",
18052
+ "copy_into",
18053
+ "apply_patch",
18054
+ "terminal"
18055
+ ].includes(part.toolName || "");
18056
+ if (isActionTool) {
18057
+ return /* @__PURE__ */ jsx73(ActionToolBox, {
18058
+ part,
18059
+ showLine: hasFollowingContent,
18060
+ compact
18061
+ });
18062
+ }
18063
+ const pendingApproval = part.type === "tool_call" && part.toolCallId ? pendingApprovals.find((a) => a.callId === part.toolCallId) ?? null : null;
18064
+ if (part.type === "tool_result" && part.toolCallId && liveActionToolCallIds.has(part.toolCallId)) {
18065
+ return null;
18066
+ }
18067
+ const isFinishTool = part.type === "tool_result" && part.toolName === "finish";
18068
+ const showLine = hasFollowingContent && !isFinishTool;
18069
+ const isLastToolCall = part.type === "tool_call" && isLastPart;
18070
+ return /* @__PURE__ */ jsx73(MessagePartItem, {
18071
+ part,
18072
+ showLine,
18073
+ isFirstPart: index === firstVisiblePartIndex && !showHeader,
18074
+ isLastToolCall,
18075
+ onNavigateToSession,
18076
+ compact,
18077
+ pendingApproval,
18078
+ onApprove: handleApprove,
18079
+ onReject: handleReject,
18080
+ sessionId,
18081
+ onRetry,
18082
+ onCompact
18083
+ });
18084
+ };
17881
18085
  if (isQueued) {
17882
18086
  return null;
17883
18087
  }
17884
18088
  return /* @__PURE__ */ jsxs62("div", {
17885
18089
  className: "relative group",
17886
- onMouseEnter: () => setIsHovered(true),
18090
+ onMouseEnter: () => {
18091
+ if (!isThreadScrolling)
18092
+ setIsHovered(true);
18093
+ },
17887
18094
  onMouseLeave: () => setIsHovered(false),
17888
18095
  children: [
17889
18096
  showHeader && /* @__PURE__ */ jsxs62("div", {
@@ -17906,7 +18113,7 @@ var AssistantMessageGroup = memo14(function AssistantMessageGroup2({
17906
18113
  title: message.agent,
17907
18114
  children: message.agent
17908
18115
  }),
17909
- message.provider && /* @__PURE__ */ jsxs62(Fragment32, {
18116
+ message.provider && /* @__PURE__ */ jsxs62(Fragment33, {
17910
18117
  children: [
17911
18118
  message.agent && /* @__PURE__ */ jsx73("span", {
17912
18119
  className: "text-muted-foreground/50",
@@ -17919,7 +18126,7 @@ var AssistantMessageGroup = memo14(function AssistantMessageGroup2({
17919
18126
  })
17920
18127
  ]
17921
18128
  }),
17922
- message.model && /* @__PURE__ */ jsxs62(Fragment32, {
18129
+ message.model && /* @__PURE__ */ jsxs62(Fragment33, {
17923
18130
  children: [
17924
18131
  /* @__PURE__ */ jsx73("span", {
17925
18132
  className: "hidden md:inline text-muted-foreground/50",
@@ -17932,7 +18139,7 @@ var AssistantMessageGroup = memo14(function AssistantMessageGroup2({
17932
18139
  })
17933
18140
  ]
17934
18141
  }),
17935
- message.createdAt && /* @__PURE__ */ jsxs62(Fragment32, {
18142
+ message.createdAt && /* @__PURE__ */ jsxs62(Fragment33, {
17936
18143
  children: [
17937
18144
  /* @__PURE__ */ jsx73("span", {
17938
18145
  className: "text-muted-foreground/50",
@@ -17948,7 +18155,7 @@ var AssistantMessageGroup = memo14(function AssistantMessageGroup2({
17948
18155
  })
17949
18156
  ]
17950
18157
  }),
17951
- isHovered && isComplete && sessionId && showBranchButton && /* @__PURE__ */ jsx73("button", {
18158
+ effectiveHovered && isComplete && sessionId && showBranchButton && /* @__PURE__ */ jsx73("button", {
17952
18159
  type: "button",
17953
18160
  onClick: handleBranchClick,
17954
18161
  className: "ml-4 p-1.5 text-muted-foreground hover:text-primary transition-colors flex-shrink-0",
@@ -17962,57 +18169,18 @@ var AssistantMessageGroup = memo14(function AssistantMessageGroup2({
17962
18169
  /* @__PURE__ */ jsxs62("div", {
17963
18170
  className: "relative ml-1",
17964
18171
  children: [
17965
- renderItems.map((item, renderIndex) => {
17966
- const hasFollowingContent = renderIndex < renderItems.length - 1 || hasNextAssistantMessage || shouldShowStatusLineToolCall || shouldShowProgressUpdate || shouldShowLoadingFallback;
17967
- if (item.kind === "group") {
17968
- return /* @__PURE__ */ jsx73(CompactActivityGroup, {
17969
- entries: item.entries,
17970
- titleOverride: item.titleOverride,
17971
- showLine: hasFollowingContent,
17972
- collapsed: message.status !== "pending" || renderIndex < renderItems.length - 1,
17973
- compact
17974
- }, item.id);
17975
- }
17976
- const { part, index } = item;
17977
- const isLastPart = index === parts.length - 1;
17978
- const isActionTool = part.ephemeral && (part.type === "tool_call" || part.type === "tool_result") && [
17979
- "shell",
17980
- "bash",
17981
- "edit",
17982
- "multiedit",
17983
- "write",
17984
- "copy_into",
17985
- "apply_patch",
17986
- "terminal"
17987
- ].includes(part.toolName || "");
17988
- if (isActionTool) {
17989
- return /* @__PURE__ */ jsx73(ActionToolBox, {
17990
- part,
17991
- showLine: hasFollowingContent,
17992
- compact
17993
- }, part.id);
17994
- }
17995
- const pendingApproval = part.type === "tool_call" && part.toolCallId ? pendingApprovals.find((a) => a.callId === part.toolCallId) ?? null : null;
17996
- if (part.type === "tool_result" && part.toolCallId && liveActionToolCallIds.has(part.toolCallId)) {
17997
- return null;
17998
- }
17999
- const isFinishTool = part.type === "tool_result" && part.toolName === "finish";
18000
- const showLine = hasFollowingContent && !isFinishTool;
18001
- const isLastToolCall = part.type === "tool_call" && isLastPart;
18002
- return /* @__PURE__ */ jsx73(MessagePartItem, {
18003
- part,
18004
- showLine,
18005
- isFirstPart: index === firstVisiblePartIndex && !showHeader,
18006
- isLastToolCall,
18007
- onNavigateToSession,
18008
- compact,
18009
- pendingApproval,
18010
- onApprove: handleApprove,
18011
- onReject: handleReject,
18012
- sessionId,
18013
- onRetry,
18014
- onCompact
18015
- }, part.id);
18172
+ visibleRenderItems.map(({ item, renderIndex }, visibleIndex) => {
18173
+ const showPartWindowGap = omittedRenderItemCount > 0 && visibleIndex === PART_WINDOW_HEAD_COUNT;
18174
+ return /* @__PURE__ */ jsxs62(Fragment33, {
18175
+ children: [
18176
+ showPartWindowGap && /* @__PURE__ */ jsx73(HiddenAssistantStepsRow, {
18177
+ count: omittedRenderItemCount,
18178
+ onShowAll: () => setShowAllParts(true),
18179
+ compact
18180
+ }),
18181
+ renderAssistantRenderItem(item, renderIndex)
18182
+ ]
18183
+ }, getRenderItemKey(item));
18016
18184
  }),
18017
18185
  messagePendingApprovals.length > 1 && /* @__PURE__ */ jsxs62("div", {
18018
18186
  className: "flex items-center gap-3 py-2 px-3 my-2 bg-amber-50 dark:bg-amber-950/30 border border-amber-200 dark:border-amber-800 rounded-lg",
@@ -18106,7 +18274,7 @@ var AssistantMessageGroup = memo14(function AssistantMessageGroup2({
18106
18274
  }),
18107
18275
  isComplete && sessionId && /* @__PURE__ */ jsx73("div", {
18108
18276
  className: "grid ml-7 transition-[grid-template-rows] duration-200 ease-out",
18109
- style: { gridTemplateRows: isHovered ? "1fr" : "0fr" },
18277
+ style: { gridTemplateRows: effectiveHovered ? "1fr" : "0fr" },
18110
18278
  children: /* @__PURE__ */ jsx73("div", {
18111
18279
  className: "overflow-hidden",
18112
18280
  children: /* @__PURE__ */ jsxs62("div", {
@@ -18127,14 +18295,14 @@ var AssistantMessageGroup = memo14(function AssistantMessageGroup2({
18127
18295
  type: "button",
18128
18296
  onClick: handleCopy,
18129
18297
  className: "flex items-center gap-1.5 px-2 py-1 text-xs text-muted-foreground hover:text-foreground hover:bg-muted/50 rounded transition-colors",
18130
- children: copied ? /* @__PURE__ */ jsxs62(Fragment32, {
18298
+ children: copied ? /* @__PURE__ */ jsxs62(Fragment33, {
18131
18299
  children: [
18132
18300
  /* @__PURE__ */ jsx73(Check10, {
18133
18301
  className: "h-3 w-3 text-green-500"
18134
18302
  }),
18135
18303
  "Copied"
18136
18304
  ]
18137
- }) : /* @__PURE__ */ jsxs62(Fragment32, {
18305
+ }) : /* @__PURE__ */ jsxs62(Fragment33, {
18138
18306
  children: [
18139
18307
  /* @__PURE__ */ jsx73(Copy2, {
18140
18308
  className: "h-3 w-3"
@@ -18202,7 +18370,7 @@ function linkifyExplicitSkillMentions(content, skills) {
18202
18370
  }
18203
18371
 
18204
18372
  // src/components/messages/UserMessageGroup.tsx
18205
- import { jsx as jsx74, jsxs as jsxs63, Fragment as Fragment33 } from "react/jsx-runtime";
18373
+ import { jsx as jsx74, jsxs as jsxs63, Fragment as Fragment34 } from "react/jsx-runtime";
18206
18374
  var UserMessageGroup = memo15(function UserMessageGroup2({
18207
18375
  sessionId,
18208
18376
  message,
@@ -18307,7 +18475,7 @@ var UserMessageGroup = memo15(function UserMessageGroup2({
18307
18475
  console.error("Failed to delete queued message:", err);
18308
18476
  }
18309
18477
  };
18310
- return /* @__PURE__ */ jsxs63(Fragment33, {
18478
+ return /* @__PURE__ */ jsxs63(Fragment34, {
18311
18479
  children: [
18312
18480
  /* @__PURE__ */ jsx74("div", {
18313
18481
  className: "relative pb-8 pt-6",
@@ -18324,7 +18492,7 @@ var UserMessageGroup = memo15(function UserMessageGroup2({
18324
18492
  className: "font-medium text-emerald-700 dark:text-emerald-300 whitespace-nowrap",
18325
18493
  children: "You"
18326
18494
  }),
18327
- message.createdAt && /* @__PURE__ */ jsxs63(Fragment33, {
18495
+ message.createdAt && /* @__PURE__ */ jsxs63(Fragment34, {
18328
18496
  children: [
18329
18497
  /* @__PURE__ */ jsx74("span", {
18330
18498
  className: "text-muted-foreground/50",
@@ -18336,7 +18504,7 @@ var UserMessageGroup = memo15(function UserMessageGroup2({
18336
18504
  })
18337
18505
  ]
18338
18506
  }),
18339
- isQueued && /* @__PURE__ */ jsxs63(Fragment33, {
18507
+ isQueued && /* @__PURE__ */ jsxs63(Fragment34, {
18340
18508
  children: [
18341
18509
  /* @__PURE__ */ jsx74("span", {
18342
18510
  className: "text-muted-foreground/50",
@@ -18545,10 +18713,10 @@ import {
18545
18713
  } from "lucide-react";
18546
18714
 
18547
18715
  // src/hooks/useContainerWidth.ts
18548
- import { useEffect as useEffect27, useState as useState34 } from "react";
18716
+ import { useEffect as useEffect28, useState as useState34 } from "react";
18549
18717
  function useContainerWidth(ref) {
18550
18718
  const [width, setWidth] = useState34(0);
18551
- useEffect27(() => {
18719
+ useEffect28(() => {
18552
18720
  const el = ref.current;
18553
18721
  if (!el)
18554
18722
  return;
@@ -18582,7 +18750,7 @@ function ToolActivityToggle({
18582
18750
  }
18583
18751
 
18584
18752
  // src/components/sessions/EditableTitle.tsx
18585
- import { useState as useState35, useRef as useRef16, useEffect as useEffect28, useCallback as useCallback20 } from "react";
18753
+ import { useState as useState35, useRef as useRef16, useEffect as useEffect29, useCallback as useCallback20 } from "react";
18586
18754
  import { Pencil as Pencil2 } from "lucide-react";
18587
18755
  import { jsx as jsx76, jsxs as jsxs64 } from "react/jsx-runtime";
18588
18756
  function EditableTitle({
@@ -18594,13 +18762,13 @@ function EditableTitle({
18594
18762
  const [draft, setDraft] = useState35(title || "");
18595
18763
  const inputRef = useRef16(null);
18596
18764
  const { mutate: updateSession } = useUpdateSession(sessionId);
18597
- useEffect28(() => {
18765
+ useEffect29(() => {
18598
18766
  if (isEditing && inputRef.current) {
18599
18767
  inputRef.current.focus();
18600
18768
  inputRef.current.select();
18601
18769
  }
18602
18770
  }, [isEditing]);
18603
- useEffect28(() => {
18771
+ useEffect29(() => {
18604
18772
  setDraft(title || "");
18605
18773
  }, [title]);
18606
18774
  const save = useCallback20(() => {
@@ -18648,7 +18816,7 @@ function EditableTitle({
18648
18816
  }
18649
18817
 
18650
18818
  // src/components/sessions/SessionHeader.tsx
18651
- import { jsx as jsx77, jsxs as jsxs65, Fragment as Fragment34 } from "react/jsx-runtime";
18819
+ import { jsx as jsx77, jsxs as jsxs65, Fragment as Fragment35 } from "react/jsx-runtime";
18652
18820
  function SessionHeader({
18653
18821
  session,
18654
18822
  isGenerating,
@@ -18732,7 +18900,7 @@ function SessionHeader({
18732
18900
  className: "text-violet-600 dark:text-violet-400 font-medium",
18733
18901
  children: "Branch"
18734
18902
  }),
18735
- parentSession && /* @__PURE__ */ jsxs65(Fragment34, {
18903
+ parentSession && /* @__PURE__ */ jsxs65(Fragment35, {
18736
18904
  children: [
18737
18905
  /* @__PURE__ */ jsx77("span", {
18738
18906
  className: "text-muted-foreground",
@@ -19163,7 +19331,7 @@ var UsageModal = memo17(function UsageModal2() {
19163
19331
  });
19164
19332
 
19165
19333
  // src/hooks/useProviderUsage.ts
19166
- import { useEffect as useEffect29, useCallback as useCallback21, useRef as useRef18 } from "react";
19334
+ import { useEffect as useEffect30, useCallback as useCallback21, useRef as useRef18 } from "react";
19167
19335
  var POLL_INTERVAL = 60000;
19168
19336
  var STALE_THRESHOLD = 60000;
19169
19337
  var inflight = new Set;
@@ -19196,12 +19364,12 @@ function useProviderUsage(provider, authType) {
19196
19364
  }, [provider, isOAuthProvider, setUsage, setLoading, setLastFetched]);
19197
19365
  const fetchRef = useRef18(fetchUsage);
19198
19366
  fetchRef.current = fetchUsage;
19199
- useEffect29(() => {
19367
+ useEffect30(() => {
19200
19368
  if (!provider || !isOAuthProvider)
19201
19369
  return;
19202
19370
  fetchRef.current();
19203
19371
  }, [isOAuthProvider, provider]);
19204
- useEffect29(() => {
19372
+ useEffect30(() => {
19205
19373
  if (!provider || !isOAuthProvider || !isModalOpen || modalProvider !== provider) {
19206
19374
  return;
19207
19375
  }
@@ -19217,7 +19385,7 @@ function useProviderUsage(provider, authType) {
19217
19385
  }
19218
19386
 
19219
19387
  // src/hooks/useOttoRouterBalance.ts
19220
- import { useEffect as useEffect30, useCallback as useCallback22 } from "react";
19388
+ import { useEffect as useEffect31, useCallback as useCallback22 } from "react";
19221
19389
  function useOttoRouterBalance(providerName) {
19222
19390
  const setBalance = useOttoRouterStore((s) => s.setBalance);
19223
19391
  const setUsdcBalance = useOttoRouterStore((s) => s.setUsdcBalance);
@@ -19294,7 +19462,7 @@ function useOttoRouterBalance(providerName) {
19294
19462
  setUsage
19295
19463
  ]);
19296
19464
  const needsUsageWindows = subscription?.active && !subscription.usageWindows;
19297
- useEffect30(() => {
19465
+ useEffect31(() => {
19298
19466
  if (providerName === "ottorouter" && (balance === null || usdcBalance === null || needsUsageWindows)) {
19299
19467
  fetchBalance();
19300
19468
  }
@@ -19305,7 +19473,7 @@ function useOttoRouterBalance(providerName) {
19305
19473
  }
19306
19474
 
19307
19475
  // src/components/sessions/LeanHeader.tsx
19308
- import { jsx as jsx80, jsxs as jsxs68, Fragment as Fragment35 } from "react/jsx-runtime";
19476
+ import { jsx as jsx80, jsxs as jsxs68, Fragment as Fragment36 } from "react/jsx-runtime";
19309
19477
  function LeanHeader({
19310
19478
  session,
19311
19479
  isVisible,
@@ -19364,7 +19532,7 @@ function LeanHeader({
19364
19532
  const rootRef = useRef19(null);
19365
19533
  const width = useContainerWidth(rootRef);
19366
19534
  const isCompact = width > 0 && width < 640;
19367
- return /* @__PURE__ */ jsxs68(Fragment35, {
19535
+ return /* @__PURE__ */ jsxs68(Fragment36, {
19368
19536
  children: [
19369
19537
  /* @__PURE__ */ jsx80("div", {
19370
19538
  ref: rootRef,
@@ -19404,7 +19572,7 @@ function LeanHeader({
19404
19572
  })
19405
19573
  ]
19406
19574
  }),
19407
- isBranch && parentSession && /* @__PURE__ */ jsxs68(Fragment35, {
19575
+ isBranch && parentSession && /* @__PURE__ */ jsxs68(Fragment36, {
19408
19576
  children: [
19409
19577
  !isCompact && /* @__PURE__ */ jsx80("span", {
19410
19578
  className: "text-muted-foreground",
@@ -19681,6 +19849,8 @@ var TODO_TOOL_NAMES = new Set([
19681
19849
  "UpdateTodos",
19682
19850
  "UpdatePlan"
19683
19851
  ]);
19852
+ var TODO_SNAPSHOT_SCAN_MESSAGE_LIMIT = 12;
19853
+ var TODO_SNAPSHOT_SCAN_PART_LIMIT = 500;
19684
19854
  function parseToolResultContent(part) {
19685
19855
  if (part.contentJson && typeof part.contentJson === "object") {
19686
19856
  return part.contentJson;
@@ -19761,7 +19931,8 @@ function findLatestTodoSnapshot(messages, queuedMessageIds) {
19761
19931
  hasNewerUserMessage = true;
19762
19932
  }
19763
19933
  const parts = message?.parts ?? [];
19764
- for (let partIndex = parts.length - 1;partIndex >= 0; partIndex--) {
19934
+ const firstPartIndex = Math.max(0, parts.length - TODO_SNAPSHOT_SCAN_PART_LIMIT);
19935
+ for (let partIndex = parts.length - 1;partIndex >= firstPartIndex; partIndex--) {
19765
19936
  const part = parts[partIndex];
19766
19937
  if (part.type !== "tool_result")
19767
19938
  continue;
@@ -19781,6 +19952,136 @@ function findLatestTodoSnapshot(messages, queuedMessageIds) {
19781
19952
  }
19782
19953
  return null;
19783
19954
  }
19955
+ function getTodoSnapshotScanWindow(messages) {
19956
+ if (messages.length <= TODO_SNAPSHOT_SCAN_MESSAGE_LIMIT)
19957
+ return messages;
19958
+ return messages.slice(-TODO_SNAPSHOT_SCAN_MESSAGE_LIMIT);
19959
+ }
19960
+ function isVisibleThreadMessage(message) {
19961
+ return message.role !== "system" && !(message.role === "assistant" && message.status === "complete" && (message.parts?.length ?? 0) === 0);
19962
+ }
19963
+ function isPendingEmptyAssistant(message, currentMessageId) {
19964
+ return message.role === "assistant" && message.status === "pending" && (message.parts?.length ?? 0) === 0 && message.id !== currentMessageId;
19965
+ }
19966
+ function isActiveAssistantMessage(message, currentMessageId, queuedMessageIds) {
19967
+ return message.role === "assistant" && (message.id === currentMessageId || message.status === "pending" && !queuedMessageIds.has(message.id));
19968
+ }
19969
+ function filterThreadMessages(messages, currentMessageId, queueLength, queuedMessageIds) {
19970
+ const visibleMessages = messages.filter(isVisibleThreadMessage);
19971
+ const queueBusy = Boolean(currentMessageId) || queueLength > 0;
19972
+ if (!queueBusy)
19973
+ return visibleMessages;
19974
+ const nextAssistantByIndex = new Array(visibleMessages.length);
19975
+ let nextAssistant;
19976
+ for (let index = visibleMessages.length - 1;index >= 0; index--) {
19977
+ nextAssistantByIndex[index] = nextAssistant;
19978
+ const message = visibleMessages[index];
19979
+ if (message?.role === "assistant") {
19980
+ nextAssistant = message;
19981
+ }
19982
+ }
19983
+ const hasEarlierActiveAssistantByIndex = new Array(visibleMessages.length);
19984
+ let hasEarlierActiveAssistant = false;
19985
+ for (let index = 0;index < visibleMessages.length; index++) {
19986
+ hasEarlierActiveAssistantByIndex[index] = hasEarlierActiveAssistant;
19987
+ const message = visibleMessages[index];
19988
+ if (message && isActiveAssistantMessage(message, currentMessageId, queuedMessageIds)) {
19989
+ hasEarlierActiveAssistant = true;
19990
+ }
19991
+ }
19992
+ return visibleMessages.filter((message, index) => {
19993
+ if (message.role === "assistant") {
19994
+ return !isPendingEmptyAssistant(message, currentMessageId);
19995
+ }
19996
+ if (message.role !== "user")
19997
+ return true;
19998
+ const nextAssistant2 = nextAssistantByIndex[index];
19999
+ if (nextAssistant2) {
20000
+ const nextAssistantIsQueued = queuedMessageIds.has(nextAssistant2.id) || isPendingEmptyAssistant(nextAssistant2, currentMessageId);
20001
+ return !nextAssistantIsQueued;
20002
+ }
20003
+ return !hasEarlierActiveAssistantByIndex[index];
20004
+ });
20005
+ }
20006
+ var ThreadMessageRow = memo19(function ThreadMessageRow2({
20007
+ sessionId,
20008
+ message,
20009
+ previousMessage,
20010
+ nextMessage,
20011
+ isFirst,
20012
+ isLastMessage,
20013
+ currentMessageId,
20014
+ queueLength,
20015
+ compact,
20016
+ isThreadScrolling,
20017
+ onSelectSession,
20018
+ createRetryHandler,
20019
+ onCompact
20020
+ }) {
20021
+ const nextAssistantMessage = nextMessage && nextMessage.role === "assistant" ? nextMessage : undefined;
20022
+ const hasQueuedOrRunningLaterTurn = Boolean(currentMessageId && currentMessageId !== message.id);
20023
+ const canRetryTurn = message.role === "assistant" && isLastMessage && !hasQueuedOrRunningLaterTurn && queueLength === 0;
20024
+ const retryHandler = useMemo20(() => canRetryTurn ? createRetryHandler(message.id) : undefined, [canRetryTurn, createRetryHandler, message.id]);
20025
+ if (message.role === "user") {
20026
+ return /* @__PURE__ */ jsx82(UserMessageGroup, {
20027
+ sessionId,
20028
+ message,
20029
+ isFirst,
20030
+ nextAssistantMessageId: nextAssistantMessage?.id
20031
+ });
20032
+ }
20033
+ if (message.role === "assistant") {
20034
+ const showHeader = !previousMessage || previousMessage.role !== "assistant";
20035
+ const nextIsAssistant = Boolean(nextAssistantMessage);
20036
+ return /* @__PURE__ */ jsx82(AssistantMessageGroup, {
20037
+ sessionId,
20038
+ message,
20039
+ showHeader,
20040
+ hasNextAssistantMessage: nextIsAssistant,
20041
+ isLastMessage,
20042
+ onBranchCreated: onSelectSession,
20043
+ onNavigateToSession: onSelectSession,
20044
+ onRetry: retryHandler,
20045
+ compact,
20046
+ onCompact: isLastMessage ? onCompact : undefined,
20047
+ isThreadScrolling
20048
+ });
20049
+ }
20050
+ return null;
20051
+ });
20052
+ function ThreadVirtuosoHeader({ context }) {
20053
+ return /* @__PURE__ */ jsx82("div", {
20054
+ ref: context.sessionHeaderRef,
20055
+ children: context.session && /* @__PURE__ */ jsx82(SessionHeader, {
20056
+ session: context.session,
20057
+ isGenerating: context.isGenerating,
20058
+ onNavigateToSession: context.onSelectSession
20059
+ })
20060
+ });
20061
+ }
20062
+ function ThreadVirtuosoFooter({ context }) {
20063
+ return /* @__PURE__ */ jsx82("div", {
20064
+ className: context.footerBottomPaddingClass,
20065
+ children: context.showTopupApproval && context.pendingTopup && /* @__PURE__ */ jsx82("div", {
20066
+ className: context.rowOuterClass,
20067
+ children: /* @__PURE__ */ jsx82("div", {
20068
+ className: context.contentWidthClass,
20069
+ children: /* @__PURE__ */ jsx82("div", {
20070
+ className: "py-4",
20071
+ children: /* @__PURE__ */ jsx82(TopupApprovalCard, {
20072
+ pendingTopup: context.pendingTopup,
20073
+ onMethodSelected: () => context.clearPendingTopup(),
20074
+ onCancel: () => context.clearPendingTopup()
20075
+ })
20076
+ })
20077
+ })
20078
+ })
20079
+ });
20080
+ }
20081
+ var THREAD_VIRTUOSO_COMPONENTS = {
20082
+ Header: ThreadVirtuosoHeader,
20083
+ Footer: ThreadVirtuosoFooter
20084
+ };
19784
20085
  var MessageThread = memo19(function MessageThread2({
19785
20086
  messages,
19786
20087
  session,
@@ -19792,21 +20093,21 @@ var MessageThread = memo19(function MessageThread2({
19792
20093
  }) {
19793
20094
  const queryClient = useQueryClient12();
19794
20095
  const { preferences } = usePreferences();
19795
- const bottomRef = useRef20(null);
20096
+ const virtuosoRef = useRef20(null);
19796
20097
  const scrollContainerRef = useRef20(null);
19797
20098
  const sessionHeaderRef = useRef20(null);
19798
20099
  const threadRootRef = useRef20(null);
19799
20100
  const threadWidth = useContainerWidth(threadRootRef);
19800
20101
  const density = threadWidth > 0 && threadWidth < 640 ? "compact" : "normal";
19801
20102
  const [autoScroll, setAutoScroll] = useState37(true);
20103
+ const [isThreadScrolling, setIsThreadScrolling] = useState37(false);
19802
20104
  const autoScrollRef = useRef20(true);
19803
20105
  const [showLeanHeader, setShowLeanHeader] = useState37(false);
19804
20106
  const userScrollingRef = useRef20(false);
19805
20107
  const userScrollTimeoutRef = useRef20(undefined);
19806
- const targetScrollRef = useRef20(0);
19807
20108
  const animationFrameRef = useRef20(undefined);
19808
20109
  const initialScrollDoneRef = useRef20(false);
19809
- const lastSessionIdRef = useRef20(session?.id);
20110
+ const lastSessionIdRef = useRef20(sessionId);
19810
20111
  const prevMessagesLengthRef = useRef20(messages.length);
19811
20112
  const prevIsGeneratingRef = useRef20(isGenerating);
19812
20113
  const lastScrollHeightRef = useRef20(0);
@@ -19817,18 +20118,29 @@ var MessageThread = memo19(function MessageThread2({
19817
20118
  const queueState = useQueueState(sessionId);
19818
20119
  const queuedMessageIds = useMemo20(() => new Set(queueState.queuedMessages.map((item) => item.messageId)), [queueState.queuedMessages]);
19819
20120
  const showTopupApproval = pendingTopup && pendingTopup.sessionId === sessionId;
19820
- useEffect31(() => {
20121
+ const todoSnapshotScanMessages = useMemo20(() => getTodoSnapshotScanWindow(messages), [messages]);
20122
+ const latestTodoSnapshot = useMemo20(() => findLatestTodoSnapshot(todoSnapshotScanMessages, queuedMessageIds), [todoSnapshotScanMessages, queuedMessageIds]);
20123
+ const filteredMessages = useMemo20(() => {
20124
+ return filterThreadMessages(messages, queueState.currentMessageId, queueState.queueLength, queuedMessageIds);
20125
+ }, [
20126
+ messages,
20127
+ queueState.currentMessageId,
20128
+ queueState.queueLength,
20129
+ queuedMessageIds
20130
+ ]);
20131
+ useEffect32(() => {
19821
20132
  if (!sessionId)
19822
20133
  return;
19823
- setSessionTodos(sessionId, findLatestTodoSnapshot(messages, queuedMessageIds));
19824
- }, [messages, queuedMessageIds, sessionId, setSessionTodos]);
20134
+ if (latestTodoSnapshot || messages.length <= TODO_SNAPSHOT_SCAN_MESSAGE_LIMIT) {
20135
+ setSessionTodos(sessionId, latestTodoSnapshot);
20136
+ }
20137
+ }, [latestTodoSnapshot, messages.length, sessionId, setSessionTodos]);
19825
20138
  const handleScroll = useCallback23(() => {
19826
20139
  const container = scrollContainerRef.current;
19827
20140
  if (!container)
19828
20141
  return;
19829
20142
  const { scrollTop, scrollHeight, clientHeight } = container;
19830
20143
  const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
19831
- const scrollHeightIncreased = scrollHeight > lastScrollHeightRef.current;
19832
20144
  const userScrolledUp = scrollTop < lastScrollTopRef.current - 5;
19833
20145
  lastScrollHeightRef.current = scrollHeight;
19834
20146
  lastScrollTopRef.current = scrollTop;
@@ -19839,9 +20151,6 @@ var MessageThread = memo19(function MessageThread2({
19839
20151
  autoScrollRef.current = false;
19840
20152
  setAutoScroll(false);
19841
20153
  userScrollingRef.current = true;
19842
- } else if (!scrollHeightIncreased && autoScrollRef.current) {
19843
- autoScrollRef.current = false;
19844
- setAutoScroll(false);
19845
20154
  }
19846
20155
  if (userScrolledUp || !autoScrollRef.current && distanceFromBottom >= 100) {
19847
20156
  if (userScrollTimeoutRef.current) {
@@ -19858,23 +20167,54 @@ var MessageThread = memo19(function MessageThread2({
19858
20167
  setShowLeanHeader(headerRect.bottom < containerRect.top);
19859
20168
  }
19860
20169
  }, []);
19861
- useEffect31(() => {
20170
+ const scrollToThreadBottom = useCallback23((behavior = "auto") => {
20171
+ virtuosoRef.current?.scrollTo({
20172
+ top: Number.MAX_SAFE_INTEGER,
20173
+ behavior
20174
+ });
20175
+ }, []);
20176
+ const scheduleScrollToThreadBottom = useCallback23((behavior = "auto", frames = 2) => {
20177
+ if (animationFrameRef.current) {
20178
+ cancelAnimationFrame(animationFrameRef.current);
20179
+ }
20180
+ let remainingFrames = Math.max(1, frames);
20181
+ const tick = () => {
20182
+ scrollToThreadBottom(behavior);
20183
+ remainingFrames -= 1;
20184
+ if (remainingFrames > 0) {
20185
+ animationFrameRef.current = requestAnimationFrame(tick);
20186
+ return;
20187
+ }
20188
+ animationFrameRef.current = undefined;
20189
+ };
20190
+ animationFrameRef.current = requestAnimationFrame(tick);
20191
+ }, [scrollToThreadBottom]);
20192
+ useLayoutEffect4(() => {
19862
20193
  if (disableAutoScroll)
19863
20194
  return;
19864
- const sessionChanged = session?.id !== lastSessionIdRef.current;
19865
- lastSessionIdRef.current = session?.id;
20195
+ const sessionChanged = sessionId !== lastSessionIdRef.current;
20196
+ lastSessionIdRef.current = sessionId;
19866
20197
  if (sessionChanged) {
19867
20198
  initialScrollDoneRef.current = false;
20199
+ userScrollingRef.current = false;
20200
+ setIsThreadScrolling(false);
20201
+ lastScrollHeightRef.current = 0;
20202
+ lastScrollTopRef.current = 0;
20203
+ setShowLeanHeader(false);
19868
20204
  }
19869
- if (!initialScrollDoneRef.current && messages.length > 0) {
20205
+ if (!initialScrollDoneRef.current && filteredMessages.length > 0) {
19870
20206
  initialScrollDoneRef.current = true;
19871
- const container = scrollContainerRef.current;
19872
- if (container) {
19873
- container.scrollTop = container.scrollHeight;
19874
- }
20207
+ autoScrollRef.current = true;
20208
+ setAutoScroll(true);
20209
+ scheduleScrollToThreadBottom("auto", 6);
19875
20210
  }
19876
- }, [messages.length, session?.id, disableAutoScroll]);
19877
- useEffect31(() => {
20211
+ }, [
20212
+ filteredMessages.length,
20213
+ sessionId,
20214
+ disableAutoScroll,
20215
+ scheduleScrollToThreadBottom
20216
+ ]);
20217
+ useEffect32(() => {
19878
20218
  if (disableAutoScroll)
19879
20219
  return;
19880
20220
  const justStartedGenerating = isGenerating && !prevIsGeneratingRef.current;
@@ -19885,50 +20225,26 @@ var MessageThread = memo19(function MessageThread2({
19885
20225
  userScrollingRef.current = false;
19886
20226
  autoScrollRef.current = true;
19887
20227
  setAutoScroll(true);
19888
- requestAnimationFrame(() => {
19889
- requestAnimationFrame(() => {
19890
- const container = scrollContainerRef.current;
19891
- if (container) {
19892
- container.scrollTop = container.scrollHeight;
19893
- }
19894
- });
19895
- });
20228
+ scheduleScrollToThreadBottom("auto", 4);
19896
20229
  } else if (messagesAdded && !userScrollingRef.current && !isGenerating) {
19897
20230
  autoScrollRef.current = true;
19898
20231
  setAutoScroll(true);
20232
+ scheduleScrollToThreadBottom("auto", 2);
19899
20233
  }
19900
- }, [messages.length, isGenerating, disableAutoScroll]);
19901
- useEffect31(() => {
20234
+ }, [
20235
+ messages.length,
20236
+ isGenerating,
20237
+ disableAutoScroll,
20238
+ scheduleScrollToThreadBottom
20239
+ ]);
20240
+ useLayoutEffect4(() => {
19902
20241
  if (disableAutoScroll)
19903
20242
  return;
19904
- const container = scrollContainerRef.current;
19905
- if (!container || !autoScroll || userScrollingRef.current)
20243
+ if (!autoScrollRef.current || userScrollingRef.current)
19906
20244
  return;
19907
- targetScrollRef.current = container.scrollHeight - container.clientHeight;
19908
- const animate = () => {
19909
- const el = scrollContainerRef.current;
19910
- if (!el || userScrollingRef.current)
19911
- return;
19912
- const current = el.scrollTop;
19913
- const target = el.scrollHeight - el.clientHeight;
19914
- const diff = target - current;
19915
- if (Math.abs(diff) < 1) {
19916
- el.scrollTop = el.scrollHeight - el.clientHeight;
19917
- return;
19918
- }
19919
- if (Math.abs(diff) < 10) {
19920
- el.scrollTop = el.scrollHeight - el.clientHeight;
19921
- return;
19922
- }
19923
- el.scrollTop = current + diff * 0.15;
19924
- animationFrameRef.current = requestAnimationFrame(animate);
19925
- };
19926
- if (animationFrameRef.current) {
19927
- cancelAnimationFrame(animationFrameRef.current);
19928
- }
19929
- animationFrameRef.current = requestAnimationFrame(animate);
19930
- }, [messages, autoScroll]);
19931
- useEffect31(() => {
20245
+ scheduleScrollToThreadBottom("auto", 1);
20246
+ }, [messages, disableAutoScroll, scheduleScrollToThreadBottom]);
20247
+ useEffect32(() => {
19932
20248
  return () => {
19933
20249
  if (userScrollTimeoutRef.current) {
19934
20250
  clearTimeout(userScrollTimeoutRef.current);
@@ -19942,38 +20258,34 @@ var MessageThread = memo19(function MessageThread2({
19942
20258
  userScrollingRef.current = false;
19943
20259
  autoScrollRef.current = true;
19944
20260
  setAutoScroll(true);
19945
- const container = scrollContainerRef.current;
19946
- if (container) {
19947
- container.scrollTop = container.scrollHeight;
19948
- }
20261
+ scheduleScrollToThreadBottom("auto", 3);
19949
20262
  };
19950
- const filteredMessages = useMemo20(() => {
19951
- const visibleMessages = messages.filter((message) => message.role !== "system" && !(message.role === "assistant" && message.status === "complete" && (message.parts?.length ?? 0) === 0));
19952
- const queueBusy = Boolean(queueState.currentMessageId) || queueState.queueLength > 0;
19953
- if (!queueBusy)
19954
- return visibleMessages;
19955
- return visibleMessages.filter((message, index) => {
19956
- if (message.role === "assistant") {
19957
- const isPendingEmptyAssistant = message.status === "pending" && (message.parts?.length ?? 0) === 0 && message.id !== queueState.currentMessageId;
19958
- return !isPendingEmptyAssistant;
19959
- }
19960
- if (message.role !== "user")
19961
- return true;
19962
- const nextAssistant = visibleMessages.slice(index + 1).find((candidate) => candidate.role === "assistant");
19963
- if (nextAssistant) {
19964
- const nextAssistantIsQueued = queuedMessageIds.has(nextAssistant.id) || nextAssistant.status === "pending" && (nextAssistant.parts?.length ?? 0) === 0 && nextAssistant.id !== queueState.currentMessageId;
19965
- return !nextAssistantIsQueued;
19966
- }
19967
- const hasEarlierActiveAssistant = visibleMessages.slice(0, index).some((candidate) => candidate.role === "assistant" && (candidate.id === queueState.currentMessageId || candidate.status === "pending" && !queuedMessageIds.has(candidate.id)));
19968
- return !hasEarlierActiveAssistant;
19969
- });
19970
- }, [
19971
- messages,
19972
- queueState.currentMessageId,
19973
- queueState.queueLength,
19974
- queuedMessageIds
19975
- ]);
19976
20263
  const contentWidthClass = preferences.fullWidthContent ? compact ? "w-full space-y-4" : "w-full space-y-6" : compact ? "max-w-3xl mx-auto space-y-4" : "max-w-3xl mx-auto space-y-6";
20264
+ const rowOuterClass = density === "compact" ? "px-2 pb-3" : compact ? "px-4 pb-4" : "px-6 pb-6";
20265
+ const firstRowTopClass = density === "compact" ? "pt-3" : compact ? "pt-4" : "pt-6";
20266
+ const footerBottomPaddingClass = density === "compact" || compact ? "pb-80" : "pb-96";
20267
+ const virtuosoContext = useMemo20(() => ({
20268
+ session,
20269
+ isGenerating,
20270
+ onSelectSession,
20271
+ sessionHeaderRef,
20272
+ footerBottomPaddingClass,
20273
+ showTopupApproval: Boolean(showTopupApproval),
20274
+ pendingTopup,
20275
+ clearPendingTopup,
20276
+ rowOuterClass,
20277
+ contentWidthClass
20278
+ }), [
20279
+ session,
20280
+ isGenerating,
20281
+ onSelectSession,
20282
+ footerBottomPaddingClass,
20283
+ showTopupApproval,
20284
+ pendingTopup,
20285
+ clearPendingTopup,
20286
+ rowOuterClass,
20287
+ contentWidthClass
20288
+ ]);
19977
20289
  const createRetryHandler = useCallback23((messageId) => {
19978
20290
  return async () => {
19979
20291
  if (!sessionId)
@@ -20035,72 +20347,47 @@ var MessageThread = memo19(function MessageThread2({
20035
20347
  isGenerating,
20036
20348
  onNavigateToSession: onSelectSession
20037
20349
  }),
20038
- /* @__PURE__ */ jsxs70("div", {
20039
- ref: scrollContainerRef,
20040
- className: "flex-1 overflow-y-auto scrollbar-hide",
20350
+ /* @__PURE__ */ jsx82(Virtuoso, {
20351
+ ref: virtuosoRef,
20352
+ className: "flex-1 scrollbar-hide",
20353
+ data: filteredMessages,
20354
+ atBottomThreshold: 100,
20355
+ increaseViewportBy: { top: 2400, bottom: 1600 },
20356
+ minOverscanItemCount: { top: 4, bottom: 3 },
20357
+ initialTopMostItemIndex: {
20358
+ index: Math.max(0, filteredMessages.length - 1),
20359
+ align: "end"
20360
+ },
20361
+ followOutput: (isAtBottom) => autoScrollRef.current && isAtBottom ? "auto" : false,
20362
+ scrollerRef: (ref) => {
20363
+ scrollContainerRef.current = ref instanceof HTMLElement ? ref : null;
20364
+ },
20041
20365
  onScroll: handleScroll,
20042
- children: [
20043
- /* @__PURE__ */ jsx82("div", {
20044
- ref: sessionHeaderRef,
20045
- children: session && /* @__PURE__ */ jsx82(SessionHeader, {
20046
- session,
20047
- isGenerating,
20048
- onNavigateToSession: onSelectSession
20049
- })
20050
- }),
20051
- /* @__PURE__ */ jsx82("div", {
20052
- className: density === "compact" ? "px-2 pt-3 pb-80" : compact ? "p-4 pb-80" : "p-6 pb-96",
20053
- children: /* @__PURE__ */ jsxs70("div", {
20054
- className: contentWidthClass,
20055
- children: [
20056
- filteredMessages.map((message, idx) => {
20057
- const prevMessage = filteredMessages[idx - 1];
20058
- const nextMessage = filteredMessages[idx + 1];
20059
- const isLastMessage = idx === filteredMessages.length - 1;
20060
- if (message.role === "user") {
20061
- const nextAssistantMessage = nextMessage && nextMessage.role === "assistant" ? nextMessage : undefined;
20062
- return /* @__PURE__ */ jsx82(UserMessageGroup, {
20063
- sessionId,
20064
- message,
20065
- isFirst: idx === 0,
20066
- nextAssistantMessageId: nextAssistantMessage?.id
20067
- }, message.id);
20068
- }
20069
- if (message.role === "assistant") {
20070
- const showHeader = !prevMessage || prevMessage.role !== "assistant";
20071
- const nextIsAssistant = nextMessage && nextMessage.role === "assistant";
20072
- const hasQueuedOrRunningLaterTurn = Boolean(queueState.currentMessageId && queueState.currentMessageId !== message.id);
20073
- const canRetryTurn = isLastMessage && !hasQueuedOrRunningLaterTurn && queueState.queueLength === 0;
20074
- return /* @__PURE__ */ jsx82(AssistantMessageGroup, {
20075
- sessionId,
20076
- message,
20077
- showHeader,
20078
- hasNextAssistantMessage: nextIsAssistant,
20079
- isLastMessage,
20080
- onBranchCreated: onSelectSession,
20081
- onNavigateToSession: onSelectSession,
20082
- onRetry: canRetryTurn ? createRetryHandler(message.id) : undefined,
20083
- compact,
20084
- onCompact: isLastMessage ? handleCompact : undefined
20085
- }, message.id);
20086
- }
20087
- return null;
20088
- }),
20089
- showTopupApproval && pendingTopup && /* @__PURE__ */ jsx82("div", {
20090
- className: "py-4",
20091
- children: /* @__PURE__ */ jsx82(TopupApprovalCard, {
20092
- pendingTopup,
20093
- onMethodSelected: () => clearPendingTopup(),
20094
- onCancel: () => clearPendingTopup()
20095
- })
20096
- }),
20097
- /* @__PURE__ */ jsx82("div", {
20098
- ref: bottomRef
20099
- })
20100
- ]
20366
+ isScrolling: setIsThreadScrolling,
20367
+ computeItemKey: (_, message) => message.id,
20368
+ components: THREAD_VIRTUOSO_COMPONENTS,
20369
+ context: virtuosoContext,
20370
+ itemContent: (idx, message) => /* @__PURE__ */ jsx82("div", {
20371
+ className: `${rowOuterClass} ${idx === 0 ? firstRowTopClass : ""}`,
20372
+ children: /* @__PURE__ */ jsx82("div", {
20373
+ className: contentWidthClass,
20374
+ children: /* @__PURE__ */ jsx82(ThreadMessageRow, {
20375
+ sessionId,
20376
+ message,
20377
+ previousMessage: filteredMessages[idx - 1],
20378
+ nextMessage: filteredMessages[idx + 1],
20379
+ isFirst: idx === 0,
20380
+ isLastMessage: idx === filteredMessages.length - 1,
20381
+ currentMessageId: queueState.currentMessageId,
20382
+ queueLength: queueState.queueLength,
20383
+ compact,
20384
+ isThreadScrolling,
20385
+ onSelectSession,
20386
+ createRetryHandler,
20387
+ onCompact: handleCompact
20101
20388
  })
20102
20389
  })
20103
- ]
20390
+ })
20104
20391
  }),
20105
20392
  !autoScroll && /* @__PURE__ */ jsxs70("button", {
20106
20393
  type: "button",
@@ -20120,11 +20407,11 @@ var MessageThread = memo19(function MessageThread2({
20120
20407
  });
20121
20408
  });
20122
20409
  // src/components/messages/MessageThreadContainer.tsx
20123
- import { memo as memo22, useEffect as useEffect35, useMemo as useMemo21 } from "react";
20410
+ import { memo as memo22, useEffect as useEffect36, useMemo as useMemo21 } from "react";
20124
20411
  import { useQueryClient as useQueryClient14 } from "@tanstack/react-query";
20125
20412
 
20126
20413
  // src/hooks/useSessionStream.ts
20127
- import { useEffect as useEffect32, useRef as useRef21 } from "react";
20414
+ import { useEffect as useEffect33, useRef as useRef21 } from "react";
20128
20415
  import { useQueryClient as useQueryClient13 } from "@tanstack/react-query";
20129
20416
 
20130
20417
  // src/lib/sse-client.ts
@@ -20263,7 +20550,7 @@ function useSessionStream(sessionId, enabled = true) {
20263
20550
  setPendingApprovals
20264
20551
  } = useToolApprovalStore();
20265
20552
  const { addPendingInput, removePendingInput, setPendingInputs } = useSecureInputStore();
20266
- useEffect32(() => {
20553
+ useEffect33(() => {
20267
20554
  if (!sessionId || !enabled) {
20268
20555
  return;
20269
20556
  }
@@ -21629,7 +21916,7 @@ ${bestEffortUnescapeJsonString(rawTail)}`;
21629
21916
  }
21630
21917
 
21631
21918
  // src/hooks/useToolApprovalShortcuts.ts
21632
- import { useEffect as useEffect33, useCallback as useCallback24 } from "react";
21919
+ import { useEffect as useEffect34, useCallback as useCallback24 } from "react";
21633
21920
  function useToolApprovalShortcuts(sessionId) {
21634
21921
  const { pendingApprovals, removePendingApproval } = useToolApprovalStore();
21635
21922
  const sessionPendingApprovals = pendingApprovals;
@@ -21665,7 +21952,7 @@ function useToolApprovalShortcuts(sessionId) {
21665
21952
  console.error("Failed to approve all tool calls:", error);
21666
21953
  }
21667
21954
  }, [sessionId, sessionPendingApprovals, removePendingApproval]);
21668
- useEffect33(() => {
21955
+ useEffect34(() => {
21669
21956
  if (!sessionId || sessionPendingApprovals.length === 0)
21670
21957
  return;
21671
21958
  const handleKeyDown = (e) => {
@@ -21696,7 +21983,7 @@ function useToolApprovalShortcuts(sessionId) {
21696
21983
  }
21697
21984
 
21698
21985
  // src/components/settings/OttoRouterTopupModal.tsx
21699
- import { memo as memo21, useState as useState38, useEffect as useEffect34, useCallback as useCallback25, useRef as useRef22 } from "react";
21986
+ import { memo as memo21, useState as useState38, useEffect as useEffect35, useCallback as useCallback25, useRef as useRef22 } from "react";
21700
21987
  import { CreditCard as CreditCard3, Wallet as Wallet2, ExternalLink as ExternalLink7, RefreshCw as RefreshCw5 } from "lucide-react";
21701
21988
 
21702
21989
  // src/components/common/StatusIndicator.tsx
@@ -21822,7 +22109,7 @@ var StatusIndicator = memo20(function StatusIndicator2({
21822
22109
  });
21823
22110
 
21824
22111
  // src/components/settings/OttoRouterTopupModal.tsx
21825
- import { jsx as jsx84, jsxs as jsxs72, Fragment as Fragment36 } from "react/jsx-runtime";
22112
+ import { jsx as jsx84, jsxs as jsxs72, Fragment as Fragment37 } from "react/jsx-runtime";
21826
22113
  var PRESET_AMOUNTS = [10, 25, 50, 100];
21827
22114
  var MIN_AMOUNT = 5;
21828
22115
  var MIN_AMOUNT_RAZORPAY = 2;
@@ -21938,7 +22225,7 @@ var OttoRouterTopupModalContent = memo21(function OttoRouterTopupModalContent2()
21938
22225
  setIsLoadingEstimate(false);
21939
22226
  }
21940
22227
  }, [gateway, minAmount]);
21941
- useEffect34(() => {
22228
+ useEffect35(() => {
21942
22229
  if (effectiveAmount >= minAmount && view === "amount") {
21943
22230
  const timeout = setTimeout(() => fetchEstimate(effectiveAmount), 300);
21944
22231
  return () => clearTimeout(timeout);
@@ -21951,7 +22238,7 @@ var OttoRouterTopupModalContent = memo21(function OttoRouterTopupModalContent2()
21951
22238
  }
21952
22239
  setIsPolling(false);
21953
22240
  }, []);
21954
- useEffect34(() => {
22241
+ useEffect35(() => {
21955
22242
  return () => stopPolling();
21956
22243
  }, [stopPolling]);
21957
22244
  const startPolling = useCallback25((checkoutId) => {
@@ -22126,7 +22413,7 @@ var OttoRouterTopupModalContent = memo21(function OttoRouterTopupModalContent2()
22126
22413
  children: /* @__PURE__ */ jsxs72("div", {
22127
22414
  className: "space-y-6",
22128
22415
  children: [
22129
- view === "confirmed" && /* @__PURE__ */ jsxs72(Fragment36, {
22416
+ view === "confirmed" && /* @__PURE__ */ jsxs72(Fragment37, {
22130
22417
  children: [
22131
22418
  /* @__PURE__ */ jsx84("div", {
22132
22419
  className: "py-4",
@@ -22152,7 +22439,7 @@ var OttoRouterTopupModalContent = memo21(function OttoRouterTopupModalContent2()
22152
22439
  sublabel: "Do not close this page"
22153
22440
  })
22154
22441
  }),
22155
- view === "checkout" && checkoutInfo && /* @__PURE__ */ jsxs72(Fragment36, {
22442
+ view === "checkout" && checkoutInfo && /* @__PURE__ */ jsxs72(Fragment37, {
22156
22443
  children: [
22157
22444
  /* @__PURE__ */ jsxs72("div", {
22158
22445
  className: "py-4",
@@ -22212,7 +22499,7 @@ var OttoRouterTopupModalContent = memo21(function OttoRouterTopupModalContent2()
22212
22499
  })
22213
22500
  ]
22214
22501
  }),
22215
- view === "amount" && /* @__PURE__ */ jsxs72(Fragment36, {
22502
+ view === "amount" && /* @__PURE__ */ jsxs72(Fragment37, {
22216
22503
  children: [
22217
22504
  hasGoPlan && /* @__PURE__ */ jsx84("div", {
22218
22505
  className: "text-xs text-muted-foreground bg-muted/30 border border-border rounded-lg px-3 py-2",
@@ -22304,7 +22591,7 @@ var OttoRouterTopupModalContent = memo21(function OttoRouterTopupModalContent2()
22304
22591
  className: "overflow-hidden",
22305
22592
  children: /* @__PURE__ */ jsx84("div", {
22306
22593
  className: "space-y-3 py-4 border-t border-border",
22307
- children: isLoadingEstimate ? /* @__PURE__ */ jsxs72(Fragment36, {
22594
+ children: isLoadingEstimate ? /* @__PURE__ */ jsxs72(Fragment37, {
22308
22595
  children: [
22309
22596
  /* @__PURE__ */ jsxs72("div", {
22310
22597
  className: "flex justify-between items-center",
@@ -22343,7 +22630,7 @@ var OttoRouterTopupModalContent = memo21(function OttoRouterTopupModalContent2()
22343
22630
  ]
22344
22631
  })
22345
22632
  ]
22346
- }) : gateway === "polar" && estimate ? /* @__PURE__ */ jsxs72(Fragment36, {
22633
+ }) : gateway === "polar" && estimate ? /* @__PURE__ */ jsxs72(Fragment37, {
22347
22634
  children: [
22348
22635
  /* @__PURE__ */ jsxs72("div", {
22349
22636
  className: "flex justify-between items-center",
@@ -22394,7 +22681,7 @@ var OttoRouterTopupModalContent = memo21(function OttoRouterTopupModalContent2()
22394
22681
  ]
22395
22682
  })
22396
22683
  ]
22397
- }) : gateway === "razorpay" && razorpayEstimate ? /* @__PURE__ */ jsxs72(Fragment36, {
22684
+ }) : gateway === "razorpay" && razorpayEstimate ? /* @__PURE__ */ jsxs72(Fragment37, {
22398
22685
  children: [
22399
22686
  /* @__PURE__ */ jsxs72("div", {
22400
22687
  className: "flex justify-between items-center",
@@ -22532,12 +22819,12 @@ var OttoRouterTopupModalContent = memo21(function OttoRouterTopupModalContent2()
22532
22819
  });
22533
22820
 
22534
22821
  // src/components/messages/MessageThreadContainer.tsx
22535
- import { jsx as jsx85, jsxs as jsxs73, Fragment as Fragment37 } from "react/jsx-runtime";
22822
+ import { jsx as jsx85, jsxs as jsxs73, Fragment as Fragment38 } from "react/jsx-runtime";
22536
22823
  var MessageThreadContainer = memo22(function MessageThreadContainer2({
22537
22824
  sessionId,
22538
22825
  onSelectSession
22539
22826
  }) {
22540
- return /* @__PURE__ */ jsxs73(Fragment37, {
22827
+ return /* @__PURE__ */ jsxs73(Fragment38, {
22541
22828
  children: [
22542
22829
  /* @__PURE__ */ jsx85(SessionStreamController, {
22543
22830
  sessionId
@@ -22556,7 +22843,7 @@ var MessageThreadContainer = memo22(function MessageThreadContainer2({
22556
22843
  function SessionStreamController({ sessionId }) {
22557
22844
  const queryClient = useQueryClient14();
22558
22845
  useSessionStream(sessionId);
22559
- useEffect35(() => {
22846
+ useEffect36(() => {
22560
22847
  queryClient.invalidateQueries({ queryKey: ["messages", sessionId] });
22561
22848
  queryClient.invalidateQueries({ queryKey: ["queueState", sessionId] });
22562
22849
  queryClient.invalidateQueries({ queryKey: sessionsQueryKey });
@@ -22596,7 +22883,7 @@ var MessageThreadData = memo22(function MessageThreadData2({
22596
22883
  });
22597
22884
  // src/components/sessions/SessionItem.tsx
22598
22885
  import { memo as memo23 } from "react";
22599
- import { CircleCheck } from "lucide-react";
22886
+ import { CircleCheck, Pin } from "lucide-react";
22600
22887
 
22601
22888
  // src/components/sessions/session-time.ts
22602
22889
  var DAY_IN_MS = 24 * 60 * 60 * 1000;
@@ -22645,10 +22932,12 @@ import { jsx as jsx86, jsxs as jsxs74 } from "react/jsx-runtime";
22645
22932
  var SessionItem = memo23(function SessionItem2({
22646
22933
  session,
22647
22934
  isActive,
22648
- onClick
22935
+ onClick,
22936
+ onTogglePinned
22649
22937
  }) {
22650
22938
  const title = session.title || `Session ${session.id.slice(0, 8)}`;
22651
22939
  const isRunning = session.isRunning ?? false;
22940
+ const isPinned = session.pinnedAt != null;
22652
22941
  const lastUpdatedAt = session.lastActiveAt ?? session.createdAt;
22653
22942
  const isReadyForReview = !isRunning && lastUpdatedAt > (session.lastViewedAt ?? 0);
22654
22943
  const metadata = formatRelativeSessionTime(lastUpdatedAt);
@@ -22661,9 +22950,7 @@ var SessionItem = memo23(function SessionItem2({
22661
22950
  }) : isReadyForReview ? /* @__PURE__ */ jsx86(CircleCheck, {
22662
22951
  className: "h-4 w-4"
22663
22952
  }) : null;
22664
- return /* @__PURE__ */ jsxs74("button", {
22665
- type: "button",
22666
- onClick,
22953
+ return /* @__PURE__ */ jsxs74("div", {
22667
22954
  className: `group flex w-full items-start gap-2 px-4 py-3 text-left transition-colors duration-150 ${isActive ? "bg-black/[0.08] text-sidebar-foreground dark:bg-white/[0.08]" : "text-sidebar-foreground hover:bg-black/[0.05] dark:hover:bg-white/[0.055]"}`,
22668
22955
  title: `${title} — ${metadata}`,
22669
22956
  children: [
@@ -22674,12 +22961,31 @@ var SessionItem = memo23(function SessionItem2({
22674
22961
  /* @__PURE__ */ jsxs74("span", {
22675
22962
  className: "block min-w-0 flex-1",
22676
22963
  children: [
22677
- /* @__PURE__ */ jsx86("span", {
22678
- className: `block min-w-0 truncate text-[13px] leading-5 ${isActive ? "font-medium" : "font-normal"}`,
22679
- children: title
22680
- }),
22681
22964
  /* @__PURE__ */ jsxs74("span", {
22682
- className: "mt-0.5 flex items-center justify-between gap-3 text-[11px] leading-4 text-sidebar-muted-foreground",
22965
+ className: "flex min-w-0 items-center",
22966
+ children: [
22967
+ /* @__PURE__ */ jsx86("button", {
22968
+ type: "button",
22969
+ onClick,
22970
+ className: `block min-w-0 flex-1 truncate text-left text-[13px] leading-5 ${isActive ? "font-medium" : "font-normal"}`,
22971
+ children: title
22972
+ }),
22973
+ /* @__PURE__ */ jsx86("button", {
22974
+ type: "button",
22975
+ onClick: onTogglePinned,
22976
+ className: `flex h-5 shrink-0 items-center justify-center overflow-hidden rounded text-sidebar-muted-foreground transition-all duration-150 ease-out hover:text-sidebar-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-sidebar-ring/50 ${isPinned ? "ml-1 w-5 translate-x-0 opacity-100 text-sidebar-foreground" : "ml-0 w-0 translate-x-1 opacity-0 group-hover:ml-1 group-hover:w-5 group-hover:translate-x-0 group-hover:opacity-100 group-focus-within:ml-1 group-focus-within:w-5 group-focus-within:translate-x-0 group-focus-within:opacity-100"}`,
22977
+ "aria-label": isPinned ? "Unpin session" : "Pin session",
22978
+ title: isPinned ? "Unpin session" : "Pin session",
22979
+ children: /* @__PURE__ */ jsx86(Pin, {
22980
+ className: `h-3.5 w-3.5 ${isPinned ? "fill-current" : ""}`
22981
+ })
22982
+ })
22983
+ ]
22984
+ }),
22985
+ /* @__PURE__ */ jsxs74("button", {
22986
+ type: "button",
22987
+ onClick,
22988
+ className: "mt-0.5 flex w-full items-center justify-between gap-3 text-left text-[11px] leading-4 text-sidebar-muted-foreground",
22683
22989
  children: [
22684
22990
  /* @__PURE__ */ jsx86("span", {
22685
22991
  className: "min-w-0 flex-1 truncate",
@@ -22704,7 +23010,7 @@ var SessionItem = memo23(function SessionItem2({
22704
23010
  });
22705
23011
  });
22706
23012
  // src/components/sessions/SessionListContainer.tsx
22707
- import { memo as memo24, useMemo as useMemo22, useCallback as useCallback26, useEffect as useEffect36, useRef as useRef23 } from "react";
23013
+ import { memo as memo24, useMemo as useMemo22, useCallback as useCallback26, useEffect as useEffect37, useRef as useRef23 } from "react";
22708
23014
 
22709
23015
  // src/stores/focusStore.ts
22710
23016
  import { create as create21 } from "zustand";
@@ -22727,6 +23033,7 @@ var SessionListRow = memo24(function SessionListRow2({
22727
23033
  snapshot,
22728
23034
  isActive,
22729
23035
  onSelectSession,
23036
+ onTogglePinned,
22730
23037
  registerItem
22731
23038
  }) {
22732
23039
  const setRef = useCallback26((element) => {
@@ -22735,12 +23042,16 @@ var SessionListRow = memo24(function SessionListRow2({
22735
23042
  const handleClick = useCallback26(() => {
22736
23043
  onSelectSession(snapshot.id);
22737
23044
  }, [onSelectSession, snapshot.id]);
23045
+ const handleTogglePinned = useCallback26(() => {
23046
+ onTogglePinned(snapshot.id, snapshot.pinnedAt == null);
23047
+ }, [onTogglePinned, snapshot.id, snapshot.pinnedAt]);
22738
23048
  return /* @__PURE__ */ jsx87("div", {
22739
23049
  ref: setRef,
22740
23050
  children: /* @__PURE__ */ jsx87(SessionItem, {
22741
23051
  session,
22742
23052
  isActive,
22743
- onClick: handleClick
23053
+ onClick: handleClick,
23054
+ onTogglePinned: handleTogglePinned
22744
23055
  })
22745
23056
  });
22746
23057
  });
@@ -22751,7 +23062,7 @@ function SessionFocusController({
22751
23062
  const currentFocus = useFocusStore((state) => state.currentFocus);
22752
23063
  const sessionIndex = useFocusStore((state) => state.sessionIndex);
22753
23064
  const previousFocusedSessionId = useRef23(null);
22754
- useEffect36(() => {
23065
+ useEffect37(() => {
22755
23066
  const previousId = previousFocusedSessionId.current;
22756
23067
  if (previousId) {
22757
23068
  const previousElement = itemRefs.current?.get(previousId);
@@ -22792,6 +23103,7 @@ var SessionListContainer = memo24(function SessionListContainer2({
22792
23103
  const previousActiveSessionId = useRef23(activeSessionId);
22793
23104
  const runningOrderRef = useRef23([]);
22794
23105
  const markSessionViewed = useMarkSessionViewed();
23106
+ const setSessionPinned = useSetSessionPinned();
22795
23107
  const handleSessionClick = useCallback26((sessionId) => {
22796
23108
  lastScrolledSessionId.current = sessionId;
22797
23109
  onSelectSession(sessionId);
@@ -22802,6 +23114,9 @@ var SessionListContainer = memo24(function SessionListContainer2({
22802
23114
  else
22803
23115
  itemRefs.current.delete(sessionId);
22804
23116
  }, []);
23117
+ const handleTogglePinned = useCallback26((sessionId, isPinned) => {
23118
+ setSessionPinned.mutate({ sessionId, isPinned });
23119
+ }, [setSessionPinned]);
22805
23120
  const sessionSnapshot = useMemo22(() => {
22806
23121
  return sessions.map((s, index) => ({
22807
23122
  id: s.id,
@@ -22812,22 +23127,27 @@ var SessionListContainer = memo24(function SessionListContainer2({
22812
23127
  createdAt: s.createdAt,
22813
23128
  lastActiveAt: s.lastActiveAt,
22814
23129
  lastViewedAt: s.lastViewedAt ?? null,
23130
+ pinnedAt: s.pinnedAt ?? null,
22815
23131
  activityAt: s.lastActiveAt ?? s.createdAt
22816
23132
  }));
22817
23133
  }, [sessions]);
22818
23134
  const sessionMap = useMemo22(() => new Map(sessions.map((session) => [session.id, session])), [sessions]);
22819
23135
  const sessionSnapshotMap = useMemo22(() => new Map(sessionSnapshot.map((session) => [session.id, session])), [sessionSnapshot]);
22820
23136
  const runningSessions = useMemo22(() => {
22821
- const runningSessionMap = new Map(sessionSnapshot.filter((session) => session.isRunning).map((session) => [session.id, session]));
23137
+ const runningSessionMap = new Map(sessionSnapshot.filter((session) => session.isRunning && session.pinnedAt == null).map((session) => [session.id, session]));
22822
23138
  runningOrderRef.current = runningOrderRef.current.filter((id) => runningSessionMap.has(id));
22823
23139
  for (const session of sessionSnapshot) {
22824
- if (session.isRunning && !runningOrderRef.current.includes(session.id)) {
23140
+ if (session.isRunning && session.pinnedAt == null && !runningOrderRef.current.includes(session.id)) {
22825
23141
  runningOrderRef.current.push(session.id);
22826
23142
  }
22827
23143
  }
22828
23144
  return runningOrderRef.current.map((id) => runningSessionMap.get(id)).filter((session) => Boolean(session));
22829
23145
  }, [sessionSnapshot]);
22830
23146
  const runningSessionIds = useMemo22(() => new Set(runningSessions.map((session) => session.id)), [runningSessions]);
23147
+ const pinnedGroups = useMemo22(() => {
23148
+ const pinnedSessions = sessionSnapshot.filter((session) => session.pinnedAt != null);
23149
+ return pinnedSessions.length > 0 ? [{ label: "Pinned", sessions: pinnedSessions }] : [];
23150
+ }, [sessionSnapshot]);
22831
23151
  const statusGroups = useMemo22(() => [
22832
23152
  ...runningSessions.length > 0 ? [
22833
23153
  {
@@ -22839,6 +23159,8 @@ var SessionListContainer = memo24(function SessionListContainer2({
22839
23159
  const recentGroups = useMemo22(() => {
22840
23160
  const groups = new Map;
22841
23161
  for (const session of sessionSnapshot) {
23162
+ if (session.pinnedAt != null)
23163
+ continue;
22842
23164
  if (runningSessionIds.has(session.id))
22843
23165
  continue;
22844
23166
  const label = getSessionGroupLabel(session.activityAt);
@@ -22865,14 +23187,14 @@ var SessionListContainer = memo24(function SessionListContainer2({
22865
23187
  onError: () => markedViewedRef.current.delete(session.id)
22866
23188
  });
22867
23189
  }, [markSessionViewed, sessionSnapshotMap]);
22868
- useEffect36(() => {
23190
+ useEffect37(() => {
22869
23191
  const previousId = previousActiveSessionId.current;
22870
23192
  if (previousId && previousId !== activeSessionId) {
22871
23193
  markViewedIfReady(previousId);
22872
23194
  }
22873
23195
  previousActiveSessionId.current = activeSessionId;
22874
23196
  }, [activeSessionId, markViewedIfReady]);
22875
- useEffect36(() => {
23197
+ useEffect37(() => {
22876
23198
  if (!activeSessionId || lastScrolledSessionId.current === activeSessionId || sessionSnapshot.length === 0)
22877
23199
  return;
22878
23200
  const activeSession = sessionSnapshotMap.get(activeSessionId);
@@ -22894,7 +23216,7 @@ var SessionListContainer = memo24(function SessionListContainer2({
22894
23216
  hasNextPage,
22895
23217
  fetchNextPage
22896
23218
  ]);
22897
- useEffect36(() => {
23219
+ useEffect37(() => {
22898
23220
  const container = scrollContainerRef.current;
22899
23221
  if (!container)
22900
23222
  return;
@@ -22915,7 +23237,7 @@ var SessionListContainer = memo24(function SessionListContainer2({
22915
23237
  container.addEventListener("scroll", handleScroll, { passive: true });
22916
23238
  return () => container.removeEventListener("scroll", handleScroll);
22917
23239
  }, [hasNextPage, isFetchingNextPage, fetchNextPage]);
22918
- useEffect36(() => {
23240
+ useEffect37(() => {
22919
23241
  const container = scrollContainerRef.current;
22920
23242
  const sentinel = paginationSentinelRef.current;
22921
23243
  if (!container || !sentinel || typeof IntersectionObserver === "undefined") {
@@ -22965,6 +23287,7 @@ var SessionListContainer = memo24(function SessionListContainer2({
22965
23287
  snapshot: session,
22966
23288
  isActive: session.id === activeSessionId,
22967
23289
  onSelectSession: handleSessionClick,
23290
+ onTogglePinned: handleTogglePinned,
22968
23291
  registerItem
22969
23292
  }, session.id);
22970
23293
  };
@@ -23005,6 +23328,7 @@ var SessionListContainer = memo24(function SessionListContainer2({
23005
23328
  children: /* @__PURE__ */ jsxs75("div", {
23006
23329
  className: "space-y-4",
23007
23330
  children: [
23331
+ pinnedGroups.map((group) => renderGroup(group)),
23008
23332
  statusGroups.map((group) => renderGroup(group)),
23009
23333
  recentGroups.map((group) => renderGroup(group))
23010
23334
  ]
@@ -23060,7 +23384,7 @@ import {
23060
23384
  lineNumbers
23061
23385
  } from "@codemirror/view";
23062
23386
  import { tags } from "@lezer/highlight";
23063
- import { useCallback as useCallback27, useEffect as useEffect37, useMemo as useMemo23, useRef as useRef24 } from "react";
23387
+ import { useCallback as useCallback27, useEffect as useEffect38, useMemo as useMemo23, useRef as useRef24 } from "react";
23064
23388
  import { jsx as jsx88 } from "react/jsx-runtime";
23065
23389
  var viewerTheme = EditorView.theme({
23066
23390
  "&": {
@@ -23334,7 +23658,7 @@ function CodeMirrorViewer({
23334
23658
  decorationsCompartmentRef.current.of(decorationsExtension)
23335
23659
  ]
23336
23660
  }), [lineNumberExtension, languageExtension, decorationsExtension]);
23337
- useEffect37(() => {
23661
+ useEffect38(() => {
23338
23662
  const host = hostRef.current;
23339
23663
  if (!host)
23340
23664
  return;
@@ -23361,7 +23685,7 @@ function CodeMirrorViewer({
23361
23685
  viewRef.current = null;
23362
23686
  };
23363
23687
  }, []);
23364
- useEffect37(() => {
23688
+ useEffect38(() => {
23365
23689
  const view = viewRef.current;
23366
23690
  if (!view)
23367
23691
  return;
@@ -23373,7 +23697,7 @@ function CodeMirrorViewer({
23373
23697
  view.setState(createEditorState(contentRef.current));
23374
23698
  }
23375
23699
  }, [lineNumberExtension, createEditorState]);
23376
- useEffect37(() => {
23700
+ useEffect38(() => {
23377
23701
  const view = viewRef.current;
23378
23702
  if (!view)
23379
23703
  return;
@@ -23385,7 +23709,7 @@ function CodeMirrorViewer({
23385
23709
  view.setState(createEditorState(contentRef.current));
23386
23710
  }
23387
23711
  }, [languageExtension, createEditorState]);
23388
- useEffect37(() => {
23712
+ useEffect38(() => {
23389
23713
  const view = viewRef.current;
23390
23714
  if (!view)
23391
23715
  return;
@@ -23397,7 +23721,7 @@ function CodeMirrorViewer({
23397
23721
  view.setState(createEditorState(contentRef.current));
23398
23722
  }
23399
23723
  }, [decorationsExtension, createEditorState]);
23400
- useEffect37(() => {
23724
+ useEffect38(() => {
23401
23725
  const view = viewRef.current;
23402
23726
  if (!view)
23403
23727
  return;
@@ -23413,7 +23737,7 @@ function CodeMirrorViewer({
23413
23737
  }
23414
23738
  contentRef.current = content;
23415
23739
  }, [content, createEditorState]);
23416
- useEffect37(() => {
23740
+ useEffect38(() => {
23417
23741
  const view = viewRef.current;
23418
23742
  if (!view || !scrollToLine || scrollToLine < 1)
23419
23743
  return;
@@ -23426,7 +23750,7 @@ function CodeMirrorViewer({
23426
23750
  view.setState(createEditorState(contentRef.current));
23427
23751
  }
23428
23752
  }, [scrollToLine, createEditorState]);
23429
- useEffect37(() => {
23753
+ useEffect38(() => {
23430
23754
  const view = viewRef.current;
23431
23755
  if (!view || scrollToEndSignal === undefined)
23432
23756
  return;
@@ -23606,7 +23930,7 @@ import { GitCommit as GitCommit3, CheckSquare, AlertTriangle as AlertTriangle2 }
23606
23930
  // src/components/git/GitFileTree.tsx
23607
23931
  import {
23608
23932
  useCallback as useCallback28,
23609
- useEffect as useEffect38,
23933
+ useEffect as useEffect39,
23610
23934
  useMemo as useMemo24,
23611
23935
  useState as useState40
23612
23936
  } from "react";
@@ -24005,7 +24329,7 @@ function GitFileTree({
24005
24329
  const gitFileIndex = useFocusStore((state) => state.gitFileIndex);
24006
24330
  const gitTreeRows = useGitStore((state) => state.gitTreeRows);
24007
24331
  const setGitTreeSectionRows = useGitStore((state) => state.setGitTreeSectionRows);
24008
- useEffect38(() => {
24332
+ useEffect39(() => {
24009
24333
  const folderPaths = getInitialExpanded(tree);
24010
24334
  setExpanded((prev) => {
24011
24335
  const hasNewFolder = [...folderPaths].some((path) => !prev.has(path));
@@ -24023,7 +24347,7 @@ function GitFileTree({
24023
24347
  });
24024
24348
  }, []);
24025
24349
  const visibleRows = useMemo24(() => collectVisibleRows(tree, sectionId, staged, expanded, toggleExpanded), [tree, sectionId, staged, expanded, toggleExpanded]);
24026
- useEffect38(() => {
24350
+ useEffect39(() => {
24027
24351
  setGitTreeSectionRows(sectionId, visibleRows);
24028
24352
  return () => setGitTreeSectionRows(sectionId, []);
24029
24353
  }, [sectionId, visibleRows, setGitTreeSectionRows]);
@@ -24262,7 +24586,7 @@ function GitFileList({ status }) {
24262
24586
  });
24263
24587
  }
24264
24588
  // src/components/git/GitSidebar.tsx
24265
- import { memo as memo25, useCallback as useCallback30, useEffect as useEffect41, useState as useState43 } from "react";
24589
+ import { memo as memo25, useCallback as useCallback30, useEffect as useEffect42, useState as useState43 } from "react";
24266
24590
  import {
24267
24591
  FolderGit2,
24268
24592
  ChevronRight as ChevronRight13,
@@ -24281,7 +24605,7 @@ import { useQueryClient as useQueryClient15 } from "@tanstack/react-query";
24281
24605
 
24282
24606
  // src/components/git/GitBranchSwitcher.tsx
24283
24607
  import {
24284
- useEffect as useEffect40,
24608
+ useEffect as useEffect41,
24285
24609
  useId as useId3,
24286
24610
  useMemo as useMemo26,
24287
24611
  useRef as useRef26,
@@ -24291,9 +24615,9 @@ import { AnimatePresence as AnimatePresence2, motion as motion3 } from "motion/r
24291
24615
  import { Check as Check12, GitBranch as GitBranch9, Plus as Plus3, RefreshCw as RefreshCw6, Search as Search7 } from "lucide-react";
24292
24616
 
24293
24617
  // src/components/git/GitCreateBranchModal.tsx
24294
- import { useEffect as useEffect39, useId as useId2, useRef as useRef25, useState as useState41 } from "react";
24618
+ import { useEffect as useEffect40, useId as useId2, useRef as useRef25, useState as useState41 } from "react";
24295
24619
  import { GitBranch as GitBranch8 } from "lucide-react";
24296
- import { jsx as jsx93, jsxs as jsxs80, Fragment as Fragment38 } from "react/jsx-runtime";
24620
+ import { jsx as jsx93, jsxs as jsxs80, Fragment as Fragment39 } from "react/jsx-runtime";
24297
24621
  var INVALID_BRANCH_PATTERN = /[\s~^:?*[\]\\]/;
24298
24622
  function sanitizeBranchName(name) {
24299
24623
  return name.trim();
@@ -24324,7 +24648,7 @@ function GitCreateBranchModal({
24324
24648
  const [checkout, setCheckout] = useState41(true);
24325
24649
  const [error, setError] = useState41(null);
24326
24650
  const inputRef = useRef25(null);
24327
- useEffect39(() => {
24651
+ useEffect40(() => {
24328
24652
  if (isOpen) {
24329
24653
  setName(defaultName);
24330
24654
  setCheckout(true);
@@ -24444,7 +24768,7 @@ function GitCreateBranchModal({
24444
24768
  onClick: handleSubmit,
24445
24769
  disabled: !valid || createBranch.isPending,
24446
24770
  className: "gap-1.5",
24447
- children: createBranch.isPending ? /* @__PURE__ */ jsxs80(Fragment38, {
24771
+ children: createBranch.isPending ? /* @__PURE__ */ jsxs80(Fragment39, {
24448
24772
  children: [
24449
24773
  /* @__PURE__ */ jsx93(StableSpinner, {
24450
24774
  size: "sm",
@@ -24452,7 +24776,7 @@ function GitCreateBranchModal({
24452
24776
  }),
24453
24777
  "Creating..."
24454
24778
  ]
24455
- }) : /* @__PURE__ */ jsxs80(Fragment38, {
24779
+ }) : /* @__PURE__ */ jsxs80(Fragment39, {
24456
24780
  children: [
24457
24781
  /* @__PURE__ */ jsx93(GitBranch8, {
24458
24782
  className: "h-3.5 w-3.5"
@@ -24520,10 +24844,10 @@ function GitBranchSwitcher({
24520
24844
  return rows;
24521
24845
  return rows.filter((row) => row.displayName.toLowerCase().includes(q) || row.branch.name.toLowerCase().includes(q));
24522
24846
  }, [rows, query]);
24523
- useEffect40(() => {
24847
+ useEffect41(() => {
24524
24848
  setActiveIndex(0);
24525
24849
  }, [query]);
24526
- useEffect40(() => {
24850
+ useEffect41(() => {
24527
24851
  if (!isOpen)
24528
24852
  return;
24529
24853
  const handleClickOutside = (e) => {
@@ -24537,7 +24861,7 @@ function GitBranchSwitcher({
24537
24861
  document.addEventListener("mousedown", handleClickOutside);
24538
24862
  return () => document.removeEventListener("mousedown", handleClickOutside);
24539
24863
  }, [isOpen]);
24540
- useEffect40(() => {
24864
+ useEffect41(() => {
24541
24865
  if (!isOpen) {
24542
24866
  setQuery("");
24543
24867
  setActionError(null);
@@ -24545,7 +24869,7 @@ function GitBranchSwitcher({
24545
24869
  requestAnimationFrame(() => searchInputRef.current?.focus());
24546
24870
  }
24547
24871
  }, [isOpen]);
24548
- useEffect40(() => {
24872
+ useEffect41(() => {
24549
24873
  const node = itemRefs.current.get(activeIndex);
24550
24874
  node?.scrollIntoView({ block: "nearest" });
24551
24875
  }, [activeIndex]);
@@ -24744,7 +25068,7 @@ function GitBranchSwitcher({
24744
25068
  }
24745
25069
 
24746
25070
  // src/components/git/GitSidebar.tsx
24747
- import { jsx as jsx95, jsxs as jsxs82, Fragment as Fragment39 } from "react/jsx-runtime";
25071
+ import { jsx as jsx95, jsxs as jsxs82, Fragment as Fragment40 } from "react/jsx-runtime";
24748
25072
  var PANEL_KEY = "git";
24749
25073
  var DEFAULT_WIDTH = 320;
24750
25074
  var MIN_WIDTH = 320;
@@ -24777,7 +25101,7 @@ var GitSidebarContent = memo25(function GitSidebarContent2({
24777
25101
  const [remoteName, setRemoteName] = useState43("origin");
24778
25102
  const [remoteUrl, setRemoteUrl] = useState43("");
24779
25103
  const [confirmRemoveRemote, setConfirmRemoveRemote] = useState43(null);
24780
- useEffect41(() => {
25104
+ useEffect42(() => {
24781
25105
  queryClient.invalidateQueries({ queryKey: ["git", "status"] });
24782
25106
  }, [queryClient]);
24783
25107
  const handleRefresh = () => {
@@ -24894,7 +25218,7 @@ Please help me fix this.`;
24894
25218
  icon: /* @__PURE__ */ jsx95(GitBranch10, {
24895
25219
  className: "size-[15px]"
24896
25220
  }),
24897
- title: /* @__PURE__ */ jsxs82(Fragment39, {
25221
+ title: /* @__PURE__ */ jsxs82(Fragment40, {
24898
25222
  children: [
24899
25223
  "Git Changes",
24900
25224
  totalChanges > 0 && /* @__PURE__ */ jsxs82("span", {
@@ -25245,7 +25569,7 @@ Please help me fix this.`;
25245
25569
  currentBranch: status.branch,
25246
25570
  isDetached: status.isDetached,
25247
25571
  shortHeadSha: status.shortHeadSha
25248
- }) : /* @__PURE__ */ jsxs82(Fragment39, {
25572
+ }) : /* @__PURE__ */ jsxs82(Fragment40, {
25249
25573
  children: [
25250
25574
  /* @__PURE__ */ jsx95(GitBranch10, {
25251
25575
  className: "w-3 h-3 flex-shrink-0"
@@ -25295,11 +25619,11 @@ import { GitBranch as GitBranch11 } from "lucide-react";
25295
25619
  import { memo as memo26 } from "react";
25296
25620
 
25297
25621
  // src/hooks/useShortcutHintsVisible.ts
25298
- import { useEffect as useEffect42, useState as useState44 } from "react";
25622
+ import { useEffect as useEffect43, useState as useState44 } from "react";
25299
25623
  var SHORTCUT_HINT_MODIFIERS = new Set(["Control", "Meta"]);
25300
25624
  function useShortcutHintsVisible() {
25301
25625
  const [isVisible, setIsVisible] = useState44(false);
25302
- useEffect42(() => {
25626
+ useEffect43(() => {
25303
25627
  const handleKeyDown = (event) => {
25304
25628
  if (SHORTCUT_HINT_MODIFIERS.has(event.key) || event.ctrlKey || event.metaKey) {
25305
25629
  setIsVisible(true);
@@ -25365,7 +25689,7 @@ var GitSidebarToggle = memo27(function GitSidebarToggle2() {
25365
25689
  });
25366
25690
  });
25367
25691
  // src/components/git/GitDiffPanel.tsx
25368
- import { useEffect as useEffect43, memo as memo28, useState as useState45 } from "react";
25692
+ import { useEffect as useEffect44, memo as memo28, useState as useState45 } from "react";
25369
25693
  import { X as X16, Maximize2, Minimize2 as Minimize22 } from "lucide-react";
25370
25694
 
25371
25695
  // src/hooks/useFileBrowser.ts
@@ -25420,11 +25744,11 @@ var GitDiffPanel = memo28(function GitDiffPanel2({
25420
25744
  const { data: fullFileDiff, isLoading: fullFileLoading } = useGitDiffFullFile(selectedFile, selectedFileStaged, showFullFile);
25421
25745
  const activeDiff = showFullFile && fullFileDiff ? fullFileDiff : diff2;
25422
25746
  const activeLoading = showFullFile ? fullFileLoading : isLoading;
25423
- useEffect43(() => {
25747
+ useEffect44(() => {
25424
25748
  if (!isDiffOpen)
25425
25749
  setShowFullFile(false);
25426
25750
  }, [isDiffOpen]);
25427
- useEffect43(() => {
25751
+ useEffect44(() => {
25428
25752
  const handleEscape = (e) => {
25429
25753
  const target = e.target;
25430
25754
  const isInInput = target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable;
@@ -25528,9 +25852,9 @@ ${activeDiff?.absPath || ""}`,
25528
25852
  });
25529
25853
  });
25530
25854
  // src/components/git/GitCommitModal.tsx
25531
- import { useState as useState46, useId as useId4, useEffect as useEffect44, useCallback as useCallback31 } from "react";
25855
+ import { useState as useState46, useId as useId4, useEffect as useEffect45, useCallback as useCallback31 } from "react";
25532
25856
  import { GitCommit as GitCommit4, Sparkles as Sparkles6 } from "lucide-react";
25533
- import { jsx as jsx99, jsxs as jsxs85, Fragment as Fragment40 } from "react/jsx-runtime";
25857
+ import { jsx as jsx99, jsxs as jsxs85, Fragment as Fragment41 } from "react/jsx-runtime";
25534
25858
  function GitCommitModal() {
25535
25859
  const isCommitModalOpen = useGitStore((state) => state.isCommitModalOpen);
25536
25860
  return isCommitModalOpen ? /* @__PURE__ */ jsx99(GitCommitModalContent, {}) : null;
@@ -25565,7 +25889,7 @@ function GitCommitModalContent() {
25565
25889
  console.error("Failed to generate commit message:", error);
25566
25890
  }
25567
25891
  }, [generateMessage]);
25568
- useEffect44(() => {
25892
+ useEffect45(() => {
25569
25893
  const handleKeyDown = (e) => {
25570
25894
  if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
25571
25895
  e.preventDefault();
@@ -25626,7 +25950,7 @@ function GitCommitModalContent() {
25626
25950
  onClick: handleGenerateMessage,
25627
25951
  className: "w-full",
25628
25952
  disabled: generateMessage.isPending,
25629
- children: generateMessage.isPending ? /* @__PURE__ */ jsxs85(Fragment40, {
25953
+ children: generateMessage.isPending ? /* @__PURE__ */ jsxs85(Fragment41, {
25630
25954
  children: [
25631
25955
  /* @__PURE__ */ jsx99(StableSpinner, {
25632
25956
  className: "mr-2",
@@ -25634,7 +25958,7 @@ function GitCommitModalContent() {
25634
25958
  }),
25635
25959
  "Generating..."
25636
25960
  ]
25637
- }) : /* @__PURE__ */ jsxs85(Fragment40, {
25961
+ }) : /* @__PURE__ */ jsxs85(Fragment41, {
25638
25962
  children: [
25639
25963
  /* @__PURE__ */ jsx99(Sparkles6, {
25640
25964
  className: "w-4 h-4 mr-2"
@@ -25672,7 +25996,7 @@ function GitCommitModalContent() {
25672
25996
  disabled: !message.trim() || commitChanges.isPending,
25673
25997
  children: commitChanges.isPending ? /* @__PURE__ */ jsx99("span", {
25674
25998
  children: "Committing..."
25675
- }) : /* @__PURE__ */ jsxs85(Fragment40, {
25999
+ }) : /* @__PURE__ */ jsxs85(Fragment41, {
25676
26000
  children: [
25677
26001
  /* @__PURE__ */ jsx99(GitCommit4, {
25678
26002
  className: "w-4 h-4 mr-2"
@@ -25696,7 +26020,7 @@ function GitCommitModalContent() {
25696
26020
  });
25697
26021
  }
25698
26022
  // src/components/terminals/TerminalsPanel.tsx
25699
- import { memo as memo31, useCallback as useCallback34, useRef as useRef28, useEffect as useEffect46 } from "react";
26023
+ import { memo as memo31, useCallback as useCallback34, useRef as useRef28, useEffect as useEffect47 } from "react";
25700
26024
  import {
25701
26025
  Terminal as TerminalIcon,
25702
26026
  Maximize2 as Maximize22,
@@ -25879,7 +26203,7 @@ var TerminalTab = memo29(function TerminalTab2({
25879
26203
  });
25880
26204
 
25881
26205
  // src/components/terminals/TerminalViewer.tsx
25882
- import { memo as memo30, useEffect as useEffect45, useRef as useRef27, useState as useState47, useCallback as useCallback33 } from "react";
26206
+ import { memo as memo30, useEffect as useEffect46, useRef as useRef27, useState as useState47, useCallback as useCallback33 } from "react";
25883
26207
  import {
25884
26208
  init,
25885
26209
  Terminal as Terminal7,
@@ -26079,7 +26403,7 @@ var TerminalViewer = memo30(function TerminalViewer2({
26079
26403
  }
26080
26404
  };
26081
26405
  }, [terminalId]);
26082
- useEffect45(() => {
26406
+ useEffect46(() => {
26083
26407
  if (!containerRef.current || !terminalId)
26084
26408
  return;
26085
26409
  let disposed = false;
@@ -26250,7 +26574,7 @@ var TerminalViewer = memo30(function TerminalViewer2({
26250
26574
  fitAddonRef.current = null;
26251
26575
  };
26252
26576
  }, [terminalId, connectWebSocket]);
26253
- useEffect45(() => {
26577
+ useEffect46(() => {
26254
26578
  const term = termRef.current;
26255
26579
  if (!term)
26256
26580
  return;
@@ -26268,7 +26592,7 @@ var TerminalViewer = memo30(function TerminalViewer2({
26268
26592
  }
26269
26593
  }
26270
26594
  }, [isActive, fitTerminal]);
26271
- useEffect45(() => {
26595
+ useEffect46(() => {
26272
26596
  if (isActive) {
26273
26597
  fitTerminal();
26274
26598
  }
@@ -26306,12 +26630,12 @@ var TerminalViewer = memo30(function TerminalViewer2({
26306
26630
  });
26307
26631
 
26308
26632
  // src/components/terminals/TerminalsPanel.tsx
26309
- import { jsx as jsx102, jsxs as jsxs88, Fragment as Fragment41 } from "react/jsx-runtime";
26633
+ import { jsx as jsx102, jsxs as jsxs88, Fragment as Fragment42 } from "react/jsx-runtime";
26310
26634
  var MIN_HEIGHT2 = 150;
26311
26635
  var MAX_HEIGHT_RATIO = 0.85;
26312
26636
  var TerminalsPanel = memo31(function TerminalsPanel2() {
26313
26637
  const isOpen = useTerminalStore((s) => s.isOpen);
26314
- return /* @__PURE__ */ jsxs88(Fragment41, {
26638
+ return /* @__PURE__ */ jsxs88(Fragment42, {
26315
26639
  children: [
26316
26640
  /* @__PURE__ */ jsx102(TerminalPanelShortcutController, {}),
26317
26641
  isOpen ? /* @__PURE__ */ jsx102(TerminalsPanelContent, {}) : null
@@ -26320,7 +26644,7 @@ var TerminalsPanel = memo31(function TerminalsPanel2() {
26320
26644
  });
26321
26645
  function TerminalPanelShortcutController() {
26322
26646
  const togglePanel = useTerminalStore((s) => s.togglePanel);
26323
- useEffect46(() => {
26647
+ useEffect47(() => {
26324
26648
  const handleKeyDown = (e) => {
26325
26649
  if (e.key === "`" && e.ctrlKey) {
26326
26650
  e.preventDefault();
@@ -26348,12 +26672,12 @@ var TerminalsPanelContent = memo31(function TerminalsPanelContent2() {
26348
26672
  const autoCreatingRef = useRef28(false);
26349
26673
  const terminalsListRef = useRef28(terminalsList);
26350
26674
  terminalsListRef.current = terminalsList;
26351
- useEffect46(() => {
26675
+ useEffect47(() => {
26352
26676
  if (terminalsListRef.current.length > 0 && (!activeTabId || !terminalsListRef.current.find((t) => t.id === activeTabId))) {
26353
26677
  selectTab(terminalsListRef.current[0].id);
26354
26678
  }
26355
26679
  }, [terminalsList.length, activeTabId, selectTab]);
26356
- useEffect46(() => {
26680
+ useEffect47(() => {
26357
26681
  if (terminals && terminalsList.length === 0 && !autoCreatingRef.current && !createTerminal.isPending) {
26358
26682
  autoCreatingRef.current = true;
26359
26683
  createTerminal.mutateAsync({
@@ -26608,7 +26932,7 @@ function useSessionFiles(sessionId, enabled = true) {
26608
26932
  }
26609
26933
 
26610
26934
  // src/components/session-files/SessionFilesSidebar.tsx
26611
- import { jsx as jsx104, jsxs as jsxs90, Fragment as Fragment42 } from "react/jsx-runtime";
26935
+ import { jsx as jsx104, jsxs as jsxs90, Fragment as Fragment43 } from "react/jsx-runtime";
26612
26936
  var PANEL_KEY2 = "session-files";
26613
26937
  var DEFAULT_WIDTH2 = 320;
26614
26938
  var MIN_WIDTH2 = 320;
@@ -26780,7 +27104,7 @@ var SessionFilesSidebarContent = memo33(function SessionFilesSidebarContent2({
26780
27104
  icon: /* @__PURE__ */ jsx104(FilePen, {
26781
27105
  className: "size-[15px]"
26782
27106
  }),
26783
- title: /* @__PURE__ */ jsxs90(Fragment42, {
27107
+ title: /* @__PURE__ */ jsxs90(Fragment43, {
26784
27108
  children: [
26785
27109
  "Session Files",
26786
27110
  data && data.totalFiles > 0 && /* @__PURE__ */ jsxs90("span", {
@@ -26897,7 +27221,7 @@ var SessionFilesSidebarToggle = memo34(function SessionFilesSidebarToggle2({
26897
27221
  });
26898
27222
  });
26899
27223
  // src/components/session-files/SessionFilesDiffPanel.tsx
26900
- import { useEffect as useEffect47, useMemo as useMemo28, memo as memo35 } from "react";
27224
+ import { useEffect as useEffect48, useMemo as useMemo28, memo as memo35 } from "react";
26901
27225
  import { X as X18, ChevronLeft, ChevronRight as ChevronRight14 } from "lucide-react";
26902
27226
  import { jsx as jsx106, jsxs as jsxs92 } from "react/jsx-runtime";
26903
27227
  function transformToUnifiedDiff(patch) {
@@ -27041,7 +27365,7 @@ ${contentLines.map((line) => `+${line}`).join(`
27041
27365
  }
27042
27366
  return rawPatch;
27043
27367
  }, [selectedOperation, selectedFile]);
27044
- useEffect47(() => {
27368
+ useEffect48(() => {
27045
27369
  const handleKeyDown = (e) => {
27046
27370
  const target = e.target;
27047
27371
  const isInInput = target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable;
@@ -27214,7 +27538,7 @@ ${contentLines.map((line) => `+${line}`).join(`
27214
27538
  });
27215
27539
  });
27216
27540
  // src/components/research/ResearchSidebar.tsx
27217
- import { memo as memo36, useState as useState48, useEffect as useEffect48, useCallback as useCallback36, useRef as useRef29, useMemo as useMemo29 } from "react";
27541
+ import { memo as memo36, useState as useState48, useEffect as useEffect49, useCallback as useCallback36, useRef as useRef29, useMemo as useMemo29 } from "react";
27218
27542
  import {
27219
27543
  FlaskConical as FlaskConical3,
27220
27544
  Plus as Plus6,
@@ -27346,7 +27670,7 @@ function useExportToSession() {
27346
27670
 
27347
27671
  // src/components/research/ResearchSidebar.tsx
27348
27672
  import { useMutation as useMutation10, useQueryClient as useQueryClient18 } from "@tanstack/react-query";
27349
- import { jsx as jsx107, jsxs as jsxs93, Fragment as Fragment43 } from "react/jsx-runtime";
27673
+ import { jsx as jsx107, jsxs as jsxs93, Fragment as Fragment44 } from "react/jsx-runtime";
27350
27674
  var PANEL_KEY3 = "research";
27351
27675
  var DEFAULT_WIDTH3 = 320;
27352
27676
  var MIN_WIDTH3 = 320;
@@ -27398,12 +27722,12 @@ var ResearchSidebarContent = memo36(function ResearchSidebarContent2({
27398
27722
  queryClient.invalidateQueries({ queryKey: ["messages", sessionId] });
27399
27723
  }
27400
27724
  });
27401
- useEffect48(() => {
27725
+ useEffect49(() => {
27402
27726
  if (parentSessionId) {
27403
27727
  useResearchStore.getState().setParentSessionId(parentSessionId);
27404
27728
  }
27405
27729
  }, [parentSessionId]);
27406
- useEffect48(() => {
27730
+ useEffect49(() => {
27407
27731
  if (researchData?.sessions?.length) {
27408
27732
  const currentIsValid = researchData.sessions.some((s) => s.id === activeResearchSessionId);
27409
27733
  if (!currentIsValid) {
@@ -27413,7 +27737,7 @@ var ResearchSidebarContent = memo36(function ResearchSidebarContent2({
27413
27737
  selectResearchSession(null);
27414
27738
  }
27415
27739
  }, [researchData, activeResearchSessionId, selectResearchSession]);
27416
- useEffect48(() => {
27740
+ useEffect49(() => {
27417
27741
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
27418
27742
  }, []);
27419
27743
  const adjustTextareaHeight = useCallback36(() => {
@@ -27423,7 +27747,7 @@ var ResearchSidebarContent = memo36(function ResearchSidebarContent2({
27423
27747
  textarea.style.height = "auto";
27424
27748
  textarea.style.height = `${Math.min(textarea.scrollHeight, 120)}px`;
27425
27749
  }, []);
27426
- useEffect48(() => {
27750
+ useEffect49(() => {
27427
27751
  adjustTextareaHeight();
27428
27752
  }, [adjustTextareaHeight]);
27429
27753
  const handleCreateNew = useCallback36(async () => {
@@ -27653,7 +27977,7 @@ var ResearchSidebarContent = memo36(function ResearchSidebarContent2({
27653
27977
  }, session.id))
27654
27978
  })
27655
27979
  ]
27656
- }) : /* @__PURE__ */ jsxs93(Fragment43, {
27980
+ }) : /* @__PURE__ */ jsxs93(Fragment44, {
27657
27981
  children: [
27658
27982
  /* @__PURE__ */ jsx107("div", {
27659
27983
  className: "flex-1 overflow-y-auto px-3 py-3 research-messages text-[13px]",
@@ -27915,7 +28239,7 @@ var ResearchSidebarToggle = memo37(function ResearchSidebarToggle2({
27915
28239
  });
27916
28240
  });
27917
28241
  // src/components/settings/SettingsSidebar.tsx
27918
- import { memo as memo39, useState as useState50, useMemo as useMemo30, useCallback as useCallback38, useEffect as useEffect51, useRef as useRef32 } from "react";
28242
+ import { memo as memo39, useState as useState50, useMemo as useMemo30, useCallback as useCallback38, useEffect as useEffect52, useRef as useRef32 } from "react";
27919
28243
  import { createPortal } from "react-dom";
27920
28244
  import { AnimatePresence as AnimatePresence3, motion as motion4 } from "motion/react";
27921
28245
  import {
@@ -27979,7 +28303,7 @@ var useOnboardingStore = create23((set, get) => ({
27979
28303
  }));
27980
28304
 
27981
28305
  // src/hooks/useAuthStatus.ts
27982
- import { useEffect as useEffect49, useCallback as useCallback37, useState as useState49, useRef as useRef30 } from "react";
28306
+ import { useEffect as useEffect50, useCallback as useCallback37, useState as useState49, useRef as useRef30 } from "react";
27983
28307
  import { useQueryClient as useQueryClient19 } from "@tanstack/react-query";
27984
28308
  var isInIframe = typeof window !== "undefined" && window.self !== window.top;
27985
28309
  function useAuthStatus() {
@@ -28183,7 +28507,7 @@ function useAuthStatus() {
28183
28507
  setLoading(false);
28184
28508
  }
28185
28509
  }, [fetchAuthStatus, setLoading, setError]);
28186
- useEffect49(() => {
28510
+ useEffect50(() => {
28187
28511
  if (!oauthPolling || !isInIframe)
28188
28512
  return;
28189
28513
  oauthPollingRef.current = setInterval(() => {
@@ -28197,7 +28521,7 @@ function useAuthStatus() {
28197
28521
  clearTimeout(timeout);
28198
28522
  };
28199
28523
  }, [oauthPolling, fetchAuthStatus]);
28200
- useEffect49(() => {
28524
+ useEffect50(() => {
28201
28525
  if (!oauthPolling || !authStatus)
28202
28526
  return;
28203
28527
  const currentConfigured = Object.entries(authStatus.providers).filter(([, p]) => p.configured);
@@ -28206,7 +28530,7 @@ function useAuthStatus() {
28206
28530
  setOauthPolling(false);
28207
28531
  }
28208
28532
  }, [authStatus, oauthPolling]);
28209
- useEffect49(() => {
28533
+ useEffect50(() => {
28210
28534
  const handleOAuthMessage = (event) => {
28211
28535
  if (event.data?.type === "oauth-success") {
28212
28536
  fetchAuthStatus();
@@ -28518,14 +28842,14 @@ var DictationSettings = memo38(function DictationSettings2({
28518
28842
  });
28519
28843
 
28520
28844
  // src/hooks/useTopupCallback.ts
28521
- import { useEffect as useEffect50, useRef as useRef31 } from "react";
28845
+ import { useEffect as useEffect51, useRef as useRef31 } from "react";
28522
28846
  var STORAGE_KEY = "pendingPolarCheckout";
28523
28847
  function useTopupCallback() {
28524
28848
  const hasHandled = useRef31(false);
28525
28849
  const loadingToastId = useRef31(null);
28526
28850
  const setBalance = useOttoRouterStore((s) => s.setBalance);
28527
28851
  const removeToast = useToastStore((s) => s.removeToast);
28528
- useEffect50(() => {
28852
+ useEffect51(() => {
28529
28853
  if (hasHandled.current)
28530
28854
  return;
28531
28855
  const params = new URLSearchParams(window.location.search);
@@ -28587,7 +28911,7 @@ function useTopupCallback() {
28587
28911
  }
28588
28912
 
28589
28913
  // src/components/settings/SettingsSidebar.tsx
28590
- import { jsx as jsx110, jsxs as jsxs96, Fragment as Fragment44 } from "react/jsx-runtime";
28914
+ import { jsx as jsx110, jsxs as jsxs96, Fragment as Fragment45 } from "react/jsx-runtime";
28591
28915
  var SETTINGS_PANEL_KEY = "settings";
28592
28916
  var SETTINGS_DEFAULT_WIDTH = 320;
28593
28917
  var SETTINGS_MIN_WIDTH = 320;
@@ -28730,7 +29054,7 @@ var SelectRow = memo39(function SelectRow2({
28730
29054
  const [menuStyle, setMenuStyle] = useState50(null);
28731
29055
  const buttonRef = useRef32(null);
28732
29056
  const selectedOption = options.find((o) => o.id === value);
28733
- useEffect51(() => {
29057
+ useEffect52(() => {
28734
29058
  if (!isOpen || !buttonRef.current)
28735
29059
  return;
28736
29060
  const update = () => {
@@ -28787,7 +29111,7 @@ var SelectRow = memo39(function SelectRow2({
28787
29111
  })
28788
29112
  ]
28789
29113
  }),
28790
- isOpen && menuStyle && typeof document !== "undefined" && createPortal(/* @__PURE__ */ jsxs96(Fragment44, {
29114
+ isOpen && menuStyle && typeof document !== "undefined" && createPortal(/* @__PURE__ */ jsxs96(Fragment45, {
28791
29115
  children: [
28792
29116
  /* @__PURE__ */ jsx110("div", {
28793
29117
  className: "fixed inset-0 z-[10000]",
@@ -28893,7 +29217,7 @@ var FontPickerRow = memo39(function FontPickerRow2({
28893
29217
  })
28894
29218
  ]
28895
29219
  }),
28896
- isOpen && /* @__PURE__ */ jsxs96(Fragment44, {
29220
+ isOpen && /* @__PURE__ */ jsxs96(Fragment45, {
28897
29221
  children: [
28898
29222
  /* @__PURE__ */ jsx110("div", {
28899
29223
  className: "fixed inset-0 z-40",
@@ -28956,7 +29280,7 @@ var NumberInputRow = memo39(function NumberInputRow2({
28956
29280
  disabled
28957
29281
  }) {
28958
29282
  const [draft, setDraft] = useState50(value !== null && value !== undefined ? String(value) : "");
28959
- useEffect51(() => {
29283
+ useEffect52(() => {
28960
29284
  setDraft(value !== null && value !== undefined ? String(value) : "");
28961
29285
  }, [value]);
28962
29286
  const persistedValue = value !== null && value !== undefined ? String(value) : "";
@@ -29511,13 +29835,13 @@ function OttoRouterSubscriptionInfo() {
29511
29835
  const payg = useOttoRouterStore((s) => s.payg);
29512
29836
  if (!subscription?.active)
29513
29837
  return null;
29514
- return /* @__PURE__ */ jsxs96(Fragment44, {
29838
+ return /* @__PURE__ */ jsxs96(Fragment45, {
29515
29839
  children: [
29516
29840
  /* @__PURE__ */ jsx110(SettingRow, {
29517
29841
  label: "Plan",
29518
29842
  value: subscription.tierName ?? "GO"
29519
29843
  }),
29520
- subscription.usageWindows && /* @__PURE__ */ jsxs96(Fragment44, {
29844
+ subscription.usageWindows && /* @__PURE__ */ jsxs96(Fragment45, {
29521
29845
  children: [
29522
29846
  /* @__PURE__ */ jsx110(SettingRow, {
29523
29847
  label: "5h",
@@ -29633,7 +29957,7 @@ var OttoRouterWalletSection = memo39(function OttoRouterWalletSection2({
29633
29957
  })
29634
29958
  }),
29635
29959
  children: [
29636
- isLoaded ? /* @__PURE__ */ jsxs96(Fragment44, {
29960
+ isLoaded ? /* @__PURE__ */ jsxs96(Fragment45, {
29637
29961
  children: [
29638
29962
  /* @__PURE__ */ jsx110("div", {
29639
29963
  className: "flex justify-center pb-3",
@@ -29671,7 +29995,7 @@ var OttoRouterWalletSection = memo39(function OttoRouterWalletSection2({
29671
29995
  ]
29672
29996
  }),
29673
29997
  /* @__PURE__ */ jsx110(OttoRouterSubscriptionInfo, {}),
29674
- !hasActiveSubscription && /* @__PURE__ */ jsxs96(Fragment44, {
29998
+ !hasActiveSubscription && /* @__PURE__ */ jsxs96(Fragment45, {
29675
29999
  children: [
29676
30000
  /* @__PURE__ */ jsx110(SettingRow, {
29677
30001
  label: "Balance",
@@ -29684,7 +30008,7 @@ var OttoRouterWalletSection = memo39(function OttoRouterWalletSection2({
29684
30008
  ]
29685
30009
  })
29686
30010
  ]
29687
- }) : /* @__PURE__ */ jsxs96(Fragment44, {
30011
+ }) : /* @__PURE__ */ jsxs96(Fragment45, {
29688
30012
  children: [
29689
30013
  /* @__PURE__ */ jsx110("div", {
29690
30014
  className: "flex justify-center pb-3",
@@ -29869,7 +30193,7 @@ import { QRCodeSVG as QRCodeSVG2 } from "qrcode.react";
29869
30193
 
29870
30194
  // src/hooks/useTunnel.ts
29871
30195
  import { useQuery as useQuery14, useMutation as useMutation11, useQueryClient as useQueryClient20 } from "@tanstack/react-query";
29872
- import { useEffect as useEffect52, useCallback as useCallback39, useRef as useRef33 } from "react";
30196
+ import { useEffect as useEffect53, useCallback as useCallback39, useRef as useRef33 } from "react";
29873
30197
  import {
29874
30198
  client as client4,
29875
30199
  getTunnelQr,
@@ -29912,7 +30236,7 @@ function useTunnelStatus() {
29912
30236
  queryFn: fetchTunnelStatus,
29913
30237
  refetchInterval: 3000
29914
30238
  });
29915
- useEffect52(() => {
30239
+ useEffect53(() => {
29916
30240
  if (query.data) {
29917
30241
  setStatus(query.data.status);
29918
30242
  setUrl(query.data.url);
@@ -29972,7 +30296,7 @@ function useTunnelQr() {
29972
30296
  queryFn: fetchTunnelQr,
29973
30297
  enabled: !!url
29974
30298
  });
29975
- useEffect52(() => {
30299
+ useEffect53(() => {
29976
30300
  if (query.data?.ok && query.data.qrCode) {
29977
30301
  setQrCode(query.data.qrCode);
29978
30302
  }
@@ -30012,7 +30336,7 @@ function useTunnelStream() {
30012
30336
  eventSourceRef.current = null;
30013
30337
  };
30014
30338
  }, [setStatus, setUrl, setError, setProgress]);
30015
- useEffect52(() => {
30339
+ useEffect53(() => {
30016
30340
  if (isExpanded) {
30017
30341
  const cleanup = connect();
30018
30342
  return cleanup;
@@ -30028,7 +30352,7 @@ function useTunnelStream() {
30028
30352
  }
30029
30353
 
30030
30354
  // src/components/tunnel/TunnelSidebar.tsx
30031
- import { jsx as jsx112, jsxs as jsxs98, Fragment as Fragment45 } from "react/jsx-runtime";
30355
+ import { jsx as jsx112, jsxs as jsxs98, Fragment as Fragment46 } from "react/jsx-runtime";
30032
30356
  function truncateUrl(url) {
30033
30357
  try {
30034
30358
  const parsed = new URL(url);
@@ -30200,7 +30524,7 @@ var TunnelSidebarContent = memo41(function TunnelSidebarContent2() {
30200
30524
  status === "error" && /* @__PURE__ */ jsxs98("div", {
30201
30525
  className: "flex flex-col items-center justify-center h-full text-center",
30202
30526
  children: [
30203
- error?.includes("Rate limited") ? /* @__PURE__ */ jsxs98(Fragment45, {
30527
+ error?.includes("Rate limited") ? /* @__PURE__ */ jsxs98(Fragment46, {
30204
30528
  children: [
30205
30529
  /* @__PURE__ */ jsx112(Clock4, {
30206
30530
  className: "w-8 h-8 text-yellow-500 mb-4"
@@ -30214,7 +30538,7 @@ var TunnelSidebarContent = memo41(function TunnelSidebarContent2() {
30214
30538
  children: "Cloudflare limits anonymous tunnel requests. Please wait 5-10 minutes before trying again."
30215
30539
  })
30216
30540
  ]
30217
- }) : /* @__PURE__ */ jsxs98(Fragment45, {
30541
+ }) : /* @__PURE__ */ jsxs98(Fragment46, {
30218
30542
  children: [
30219
30543
  /* @__PURE__ */ jsx112(AlertCircle12, {
30220
30544
  className: "w-8 h-8 text-destructive mb-4"
@@ -30272,7 +30596,7 @@ var TunnelSidebarToggle = memo42(function TunnelSidebarToggle2() {
30272
30596
  });
30273
30597
  });
30274
30598
  // src/components/mcp/MCPSidebar.tsx
30275
- import { memo as memo45, useState as useState53, useCallback as useCallback42, useMemo as useMemo31, useEffect as useEffect55, useRef as useRef36 } from "react";
30599
+ import { memo as memo45, useState as useState53, useCallback as useCallback42, useMemo as useMemo31, useEffect as useEffect56, useRef as useRef36 } from "react";
30276
30600
  import {
30277
30601
  ChevronDown as ChevronDown13,
30278
30602
  ChevronRight as ChevronRight16,
@@ -30293,7 +30617,7 @@ import { useQueryClient as useQueryClient22 } from "@tanstack/react-query";
30293
30617
 
30294
30618
  // src/hooks/useMCP.ts
30295
30619
  import { useQuery as useQuery15, useMutation as useMutation12, useQueryClient as useQueryClient21 } from "@tanstack/react-query";
30296
- import { useEffect as useEffect53, useRef as useRef34, useCallback as useCallback40 } from "react";
30620
+ import { useEffect as useEffect54, useRef as useRef34, useCallback as useCallback40 } from "react";
30297
30621
  import {
30298
30622
  listMcpServers,
30299
30623
  startMcpServer,
@@ -30315,7 +30639,7 @@ function useMCPServers() {
30315
30639
  },
30316
30640
  refetchInterval: 1e4
30317
30641
  });
30318
- useEffect53(() => {
30642
+ useEffect54(() => {
30319
30643
  if (query.data?.servers) {
30320
30644
  setServers(query.data.servers);
30321
30645
  }
@@ -30466,7 +30790,7 @@ function useCopilotDevicePoller() {
30466
30790
  timerRef.current = null;
30467
30791
  }
30468
30792
  }, []);
30469
- useEffect53(() => {
30793
+ useEffect54(() => {
30470
30794
  if (!copilotDevice) {
30471
30795
  stopPolling();
30472
30796
  return;
@@ -30502,9 +30826,9 @@ function useCopilotDevicePoller() {
30502
30826
  }
30503
30827
 
30504
30828
  // src/components/mcp/AddMCPServerModal.tsx
30505
- import { memo as memo43, useState as useState52, useCallback as useCallback41, useRef as useRef35, useEffect as useEffect54 } from "react";
30829
+ import { memo as memo43, useState as useState52, useCallback as useCallback41, useRef as useRef35, useEffect as useEffect55 } from "react";
30506
30830
  import { Globe as Globe5, Laptop, FolderDot, Terminal as Terminal9 } from "lucide-react";
30507
- import { jsx as jsx114, jsxs as jsxs100, Fragment as Fragment46 } from "react/jsx-runtime";
30831
+ import { jsx as jsx114, jsxs as jsxs100, Fragment as Fragment47 } from "react/jsx-runtime";
30508
30832
  function parseCommandString(input) {
30509
30833
  const parts = input.trim().split(/\s+/);
30510
30834
  return { command: parts[0] ?? "", args: parts.slice(1) };
@@ -30625,7 +30949,7 @@ var AddMCPServerModal = memo43(function AddMCPServerModal2({
30625
30949
  ]);
30626
30950
  const contentRef = useRef35(null);
30627
30951
  const [contentHeight, setContentHeight] = useState52(undefined);
30628
- useEffect54(() => {
30952
+ useEffect55(() => {
30629
30953
  const el = contentRef.current;
30630
30954
  if (!el)
30631
30955
  return;
@@ -30694,7 +31018,7 @@ var AddMCPServerModal = memo43(function AddMCPServerModal2({
30694
31018
  })
30695
31019
  ]
30696
31020
  }),
30697
- serverMode === "local" ? /* @__PURE__ */ jsxs100(Fragment46, {
31021
+ serverMode === "local" ? /* @__PURE__ */ jsxs100(Fragment47, {
30698
31022
  children: [
30699
31023
  /* @__PURE__ */ jsxs100("div", {
30700
31024
  children: [
@@ -30745,7 +31069,7 @@ API_KEY=sk-xxx`,
30745
31069
  ]
30746
31070
  })
30747
31071
  ]
30748
- }) : /* @__PURE__ */ jsxs100(Fragment46, {
31072
+ }) : /* @__PURE__ */ jsxs100(Fragment47, {
30749
31073
  children: [
30750
31074
  /* @__PURE__ */ jsxs100("div", {
30751
31075
  children: [
@@ -30883,7 +31207,7 @@ API_KEY=sk-xxx`,
30883
31207
  variant: "primary",
30884
31208
  onClick: handleSubmit,
30885
31209
  disabled: addServer.isPending,
30886
- children: addServer.isPending ? /* @__PURE__ */ jsxs100(Fragment46, {
31210
+ children: addServer.isPending ? /* @__PURE__ */ jsxs100(Fragment47, {
30887
31211
  children: [
30888
31212
  /* @__PURE__ */ jsx114(StableSpinner, {
30889
31213
  size: "xs",
@@ -30937,7 +31261,7 @@ var ToggleSwitch = memo44(function ToggleSwitch2({
30937
31261
  });
30938
31262
 
30939
31263
  // src/components/mcp/MCPSidebar.tsx
30940
- import { jsx as jsx116, jsxs as jsxs101, Fragment as Fragment47 } from "react/jsx-runtime";
31264
+ import { jsx as jsx116, jsxs as jsxs101, Fragment as Fragment48 } from "react/jsx-runtime";
30941
31265
  var CopilotDeviceAuth = memo45(function CopilotDeviceAuth2({
30942
31266
  userCode,
30943
31267
  verificationUri
@@ -31173,7 +31497,7 @@ var MCPServerCard = memo45(function MCPServerCard2({
31173
31497
  function useAuthPoller(name, onAuthenticated) {
31174
31498
  const { data } = useMCPAuthStatus(name);
31175
31499
  const prevAuth = useRef36(false);
31176
- useEffect55(() => {
31500
+ useEffect56(() => {
31177
31501
  if (data?.authenticated && !prevAuth.current) {
31178
31502
  onAuthenticated();
31179
31503
  }
@@ -31211,7 +31535,7 @@ var MCPSidebarContent = memo45(function MCPSidebarContent2() {
31211
31535
  }
31212
31536
  }, [pollingServer, setAuthUrl, queryClient]);
31213
31537
  useAuthPoller(pollingServer, handleAuthCompleted);
31214
- useEffect55(() => {
31538
+ useEffect56(() => {
31215
31539
  for (const name of loading) {
31216
31540
  const server = servers.find((s) => s.name === name);
31217
31541
  if (server?.connected) {
@@ -31300,7 +31624,7 @@ var MCPSidebarContent = memo45(function MCPSidebarContent2() {
31300
31624
  icon: /* @__PURE__ */ jsx116(Plug4, {
31301
31625
  className: "size-[15px]"
31302
31626
  }),
31303
- title: /* @__PURE__ */ jsxs101(Fragment47, {
31627
+ title: /* @__PURE__ */ jsxs101(Fragment48, {
31304
31628
  children: [
31305
31629
  "MCP Servers",
31306
31630
  connectedCount > 0 && /* @__PURE__ */ jsxs101("span", {
@@ -31903,7 +32227,7 @@ var SkillsSidebarToggle = memo48(function SkillsSidebarToggle2() {
31903
32227
  });
31904
32228
  });
31905
32229
  // src/components/skills/SkillViewerPanel.tsx
31906
- import { memo as memo49, useEffect as useEffect56 } from "react";
32230
+ import { memo as memo49, useEffect as useEffect57 } from "react";
31907
32231
  import { X as X22 } from "lucide-react";
31908
32232
  import { jsx as jsx120, jsxs as jsxs105 } from "react/jsx-runtime";
31909
32233
  var SkillViewerPanel = memo49(function SkillViewerPanel2({
@@ -31927,7 +32251,7 @@ var SkillViewerPanel = memo49(function SkillViewerPanel2({
31927
32251
  const content = isMainFile ? skillDetail?.content : fileData?.content;
31928
32252
  const isLoading = isMainFile ? !skillDetail : fileLoading;
31929
32253
  const displayPath = isMainFile ? "SKILL.md" : viewingFile ?? "";
31930
- useEffect56(() => {
32254
+ useEffect57(() => {
31931
32255
  const handleEscape = (e) => {
31932
32256
  const target = e.target;
31933
32257
  const isInInput = target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable;
@@ -32002,7 +32326,7 @@ var SkillViewerPanel = memo49(function SkillViewerPanel2({
32002
32326
  });
32003
32327
  });
32004
32328
  // src/components/file-browser/FileBrowserSidebar.tsx
32005
- import { memo as memo50, useCallback as useCallback43, useEffect as useEffect57, useRef as useRef37 } from "react";
32329
+ import { memo as memo50, useCallback as useCallback43, useEffect as useEffect58, useRef as useRef37 } from "react";
32006
32330
  import {
32007
32331
  ChevronRight as ChevronRight17,
32008
32332
  ChevronDown as ChevronDown14,
@@ -32213,7 +32537,7 @@ function FileTypeIcon({
32213
32537
  }
32214
32538
 
32215
32539
  // src/components/file-browser/FileBrowserSidebar.tsx
32216
- import { jsx as jsx122, jsxs as jsxs106, Fragment as Fragment48 } from "react/jsx-runtime";
32540
+ import { jsx as jsx122, jsxs as jsxs106, Fragment as Fragment49 } from "react/jsx-runtime";
32217
32541
  var PANEL_KEY4 = "file-browser";
32218
32542
  var DEFAULT_WIDTH4 = 320;
32219
32543
  var MIN_WIDTH4 = 320;
@@ -32272,7 +32596,7 @@ function TreeItem({
32272
32596
  const activeViewerTabPath = getViewerTabPath3(activeViewerTab);
32273
32597
  const isSelected = selectedFile === path || activeViewerTabPath === path;
32274
32598
  const itemRef = useRef37(null);
32275
- useEffect57(() => {
32599
+ useEffect58(() => {
32276
32600
  if (isSelected) {
32277
32601
  itemRef.current?.scrollIntoView({ block: "nearest" });
32278
32602
  }
@@ -32292,7 +32616,7 @@ function TreeItem({
32292
32616
  onClick: handleClick,
32293
32617
  className: `w-full text-left flex items-center gap-1.5 px-2 py-1 text-sm hover:bg-muted/50 rounded transition-colors ${isSelected ? "bg-muted text-foreground" : "text-foreground/80"} ${gitignored || vendor ? "opacity-40" : ""}`,
32294
32618
  children: [
32295
- type === "directory" ? /* @__PURE__ */ jsxs106(Fragment48, {
32619
+ type === "directory" ? /* @__PURE__ */ jsxs106(Fragment49, {
32296
32620
  children: [
32297
32621
  isExpanded ? /* @__PURE__ */ jsx122(ChevronDown14, {
32298
32622
  className: "w-3 h-3 text-muted-foreground flex-shrink-0"
@@ -32305,7 +32629,7 @@ function TreeItem({
32305
32629
  className: "w-3.5 h-3.5 text-blue-500 flex-shrink-0"
32306
32630
  })
32307
32631
  ]
32308
- }) : /* @__PURE__ */ jsxs106(Fragment48, {
32632
+ }) : /* @__PURE__ */ jsxs106(Fragment49, {
32309
32633
  children: [
32310
32634
  /* @__PURE__ */ jsx122("span", {
32311
32635
  className: "w-3 flex-shrink-0"
@@ -32341,7 +32665,7 @@ var FileBrowserSidebarContent = memo50(function FileBrowserSidebarContent2() {
32341
32665
  });
32342
32666
  const panelWidth = usePanelWidthStore((s) => s.widths[PANEL_KEY4] ?? DEFAULT_WIDTH4);
32343
32667
  const { data: rootData, isLoading, refetch } = useFileTree(".");
32344
- useEffect57(() => {
32668
+ useEffect58(() => {
32345
32669
  if (activeFileTabPath) {
32346
32670
  revealFile(activeFileTabPath);
32347
32671
  }
@@ -32444,7 +32768,7 @@ var FileBrowserSidebarToggle = memo51(function FileBrowserSidebarToggle2() {
32444
32768
  // src/components/file-browser/FileViewerPanel.tsx
32445
32769
  import {
32446
32770
  memo as memo52,
32447
- useEffect as useEffect59,
32771
+ useEffect as useEffect60,
32448
32772
  useMemo as useMemo34,
32449
32773
  useRef as useRef39
32450
32774
  } from "react";
@@ -32454,7 +32778,7 @@ import remarkGfm3 from "remark-gfm";
32454
32778
 
32455
32779
  // src/components/workspace/ToolPreviewPanel.tsx
32456
32780
  import { CheckCircle2 as CheckCircle22, XCircle as XCircle4 } from "lucide-react";
32457
- import { useEffect as useEffect58, useMemo as useMemo33, useRef as useRef38 } from "react";
32781
+ import { useEffect as useEffect59, useMemo as useMemo33, useRef as useRef38 } from "react";
32458
32782
  import { jsx as jsx124, jsxs as jsxs108 } from "react/jsx-runtime";
32459
32783
  var LARGE_WRITE_PREVIEW_CHARS = 24000;
32460
32784
  var LARGE_WRITE_PREVIEW_LINES = 500;
@@ -33110,7 +33434,7 @@ function ToolPreviewPanel({ tab }) {
33110
33434
  };
33111
33435
  }
33112
33436
  const stablePatchPreview = lastPatchPreviewRef.current?.key === patchPreviewKey ? livePatchPreview ?? lastPatchPreviewRef.current.preview : livePatchPreview ?? persistedPatchPreview;
33113
- useEffect58(() => {
33437
+ useEffect59(() => {
33114
33438
  if (tab.toolName !== "apply_patch" || !livePatchPreview)
33115
33439
  return;
33116
33440
  const baseContent = tab.baseContent ?? appliedFile?.content;
@@ -33135,11 +33459,11 @@ function ToolPreviewPanel({ tab }) {
33135
33459
  error: tab.error
33136
33460
  });
33137
33461
  }, [tab, appliedFile?.content, livePatchPreview]);
33138
- useEffect58(() => {
33462
+ useEffect59(() => {
33139
33463
  if (shouldLoadAppliedFile)
33140
33464
  refetchAppliedFile();
33141
33465
  }, [shouldLoadAppliedFile, refetchAppliedFile]);
33142
- useEffect58(() => {
33466
+ useEffect59(() => {
33143
33467
  if (scrollSignal.length === 0)
33144
33468
  return;
33145
33469
  const frame = window.requestAnimationFrame(() => {
@@ -33357,7 +33681,7 @@ var FileViewerPanel = memo52(function FileViewerPanel2({
33357
33681
  const closeViewer = onClose ?? storeCloseViewer;
33358
33682
  const selectedFileIsImage = selectedFile ? isImageFile2(selectedFile) : false;
33359
33683
  const { data, isLoading } = useFileContent(selectedFileIsImage ? null : selectedFile);
33360
- useEffect59(() => {
33684
+ useEffect60(() => {
33361
33685
  const handleEscape = (e) => {
33362
33686
  const target = e.target;
33363
33687
  const isInInput = target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable;
@@ -33384,7 +33708,7 @@ var FileViewerPanel = memo52(function FileViewerPanel2({
33384
33708
  const patchBaseContent = patchPreview ? patchPreview.baseContent ?? data?.content ?? "" : undefined;
33385
33709
  const livePatchPreview = useMemo34(() => selectedFile && patchPreview?.patch && patchBaseContent !== undefined ? buildLivePatchPreview(patchBaseContent, patchPreview.patch, selectedFile) : null, [selectedFile, patchPreview?.patch, patchBaseContent]);
33386
33710
  const activePatchPreview = patchPreview?.status === "success" ? persistedPatchPreview ?? livePatchPreview : livePatchPreview ?? persistedPatchPreview;
33387
- useEffect59(() => {
33711
+ useEffect60(() => {
33388
33712
  if (!selectedFile || !patchPreview || !livePatchPreview)
33389
33713
  return;
33390
33714
  const baseContent = patchPreview.baseContent ?? data?.content;
@@ -33409,7 +33733,7 @@ var FileViewerPanel = memo52(function FileViewerPanel2({
33409
33733
  error: patchPreview.error
33410
33734
  });
33411
33735
  }, [selectedFile, data?.content, patchPreview, livePatchPreview]);
33412
- useEffect59(() => {
33736
+ useEffect60(() => {
33413
33737
  if (!data || !effectiveHighlight?.startLine)
33414
33738
  return;
33415
33739
  const frame = window.requestAnimationFrame(() => {
@@ -33642,7 +33966,7 @@ var FileViewerPanel = memo52(function FileViewerPanel2({
33642
33966
  });
33643
33967
  });
33644
33968
  // src/components/file-browser/QuickFilePicker.tsx
33645
- import { memo as memo53, useState as useState55, useEffect as useEffect60, useRef as useRef40, useCallback as useCallback44, useMemo as useMemo35 } from "react";
33969
+ import { memo as memo53, useState as useState55, useEffect as useEffect61, useRef as useRef40, useCallback as useCallback44, useMemo as useMemo35 } from "react";
33646
33970
  import { FileCode as FileCode3, Search as Search10 } from "lucide-react";
33647
33971
 
33648
33972
  // src/stores/filePickerStore.ts
@@ -33700,10 +34024,10 @@ var QuickFilePickerContent = memo53(function QuickFilePickerContent2() {
33700
34024
  const results = filesData.files.map((file) => ({ file, ...fuzzyMatch(query, file) })).filter((r) => r.match).sort((a, b) => b.score - a.score).slice(0, 50);
33701
34025
  return results.map((r) => r.file);
33702
34026
  }, [filesData?.files, query]);
33703
- useEffect60(() => {
34027
+ useEffect61(() => {
33704
34028
  setTimeout(() => inputRef.current?.focus(), 0);
33705
34029
  }, []);
33706
- useEffect60(() => {
34030
+ useEffect61(() => {
33707
34031
  const item = listRef.current?.children[selectedIndex];
33708
34032
  item?.scrollIntoView({ block: "nearest" });
33709
34033
  }, [selectedIndex]);
@@ -33892,7 +34216,7 @@ var BrowserPanelToggle = memo54(function BrowserPanelToggle2() {
33892
34216
  });
33893
34217
  });
33894
34218
  // src/components/browser/BrowserViewerPanel.tsx
33895
- import { useCallback as useCallback45, useEffect as useEffect61, useRef as useRef41, useState as useState56 } from "react";
34219
+ import { useCallback as useCallback45, useEffect as useEffect62, useRef as useRef41, useState as useState56 } from "react";
33896
34220
  import {
33897
34221
  ChevronLeft as ChevronLeft2,
33898
34222
  ChevronRight as ChevronRight18,
@@ -33906,20 +34230,35 @@ import {
33906
34230
 
33907
34231
  // src/hooks/useSimulator.ts
33908
34232
  import { useMutation as useMutation13, useQuery as useQuery16, useQueryClient as useQueryClient23 } from "@tanstack/react-query";
33909
- function simulatorUrl(path) {
33910
- return `${API_BASE_URL.replace(/\/$/, "")}${path}`;
33911
- }
33912
- async function readJson(response) {
33913
- const data = await response.json();
33914
- if (!response.ok) {
33915
- const message = data && typeof data === "object" && "error" in data ? String(data.error) : response.statusText;
33916
- throw new Error(message);
34233
+ import {
34234
+ getSimulatorStatus,
34235
+ startSimulator as apiStartSimulator,
34236
+ stopSimulator as apiStopSimulator
34237
+ } from "@ottocode/api";
34238
+ function getSimulatorApiErrorMessage(error, fallback) {
34239
+ if (error && typeof error === "object") {
34240
+ const record = error;
34241
+ if (typeof record.error === "string" && record.error.trim()) {
34242
+ return record.error;
34243
+ }
33917
34244
  }
33918
- return data;
34245
+ return fallback;
33919
34246
  }
33920
34247
  async function fetchSimulatorStatus() {
33921
- const response = await fetch(simulatorUrl("/v1/simulator/status"));
33922
- return readJson(response);
34248
+ const response = await getSimulatorStatus();
34249
+ if (response.error) {
34250
+ throw new Error(getSimulatorApiErrorMessage(response.error, "Failed to get simulator status"));
34251
+ }
34252
+ return response.data;
34253
+ }
34254
+ async function startSimulator(port = 3200) {
34255
+ const response = await apiStartSimulator({
34256
+ body: { port }
34257
+ });
34258
+ if (response.error) {
34259
+ throw new Error(getSimulatorApiErrorMessage(response.error, "Failed to start simulator"));
34260
+ }
34261
+ return response.data;
33923
34262
  }
33924
34263
  function useSimulatorStatus() {
33925
34264
  return useQuery16({
@@ -33928,11 +34267,39 @@ function useSimulatorStatus() {
33928
34267
  refetchInterval: 3000
33929
34268
  });
33930
34269
  }
34270
+ function useStartSimulator() {
34271
+ const queryClient = useQueryClient23();
34272
+ return useMutation13({
34273
+ mutationFn: (port) => startSimulator(port),
34274
+ onMutate: (port) => {
34275
+ const previous = queryClient.getQueryData([
34276
+ "simulator",
34277
+ "status"
34278
+ ]);
34279
+ queryClient.setQueryData(["simulator", "status"], {
34280
+ status: "starting",
34281
+ url: previous?.url ?? null,
34282
+ deviceName: previous?.deviceName ?? null,
34283
+ udid: previous?.udid ?? null,
34284
+ port: port ?? previous?.port ?? 3200,
34285
+ error: null,
34286
+ updatedAt: new Date().toISOString()
34287
+ });
34288
+ },
34289
+ onSuccess: (result) => {
34290
+ queryClient.setQueryData(["simulator", "status"], result);
34291
+ },
34292
+ onSettled: () => {
34293
+ queryClient.invalidateQueries({ queryKey: ["simulator"] });
34294
+ }
34295
+ });
34296
+ }
33931
34297
 
33932
34298
  // src/components/browser/BrowserViewerPanel.tsx
33933
34299
  import { jsx as jsx128, jsxs as jsxs111 } from "react/jsx-runtime";
33934
34300
  var DEFAULT_BROWSER_URL = "http://localhost:3000";
33935
34301
  var SIMULATOR_URL = "http://localhost:3200";
34302
+ var SIMULATOR_TAB_ID = "browser:simulator";
33936
34303
  var IFRAME_EMBED_TIMEOUT_MS = 6000;
33937
34304
  function normalizeBrowserUrl(value) {
33938
34305
  const trimmed = value.trim();
@@ -33971,12 +34338,23 @@ function BrowserViewerPanel({ tab }) {
33971
34338
  const [loadingProgress, setLoadingProgress] = useState56(() => isEmbeddableUrl(normalizeBrowserUrl(tab.url)) ? 12 : 0);
33972
34339
  const [embedError, setEmbedError] = useState56(null);
33973
34340
  const simulatorStatus = useSimulatorStatus();
34341
+ const {
34342
+ mutate: startSimulatorPreview,
34343
+ isPending: isStartingSimulatorPreview,
34344
+ error: startSimulatorError
34345
+ } = useStartSimulator();
33974
34346
  const nativeBrowser = typeof window !== "undefined" ? window.OTTO_NATIVE_BROWSER : undefined;
33975
34347
  const normalizedUrl = normalizeBrowserUrl(tab.url);
33976
34348
  const canRenderUrl = isEmbeddableUrl(normalizedUrl);
33977
34349
  const useNativeBrowser = Boolean(nativeBrowser?.isAvailable && canRenderUrl);
33978
34350
  const canGoBack = historyIndex > 0;
33979
34351
  const canGoForward = historyIndex >= 0 && historyIndex < historyEntries.length - 1;
34352
+ const simulatorStateStatus = simulatorStatus.data?.status;
34353
+ const simulatorStateUrl = simulatorStatus.data?.url;
34354
+ const isSimulatorPreview = tab.kind === "simulator";
34355
+ const isStartingSimulator = isStartingSimulatorPreview || simulatorStateStatus === "starting";
34356
+ const simulatorError = isSimulatorPreview && simulatorStatus.data?.status === "error" ? simulatorStatus.data.error : isSimulatorPreview ? startSimulatorError?.message : null;
34357
+ const shouldHoldSimulatorPreview = isSimulatorPreview && !simulatorError && simulatorStateStatus !== "connected";
33980
34358
  const clearIframeEmbedTimeout = useCallback45(() => {
33981
34359
  if (iframeEmbedTimeoutRef.current) {
33982
34360
  clearTimeout(iframeEmbedTimeoutRef.current);
@@ -33996,13 +34374,13 @@ function BrowserViewerPanel({ tab }) {
33996
34374
  loadingDoneTimeoutRef.current = null;
33997
34375
  }, 180);
33998
34376
  }, [clearIframeEmbedTimeout]);
33999
- useEffect61(() => {
34377
+ useEffect62(() => {
34000
34378
  setDraftUrl(tab.url);
34001
34379
  }, [tab.url]);
34002
- useEffect61(() => {
34380
+ useEffect62(() => {
34003
34381
  isLoadingRef.current = isLoading;
34004
34382
  }, [isLoading]);
34005
- useEffect61(() => {
34383
+ useEffect62(() => {
34006
34384
  if (!isLoading)
34007
34385
  return;
34008
34386
  setLoadingProgress((progress) => progress <= 0 || progress >= 100 ? 12 : progress);
@@ -34015,17 +34393,25 @@ function BrowserViewerPanel({ tab }) {
34015
34393
  }, 250);
34016
34394
  return () => clearInterval(interval);
34017
34395
  }, [isLoading]);
34018
- useEffect61(() => () => {
34396
+ useEffect62(() => () => {
34019
34397
  if (loadingDoneTimeoutRef.current) {
34020
34398
  clearTimeout(loadingDoneTimeoutRef.current);
34021
34399
  }
34022
34400
  clearIframeEmbedTimeout();
34023
34401
  }, [clearIframeEmbedTimeout]);
34024
- useEffect61(() => {
34402
+ useEffect62(() => {
34403
+ if (shouldHoldSimulatorPreview) {
34404
+ clearIframeEmbedTimeout();
34405
+ return;
34406
+ }
34025
34407
  if (!isLoading || !canRenderUrl || useNativeBrowser) {
34026
34408
  clearIframeEmbedTimeout();
34027
34409
  return;
34028
34410
  }
34411
+ if (isSimulatorPreview) {
34412
+ clearIframeEmbedTimeout();
34413
+ return;
34414
+ }
34029
34415
  clearIframeEmbedTimeout();
34030
34416
  iframeEmbedTimeoutRef.current = setTimeout(() => {
34031
34417
  setEmbedError("This site may block embedding in Otto, or it took too long to load.");
@@ -34033,13 +34419,34 @@ function BrowserViewerPanel({ tab }) {
34033
34419
  setLoadingProgress(0);
34034
34420
  }, IFRAME_EMBED_TIMEOUT_MS);
34035
34421
  return clearIframeEmbedTimeout;
34036
- }, [canRenderUrl, clearIframeEmbedTimeout, isLoading, useNativeBrowser]);
34037
- useEffect61(() => () => {
34422
+ }, [
34423
+ canRenderUrl,
34424
+ clearIframeEmbedTimeout,
34425
+ isSimulatorPreview,
34426
+ isLoading,
34427
+ shouldHoldSimulatorPreview,
34428
+ useNativeBrowser
34429
+ ]);
34430
+ useEffect62(() => {
34431
+ if (!shouldHoldSimulatorPreview)
34432
+ return;
34433
+ setEmbedError(null);
34434
+ setIsLoading(true);
34435
+ setLoadingProgress((progress) => progress <= 0 || progress >= 100 ? 12 : progress);
34436
+ }, [shouldHoldSimulatorPreview]);
34437
+ useEffect62(() => {
34438
+ if (!isSimulatorPreview || simulatorStateStatus !== "connected")
34439
+ return;
34440
+ setEmbedError(null);
34441
+ setIsLoading(true);
34442
+ setLoadingProgress(12);
34443
+ }, [isSimulatorPreview, simulatorStateStatus]);
34444
+ useEffect62(() => () => {
34038
34445
  if (nativeBrowser?.isAvailable) {
34039
34446
  nativeBrowser.unmount(tab.id);
34040
34447
  }
34041
34448
  }, [nativeBrowser, tab.id]);
34042
- useEffect61(() => {
34449
+ useEffect62(() => {
34043
34450
  const nextUrl = normalizeBrowserUrl(tab.url);
34044
34451
  if (!nextUrl)
34045
34452
  return;
@@ -34053,7 +34460,7 @@ function BrowserViewerPanel({ tab }) {
34053
34460
  return nextEntries;
34054
34461
  });
34055
34462
  }, [tab.url, historyIndex]);
34056
- useEffect61(() => {
34463
+ useEffect62(() => {
34057
34464
  const url = simulatorStatus.data?.url ?? SIMULATOR_URL;
34058
34465
  if (tab.kind === "simulator" && simulatorStatus.data?.status === "connected" && url !== tab.url) {
34059
34466
  openBrowserTab(url, {
@@ -34062,7 +34469,7 @@ function BrowserViewerPanel({ tab }) {
34062
34469
  });
34063
34470
  }
34064
34471
  }, [openBrowserTab, simulatorStatus.data, tab.kind, tab.url]);
34065
- useEffect61(() => {
34472
+ useEffect62(() => {
34066
34473
  if (!useNativeBrowser || !nativeBrowser?.isAvailable)
34067
34474
  return;
34068
34475
  const content = contentRef.current;
@@ -34155,7 +34562,32 @@ function BrowserViewerPanel({ tab }) {
34155
34562
  nativeBrowser.setVisible(tab.id, false);
34156
34563
  }
34157
34564
  };
34158
- const simulatorError = tab.kind === "simulator" && simulatorStatus.data?.status === "error" ? simulatorStatus.data.error : null;
34565
+ const openSimulatorPreview = useCallback45(() => {
34566
+ const previewUrl = simulatorStateUrl ?? SIMULATOR_URL;
34567
+ openBrowserTab(previewUrl, {
34568
+ kind: "simulator",
34569
+ title: "Simulator"
34570
+ });
34571
+ if (simulatorStateStatus === "connected" || simulatorStateStatus === "starting" || isStartingSimulatorPreview) {
34572
+ return;
34573
+ }
34574
+ startSimulatorPreview(3200, {
34575
+ onSuccess: (result) => {
34576
+ openBrowserTab(result.url ?? SIMULATOR_URL, {
34577
+ kind: "simulator",
34578
+ title: "Simulator"
34579
+ });
34580
+ reloadBrowserTab(SIMULATOR_TAB_ID);
34581
+ }
34582
+ });
34583
+ }, [
34584
+ isStartingSimulatorPreview,
34585
+ openBrowserTab,
34586
+ reloadBrowserTab,
34587
+ simulatorStateStatus,
34588
+ simulatorStateUrl,
34589
+ startSimulatorPreview
34590
+ ]);
34159
34591
  return /* @__PURE__ */ jsxs111("div", {
34160
34592
  className: "h-full w-full min-w-0 bg-background flex flex-col",
34161
34593
  children: [
@@ -34227,40 +34659,43 @@ function BrowserViewerPanel({ tab }) {
34227
34659
  ]
34228
34660
  })
34229
34661
  }),
34230
- /* @__PURE__ */ jsx128("button", {
34231
- type: "button",
34232
- onClick: () => window.open(normalizedUrl, "_blank", "noopener,noreferrer"),
34233
- disabled: !canRenderUrl,
34234
- title: "Open externally",
34235
- className: "flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-accent-foreground disabled:text-muted-foreground/40 disabled:hover:bg-transparent",
34236
- children: /* @__PURE__ */ jsx128(ExternalLink11, {
34237
- className: "h-4 w-4"
34238
- })
34239
- }),
34240
- /* @__PURE__ */ jsx128("button", {
34241
- type: "button",
34242
- onClick: () => openBrowserTab("", {
34243
- kind: "browser",
34244
- title: "Browser",
34245
- newTab: true
34246
- }),
34247
- title: "New browser preview",
34248
- className: "flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-accent-foreground",
34249
- children: /* @__PURE__ */ jsx128(Plus9, {
34250
- className: "h-4 w-4"
34251
- })
34252
- }),
34253
- /* @__PURE__ */ jsx128("button", {
34254
- type: "button",
34255
- onClick: () => openBrowserTab(SIMULATOR_URL, {
34256
- kind: "simulator",
34257
- title: "Simulator"
34258
- }),
34259
- title: "Simulator preview",
34260
- className: "flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-accent-foreground",
34261
- children: /* @__PURE__ */ jsx128(Smartphone, {
34262
- className: "h-4 w-4"
34263
- })
34662
+ /* @__PURE__ */ jsxs111("div", {
34663
+ className: "flex shrink-0 items-center gap-1",
34664
+ "data-smart-edge-ignore": "right",
34665
+ children: [
34666
+ /* @__PURE__ */ jsx128("button", {
34667
+ type: "button",
34668
+ onClick: () => window.open(normalizedUrl, "_blank", "noopener,noreferrer"),
34669
+ disabled: !canRenderUrl,
34670
+ title: "Open externally",
34671
+ className: "flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-accent-foreground disabled:text-muted-foreground/40 disabled:hover:bg-transparent",
34672
+ children: /* @__PURE__ */ jsx128(ExternalLink11, {
34673
+ className: "h-4 w-4"
34674
+ })
34675
+ }),
34676
+ /* @__PURE__ */ jsx128("button", {
34677
+ type: "button",
34678
+ onClick: () => openBrowserTab("", {
34679
+ kind: "browser",
34680
+ title: "Browser",
34681
+ newTab: true
34682
+ }),
34683
+ title: "New browser preview",
34684
+ className: "flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-accent-foreground",
34685
+ children: /* @__PURE__ */ jsx128(Plus9, {
34686
+ className: "h-4 w-4"
34687
+ })
34688
+ }),
34689
+ /* @__PURE__ */ jsx128("button", {
34690
+ type: "button",
34691
+ onClick: openSimulatorPreview,
34692
+ title: isStartingSimulator ? "Starting simulator preview" : "Start simulator preview",
34693
+ className: "flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-accent-foreground",
34694
+ children: /* @__PURE__ */ jsx128(Smartphone, {
34695
+ className: `h-4 w-4 ${isStartingSimulator ? "animate-pulse" : ""}`
34696
+ })
34697
+ })
34698
+ ]
34264
34699
  })
34265
34700
  ]
34266
34701
  })
@@ -34272,7 +34707,32 @@ function BrowserViewerPanel({ tab }) {
34272
34707
  /* @__PURE__ */ jsx128("div", {
34273
34708
  ref: contentRef,
34274
34709
  className: "min-h-0 flex-1 bg-muted/20",
34275
- children: useNativeBrowser && canRenderUrl ? /* @__PURE__ */ jsx128("div", {
34710
+ children: shouldHoldSimulatorPreview ? /* @__PURE__ */ jsx128("div", {
34711
+ className: "h-full w-full flex items-center justify-center p-6 text-center",
34712
+ children: /* @__PURE__ */ jsxs111("div", {
34713
+ className: "max-w-md rounded-lg border border-border bg-background p-6 shadow-sm",
34714
+ children: [
34715
+ /* @__PURE__ */ jsx128(Smartphone, {
34716
+ className: "mx-auto mb-3 h-8 w-8 animate-pulse text-violet-500"
34717
+ }),
34718
+ /* @__PURE__ */ jsx128("h2", {
34719
+ className: "mb-2 text-sm font-semibold text-foreground",
34720
+ children: isStartingSimulator || simulatorStatus.isLoading ? "Starting simulator preview" : "Simulator preview is not running"
34721
+ }),
34722
+ /* @__PURE__ */ jsx128("p", {
34723
+ className: "mb-4 text-xs leading-relaxed text-muted-foreground",
34724
+ children: isStartingSimulator || simulatorStatus.isLoading ? "Otto is waiting for serve-sim to publish a preview URL." : "Start serve-sim before loading the simulator web preview."
34725
+ }),
34726
+ !isStartingSimulator && !simulatorStatus.isLoading && /* @__PURE__ */ jsx128(Button, {
34727
+ type: "button",
34728
+ variant: "secondary",
34729
+ size: "sm",
34730
+ onClick: openSimulatorPreview,
34731
+ children: "Start simulator"
34732
+ })
34733
+ ]
34734
+ })
34735
+ }) : useNativeBrowser && canRenderUrl ? /* @__PURE__ */ jsx128("div", {
34276
34736
  className: "h-full w-full bg-background"
34277
34737
  }) : canRenderUrl && !embedError ? /* @__PURE__ */ jsx128("iframe", {
34278
34738
  ref: iframeRef,
@@ -34364,7 +34824,7 @@ function BrowserViewerPanel({ tab }) {
34364
34824
  });
34365
34825
  }
34366
34826
  // src/components/workspace/ViewerTabs.tsx
34367
- import { memo as memo55, useEffect as useEffect62 } from "react";
34827
+ import { memo as memo55, useEffect as useEffect63 } from "react";
34368
34828
  import { Code2, GitCommit as GitCommit5, Globe2 as Globe24, Smartphone as Smartphone2, X as X25 } from "lucide-react";
34369
34829
  import { jsx as jsx129, jsxs as jsxs112 } from "react/jsx-runtime";
34370
34830
  function tabKindLabel(tab) {
@@ -34547,7 +35007,7 @@ var ViewerTabs = memo55(function ViewerTabs2() {
34547
35007
  const closeTab = useViewerTabsStore((state) => state.closeTab);
34548
35008
  const closeAllTabs = useViewerTabsStore((state) => state.closeAllTabs);
34549
35009
  const updateSessionFileOperationIndex = useViewerTabsStore((state) => state.updateSessionFileOperationIndex);
34550
- useEffect62(() => {
35010
+ useEffect63(() => {
34551
35011
  const handleKeyDown = (event) => {
34552
35012
  const target = event.target;
34553
35013
  const isInInput = target?.tagName === "INPUT" || target?.tagName === "TEXTAREA" || target?.isContentEditable;
@@ -34713,10 +35173,10 @@ ${tabKindLabel(tab)}`,
34713
35173
  });
34714
35174
  });
34715
35175
  // src/components/onboarding/OnboardingModal.tsx
34716
- import { memo as memo58, useEffect as useEffect65 } from "react";
35176
+ import { memo as memo58, useEffect as useEffect66 } from "react";
34717
35177
 
34718
35178
  // src/components/onboarding/steps/ProviderSetupStep.tsx
34719
- import { memo as memo56, useEffect as useEffect63, useState as useState57, useRef as useRef42 } from "react";
35179
+ import { memo as memo56, useEffect as useEffect64, useState as useState57, useRef as useRef42 } from "react";
34720
35180
  import {
34721
35181
  Copy as Copy5,
34722
35182
  Check as Check16,
@@ -34732,7 +35192,7 @@ import {
34732
35192
  Globe as Globe8
34733
35193
  } from "lucide-react";
34734
35194
  import { QRCodeSVG as QRCodeSVG3 } from "qrcode.react";
34735
- import { jsx as jsx130, jsxs as jsxs113, Fragment as Fragment49 } from "react/jsx-runtime";
35195
+ import { jsx as jsx130, jsxs as jsxs113, Fragment as Fragment50 } from "react/jsx-runtime";
34736
35196
  var CUSTOM_PROVIDER_COMPATIBILITY_OPTIONS = [
34737
35197
  { value: "openai-compatible", label: "OpenAI-compatible" },
34738
35198
  { value: "openai", label: "OpenAI" },
@@ -34839,34 +35299,34 @@ var ProviderSetupStep = memo56(function ProviderSetupStep2({
34839
35299
  const { fetchBalance } = useOttoRouterBalance("ottorouter");
34840
35300
  const effectivePayg = payg?.effectiveSpendableUsd ?? balance ?? 0;
34841
35301
  const ottorouterStatusLabel = subscription?.active ? `GO ${(subscription.creditsRemaining ?? 0).toFixed(1)} credits` : `$${effectivePayg.toFixed(2)}`;
34842
- useEffect63(() => {
35302
+ useEffect64(() => {
34843
35303
  if (prevTopupModalOpen.current && !isTopupModalOpen) {
34844
35304
  fetchBalance();
34845
35305
  }
34846
35306
  prevTopupModalOpen.current = isTopupModalOpen;
34847
35307
  }, [isTopupModalOpen, fetchBalance]);
34848
- useEffect63(() => {
35308
+ useEffect64(() => {
34849
35309
  if (!authStatus.ottorouter.configured && !isSettingUp) {
34850
35310
  setIsSettingUp(true);
34851
35311
  onSetupWallet().finally(() => setIsSettingUp(false));
34852
35312
  }
34853
35313
  }, [authStatus.ottorouter.configured, onSetupWallet, isSettingUp]);
34854
- useEffect63(() => {
35314
+ useEffect64(() => {
34855
35315
  if (addingProvider && apiKeyInputRef.current) {
34856
35316
  apiKeyInputRef.current.focus();
34857
35317
  }
34858
35318
  }, [addingProvider]);
34859
- useEffect63(() => {
35319
+ useEffect64(() => {
34860
35320
  if (oauthSession && oauthCodeInputRef.current) {
34861
35321
  oauthCodeInputRef.current.focus();
34862
35322
  }
34863
35323
  }, [oauthSession]);
34864
- useEffect63(() => {
35324
+ useEffect64(() => {
34865
35325
  if (isImportModalOpen && importPrivateKeyRef.current) {
34866
35326
  importPrivateKeyRef.current.focus();
34867
35327
  }
34868
35328
  }, [isImportModalOpen]);
34869
- useEffect63(() => {
35329
+ useEffect64(() => {
34870
35330
  if (!copilotPolling || !copilotDevice || !copilotPollFnRef.current)
34871
35331
  return;
34872
35332
  copilotCancelledRef.current = false;
@@ -34911,7 +35371,7 @@ var ProviderSetupStep = memo56(function ProviderSetupStep2({
34911
35371
  clearTimeout(timeout);
34912
35372
  };
34913
35373
  }, [copilotPolling, copilotDevice]);
34914
- useEffect63(() => {
35374
+ useEffect64(() => {
34915
35375
  if (!openAIPolling || !openAIDevice || !openAIPollFnRef.current)
34916
35376
  return;
34917
35377
  openAICancelledRef.current = false;
@@ -35264,7 +35724,7 @@ var ProviderSetupStep = memo56(function ProviderSetupStep2({
35264
35724
  setImportWalletError(null);
35265
35725
  setImportPrivateKey("");
35266
35726
  };
35267
- useEffect63(() => {
35727
+ useEffect64(() => {
35268
35728
  const handleNativeBack = (event) => {
35269
35729
  const customEvent = event;
35270
35730
  if (!customEvent.detail || customEvent.detail.handled)
@@ -36222,7 +36682,7 @@ var ProviderSetupStep = memo56(function ProviderSetupStep2({
36222
36682
  className: "flex-1 h-11 px-4 bg-foreground text-background rounded-lg font-medium hover:bg-foreground/90 transition-colors disabled:opacity-50 flex items-center justify-center gap-2",
36223
36683
  children: isOpeningPopup ? /* @__PURE__ */ jsx130(StableSpinner, {
36224
36684
  title: "Opening OAuth popup"
36225
- }) : /* @__PURE__ */ jsxs113(Fragment49, {
36685
+ }) : /* @__PURE__ */ jsxs113(Fragment50, {
36226
36686
  children: [
36227
36687
  "Continue",
36228
36688
  /* @__PURE__ */ jsx130(ExternalLink12, {
@@ -36381,7 +36841,7 @@ var ProviderSetupStep = memo56(function ProviderSetupStep2({
36381
36841
  children: /* @__PURE__ */ jsx130("div", {
36382
36842
  className: "h-9 w-48 bg-muted-foreground/20 rounded"
36383
36843
  })
36384
- }) : openAIDevice ? /* @__PURE__ */ jsxs113(Fragment49, {
36844
+ }) : openAIDevice ? /* @__PURE__ */ jsxs113(Fragment50, {
36385
36845
  children: [
36386
36846
  /* @__PURE__ */ jsx130("code", {
36387
36847
  className: "text-3xl font-mono font-bold tracking-widest text-foreground bg-muted px-6 py-3 rounded-lg select-all",
@@ -36429,7 +36889,7 @@ var ProviderSetupStep = memo56(function ProviderSetupStep2({
36429
36889
  className: "flex-1 h-11 px-4 bg-foreground text-background rounded-lg font-medium hover:bg-foreground/90 transition-colors disabled:opacity-50 flex items-center justify-center gap-2",
36430
36890
  children: openAIPolling || openAILoading ? /* @__PURE__ */ jsx130(StableSpinner, {
36431
36891
  title: "Opening OpenAI"
36432
- }) : /* @__PURE__ */ jsxs113(Fragment49, {
36892
+ }) : /* @__PURE__ */ jsxs113(Fragment50, {
36433
36893
  children: [
36434
36894
  "Open OpenAI",
36435
36895
  /* @__PURE__ */ jsx130(ExternalLink12, {
@@ -36484,7 +36944,7 @@ var ProviderSetupStep = memo56(function ProviderSetupStep2({
36484
36944
  })
36485
36945
  ]
36486
36946
  }),
36487
- copilotAuthMode === "oauth" ? /* @__PURE__ */ jsxs113(Fragment49, {
36947
+ copilotAuthMode === "oauth" ? /* @__PURE__ */ jsxs113(Fragment50, {
36488
36948
  children: [
36489
36949
  /* @__PURE__ */ jsx130("p", {
36490
36950
  className: "text-sm text-muted-foreground",
@@ -36497,7 +36957,7 @@ var ProviderSetupStep = memo56(function ProviderSetupStep2({
36497
36957
  children: /* @__PURE__ */ jsx130("div", {
36498
36958
  className: "h-9 w-48 bg-muted-foreground/20 rounded"
36499
36959
  })
36500
- }) : copilotDevice ? /* @__PURE__ */ jsxs113(Fragment49, {
36960
+ }) : copilotDevice ? /* @__PURE__ */ jsxs113(Fragment50, {
36501
36961
  children: [
36502
36962
  /* @__PURE__ */ jsx130("code", {
36503
36963
  className: "text-3xl font-mono font-bold tracking-widest text-foreground bg-muted px-6 py-3 rounded-lg select-all",
@@ -36517,7 +36977,7 @@ var ProviderSetupStep = memo56(function ProviderSetupStep2({
36517
36977
  }) : null
36518
36978
  })
36519
36979
  ]
36520
- }) : /* @__PURE__ */ jsxs113(Fragment49, {
36980
+ }) : /* @__PURE__ */ jsxs113(Fragment50, {
36521
36981
  children: [
36522
36982
  /* @__PURE__ */ jsx130("p", {
36523
36983
  className: "text-sm text-muted-foreground",
@@ -36590,7 +37050,7 @@ var ProviderSetupStep = memo56(function ProviderSetupStep2({
36590
37050
  className: "flex-1 h-11 px-4 bg-foreground text-background rounded-lg font-medium hover:bg-foreground/90 transition-colors disabled:opacity-50 flex items-center justify-center gap-2",
36591
37051
  children: copilotPolling || copilotLoading ? /* @__PURE__ */ jsx130(StableSpinner, {
36592
37052
  title: "Opening GitHub"
36593
- }) : /* @__PURE__ */ jsxs113(Fragment49, {
37053
+ }) : /* @__PURE__ */ jsxs113(Fragment50, {
36594
37054
  children: [
36595
37055
  "Open GitHub",
36596
37056
  /* @__PURE__ */ jsx130(ExternalLink12, {
@@ -36619,9 +37079,9 @@ var ProviderSetupStep = memo56(function ProviderSetupStep2({
36619
37079
  });
36620
37080
 
36621
37081
  // src/components/onboarding/steps/DefaultsStep.tsx
36622
- import { memo as memo57, useState as useState58, useEffect as useEffect64, useId as useId5, useRef as useRef43 } from "react";
37082
+ import { memo as memo57, useState as useState58, useEffect as useEffect65, useId as useId5, useRef as useRef43 } from "react";
36623
37083
  import { ArrowLeft, Sparkles as Sparkles9, ChevronDown as ChevronDown15 } from "lucide-react";
36624
- import { jsx as jsx131, jsxs as jsxs114, Fragment as Fragment50 } from "react/jsx-runtime";
37084
+ import { jsx as jsx131, jsxs as jsxs114, Fragment as Fragment51 } from "react/jsx-runtime";
36625
37085
  var DefaultsStep = memo57(function DefaultsStep2({
36626
37086
  authStatus,
36627
37087
  onComplete,
@@ -36642,7 +37102,7 @@ var DefaultsStep = memo57(function DefaultsStep2({
36642
37102
  const modelId = useId5();
36643
37103
  const agentId = useId5();
36644
37104
  const approvalId = useId5();
36645
- useEffect64(() => {
37105
+ useEffect65(() => {
36646
37106
  const loadConfig = async () => {
36647
37107
  try {
36648
37108
  const [configData, modelsData] = await Promise.all([
@@ -36673,7 +37133,7 @@ var DefaultsStep = memo57(function DefaultsStep2({
36673
37133
  };
36674
37134
  loadConfig();
36675
37135
  }, []);
36676
- useEffect64(() => {
37136
+ useEffect65(() => {
36677
37137
  if (config2?.agents?.length) {
36678
37138
  const agents = config2.agents;
36679
37139
  if (!selectedAgent || !agents.includes(selectedAgent)) {
@@ -36681,7 +37141,7 @@ var DefaultsStep = memo57(function DefaultsStep2({
36681
37141
  }
36682
37142
  }
36683
37143
  }, [config2, selectedAgent]);
36684
- useEffect64(() => {
37144
+ useEffect65(() => {
36685
37145
  if (allModels?.[selectedProvider] && hasUserChangedProvider.current) {
36686
37146
  const providerModels = allModels[selectedProvider];
36687
37147
  if (!providerModels.models.some((m) => m.id === selectedModel)) {
@@ -36956,14 +37416,14 @@ var DefaultsStep = memo57(function DefaultsStep2({
36956
37416
  onClick: handleFinish,
36957
37417
  disabled: isSaving,
36958
37418
  className: "flex items-center gap-2 px-6 py-3 bg-primary text-primary-foreground rounded-lg font-medium hover:bg-primary/90 transition-colors disabled:opacity-50",
36959
- children: isSaving ? /* @__PURE__ */ jsxs114(Fragment50, {
37419
+ children: isSaving ? /* @__PURE__ */ jsxs114(Fragment51, {
36960
37420
  children: [
36961
37421
  /* @__PURE__ */ jsx131(StableSpinner, {
36962
37422
  title: "Setting up defaults"
36963
37423
  }),
36964
37424
  "Setting up..."
36965
37425
  ]
36966
- }) : /* @__PURE__ */ jsxs114(Fragment50, {
37426
+ }) : /* @__PURE__ */ jsxs114(Fragment51, {
36967
37427
  children: [
36968
37428
  "Start Using otto",
36969
37429
  /* @__PURE__ */ jsx131(Sparkles9, {
@@ -37014,7 +37474,7 @@ var OnboardingModal = memo58(function OnboardingModal2({
37014
37474
  importCopilotTokenFromGh,
37015
37475
  getCopilotDiagnostics
37016
37476
  } = useAuthStatus();
37017
- useEffect65(() => {
37477
+ useEffect66(() => {
37018
37478
  if (!isOpen)
37019
37479
  return;
37020
37480
  const handleNativeBack = (event) => {
@@ -37100,9 +37560,9 @@ var OnboardingModal = memo58(function OnboardingModal2({
37100
37560
  });
37101
37561
  });
37102
37562
  // src/components/dashboard/UsageDashboard.tsx
37103
- import { useCallback as useCallback46, useEffect as useEffect66, useMemo as useMemo36, useState as useState59 } from "react";
37563
+ import { useCallback as useCallback46, useEffect as useEffect67, useMemo as useMemo36, useState as useState59 } from "react";
37104
37564
  import { AlertTriangle as AlertTriangle3, ArrowLeft as ArrowLeft2, Globe2 as Globe25, RefreshCw as RefreshCw15 } from "lucide-react";
37105
- import { jsx as jsx133, jsxs as jsxs116, Fragment as Fragment51 } from "react/jsx-runtime";
37565
+ import { jsx as jsx133, jsxs as jsxs116, Fragment as Fragment52 } from "react/jsx-runtime";
37106
37566
  function formatNumber(n) {
37107
37567
  if (!Number.isFinite(n) || n === 0)
37108
37568
  return "0";
@@ -37690,7 +38150,7 @@ function UsageDashboard({ onBack }) {
37690
38150
  setLoading(false);
37691
38151
  }
37692
38152
  }, [scope]);
37693
- useEffect66(() => {
38153
+ useEffect67(() => {
37694
38154
  fetchStats();
37695
38155
  }, [fetchStats]);
37696
38156
  const handleBack = useCallback46(() => {
@@ -37814,7 +38274,7 @@ function UsageDashboard({ onBack }) {
37814
38274
  className: "py-24 text-center text-xs text-muted-foreground",
37815
38275
  children: "loading…"
37816
38276
  }),
37817
- stats && /* @__PURE__ */ jsxs116(Fragment51, {
38277
+ stats && /* @__PURE__ */ jsxs116(Fragment52, {
37818
38278
  children: [
37819
38279
  /* @__PURE__ */ jsxs116("div", {
37820
38280
  className: "rounded-2xl border border-border bg-gradient-to-br from-card to-card/40 px-6 py-7",
@@ -37953,7 +38413,7 @@ function UsageDashboard({ onBack }) {
37953
38413
  " api"
37954
38414
  ]
37955
38415
  }),
37956
- stats.totals.messagesByAuth.oauth > 0 && /* @__PURE__ */ jsxs116(Fragment51, {
38416
+ stats.totals.messagesByAuth.oauth > 0 && /* @__PURE__ */ jsxs116(Fragment52, {
37957
38417
  children: [
37958
38418
  /* @__PURE__ */ jsx133("span", {
37959
38419
  className: "text-muted-foreground/40",
@@ -37969,7 +38429,7 @@ function UsageDashboard({ onBack }) {
37969
38429
  })
37970
38430
  ]
37971
38431
  }),
37972
- stats.totals.messagesByAuth.subscription > 0 && /* @__PURE__ */ jsxs116(Fragment51, {
38432
+ stats.totals.messagesByAuth.subscription > 0 && /* @__PURE__ */ jsxs116(Fragment52, {
37973
38433
  children: [
37974
38434
  /* @__PURE__ */ jsx133("span", {
37975
38435
  className: "text-muted-foreground/40",
@@ -38144,4 +38604,4 @@ export {
38144
38604
  AssistantMessageGroup
38145
38605
  };
38146
38606
 
38147
- //# debugId=D87EB3763E9B505964756E2164756E21
38607
+ //# debugId=FBCDE45D41C93AD964756E2164756E21