@opengeni/react 0.45.0 → 0.48.0

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 (141) hide show
  1. package/README.md +68 -12
  2. package/dist/{chunk-U3CSCMWA.js → chunk-FK6VG4LL.js} +9 -13
  3. package/dist/chunk-FK6VG4LL.js.map +1 -0
  4. package/dist/{chunk-HFO4ERGQ.js → chunk-FWXGSNXT.js} +2 -2
  5. package/dist/{chunk-SRFUT2ZU.js → chunk-I4MPFDRP.js} +119 -63
  6. package/dist/chunk-I4MPFDRP.js.map +1 -0
  7. package/dist/{chunk-EB67J347.js → chunk-KZ73RL76.js} +111 -31
  8. package/dist/chunk-KZ73RL76.js.map +1 -0
  9. package/dist/{chunk-LWR4MXSS.js → chunk-LHLYWG5R.js} +10 -1
  10. package/dist/chunk-LHLYWG5R.js.map +1 -0
  11. package/dist/{chunk-L3EWO3UK.js → chunk-NJ7M2B6Z.js} +445 -84
  12. package/dist/chunk-NJ7M2B6Z.js.map +1 -0
  13. package/dist/{chunk-VCBFUSUZ.js → chunk-O4VXOBGF.js} +3 -3
  14. package/dist/{chunk-VCBFUSUZ.js.map → chunk-O4VXOBGF.js.map} +1 -1
  15. package/dist/{chunk-6EDV6YFE.js → chunk-OL6QVXGF.js} +121 -29
  16. package/dist/chunk-OL6QVXGF.js.map +1 -0
  17. package/dist/chunk-VZTQ73XT.js +86 -0
  18. package/dist/chunk-VZTQ73XT.js.map +1 -0
  19. package/dist/{chunk-EHSYZ4KP.js → chunk-WYA65Y3F.js} +16 -16
  20. package/dist/chunk-WYA65Y3F.js.map +1 -0
  21. package/dist/{chunk-FD72GJMK.js → chunk-XCNTXWBB.js} +274 -125
  22. package/dist/chunk-XCNTXWBB.js.map +1 -0
  23. package/dist/client.d.ts +1 -1
  24. package/dist/components/chat-composer.d.ts +7 -2
  25. package/dist/components/composer-transcription-control.d.ts +2 -0
  26. package/dist/components/composer.d.ts +14 -0
  27. package/dist/components/machines/machine-detail.d.ts +3 -1
  28. package/dist/components/machines-dashboard.d.ts +3 -3
  29. package/dist/components/message-timeline.d.ts +12 -2
  30. package/dist/components/sandbox-files.d.ts +2 -2
  31. package/dist/components/sandbox-terminal.d.ts +29 -0
  32. package/dist/components/sandbox-workspace.d.ts +5 -1
  33. package/dist/components/workspace-dock.d.ts +3 -1
  34. package/dist/composer.d.ts +2 -2
  35. package/dist/composer.js +6 -6
  36. package/dist/hooks/internal.d.ts +6 -0
  37. package/dist/hooks/use-codex-accounts.d.ts +1 -1
  38. package/dist/hooks/use-environments.d.ts +10 -4
  39. package/dist/hooks/use-machines.d.ts +7 -0
  40. package/dist/hooks/use-sandbox-files.d.ts +19 -0
  41. package/dist/hooks/use-sandbox-git.d.ts +7 -0
  42. package/dist/hooks/use-transcription.d.ts +2 -2
  43. package/dist/hooks/use-voice-input.d.ts +13 -4
  44. package/dist/index.d.ts +6 -5
  45. package/dist/index.js +904 -538
  46. package/dist/index.js.map +1 -1
  47. package/dist/lib/composer-responsive-context.d.ts +9 -0
  48. package/dist/lib/use-portal-token-style.d.ts +14 -3
  49. package/dist/machines.js +2 -2
  50. package/dist/{queue-surface-implementation-J4XJFKRW.js → queue-surface-implementation-U5JFG7HT.js} +19 -18
  51. package/dist/queue-surface-implementation-U5JFG7HT.js.map +1 -0
  52. package/dist/realtime/realtime-control.d.ts +9 -0
  53. package/dist/realtime.js +29 -32
  54. package/dist/realtime.js.map +1 -1
  55. package/dist/session-ui.js +5 -5
  56. package/dist/session.js +4 -4
  57. package/dist/timeline/activity-rail.d.ts +3 -2
  58. package/dist/timeline/index.d.ts +2 -2
  59. package/dist/timeline/parsers.d.ts +3 -3
  60. package/dist/timeline/registry.d.ts +3 -0
  61. package/dist/timeline/screenshot-lightbox.d.ts +1 -1
  62. package/dist/timeline/types.d.ts +4 -0
  63. package/dist/voice-recording-store.d.ts +8 -2
  64. package/package.json +20 -4
  65. package/src/client.ts +3 -0
  66. package/src/components/chat-composer.tsx +12 -2
  67. package/src/components/code-editor.tsx +1 -1
  68. package/src/components/command-palette.tsx +1 -1
  69. package/src/components/composer-transcription-control.tsx +41 -16
  70. package/src/components/composer.tsx +52 -26
  71. package/src/components/copy-button.tsx +2 -2
  72. package/src/components/desktop-viewer.tsx +2 -2
  73. package/src/components/file-browser.tsx +39 -17
  74. package/src/components/human-input-form.tsx +2 -2
  75. package/src/components/machine-card.tsx +1 -1
  76. package/src/components/machines/machine-detail.tsx +39 -15
  77. package/src/components/machines-dashboard.tsx +7 -6
  78. package/src/components/message-timeline.tsx +44 -11
  79. package/src/components/model-picker.tsx +2 -2
  80. package/src/components/model-policy-picker.tsx +16 -15
  81. package/src/components/queue-surface-implementation.tsx +20 -16
  82. package/src/components/queue-surface-state.tsx +2 -2
  83. package/src/components/sandbox-files.tsx +140 -60
  84. package/src/components/sandbox-terminal.tsx +24 -5
  85. package/src/components/sandbox-workspace.tsx +180 -142
  86. package/src/components/session-chrome.tsx +14 -14
  87. package/src/components/tooltip.tsx +13 -16
  88. package/src/components/user-message-body.tsx +1 -1
  89. package/src/components/workbench-changes.tsx +4 -4
  90. package/src/components/workspace-dock.tsx +13 -5
  91. package/src/composer.ts +3 -0
  92. package/src/hooks/internal.ts +132 -38
  93. package/src/hooks/use-codex-accounts.ts +4 -4
  94. package/src/hooks/use-environments.ts +24 -5
  95. package/src/hooks/use-file-attachments.ts +70 -32
  96. package/src/hooks/use-goal.ts +17 -5
  97. package/src/hooks/use-machines.ts +47 -1
  98. package/src/hooks/use-sandbox-files.ts +310 -59
  99. package/src/hooks/use-sandbox-git.ts +148 -41
  100. package/src/hooks/use-session-capabilities.ts +35 -24
  101. package/src/hooks/use-session-events.ts +18 -4
  102. package/src/hooks/use-transcription.ts +4 -14
  103. package/src/hooks/use-turn-queue.ts +17 -5
  104. package/src/hooks/use-voice-input.ts +481 -36
  105. package/src/hooks/use-windowed-sections.ts +9 -7
  106. package/src/index.ts +11 -6
  107. package/src/lib/composer-responsive-context.ts +15 -0
  108. package/src/lib/use-portal-token-style.ts +60 -15
  109. package/src/provider.tsx +52 -7
  110. package/src/realtime/dropdown-menu.tsx +15 -19
  111. package/src/realtime/realtime-control.tsx +18 -7
  112. package/src/timeline/activity-rail.tsx +15 -6
  113. package/src/timeline/index.ts +1 -1
  114. package/src/timeline/parsers.ts +33 -20
  115. package/src/timeline/projection.ts +23 -0
  116. package/src/timeline/registry.ts +7 -0
  117. package/src/timeline/screenshot-lightbox.tsx +15 -4
  118. package/src/timeline/shared.tsx +8 -8
  119. package/src/timeline/tool-renderers.tsx +166 -22
  120. package/src/timeline/turn-summary.tsx +6 -6
  121. package/src/timeline/types.ts +4 -0
  122. package/src/voice-recording-store.ts +15 -0
  123. package/styles/compiled.css +5065 -0
  124. package/styles/compiled.d.ts +2 -0
  125. package/styles/effective-tokens.css +27 -0
  126. package/styles/index.css +3 -0
  127. package/styles/responsive.css +196 -0
  128. package/styles/responsive.d.ts +2 -0
  129. package/styles/tokens.css +50 -17
  130. package/dist/chunk-6EDV6YFE.js.map +0 -1
  131. package/dist/chunk-EB67J347.js.map +0 -1
  132. package/dist/chunk-EHSYZ4KP.js.map +0 -1
  133. package/dist/chunk-FD72GJMK.js.map +0 -1
  134. package/dist/chunk-JB5725BI.js +0 -48
  135. package/dist/chunk-JB5725BI.js.map +0 -1
  136. package/dist/chunk-L3EWO3UK.js.map +0 -1
  137. package/dist/chunk-LWR4MXSS.js.map +0 -1
  138. package/dist/chunk-SRFUT2ZU.js.map +0 -1
  139. package/dist/chunk-U3CSCMWA.js.map +0 -1
  140. package/dist/queue-surface-implementation-J4XJFKRW.js.map +0 -1
  141. /package/dist/{chunk-HFO4ERGQ.js.map → chunk-FWXGSNXT.js.map} +0 -0
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@ import {
25
25
  healthPulses,
26
26
  pointsFor,
27
27
  useMachines
28
- } from "./chunk-6EDV6YFE.js";
28
+ } from "./chunk-OL6QVXGF.js";
29
29
  import {
30
30
  SESSION_EVENT_BROWSER_MAX_BYTES,
31
31
  SESSION_EVENT_BROWSER_MAX_COUNT,
@@ -52,7 +52,7 @@ import {
52
52
  useSessionLineage,
53
53
  useSessionMcpApprovalPolicy,
54
54
  useTurnQueue
55
- } from "./chunk-SRFUT2ZU.js";
55
+ } from "./chunk-I4MPFDRP.js";
56
56
  import {
57
57
  ActivityDisclosure,
58
58
  ActivityRail,
@@ -99,7 +99,6 @@ import {
99
99
  parseFreeformApplyPatch,
100
100
  parseToolArgs,
101
101
  rawTypeOf,
102
- redactSecrets,
103
102
  sandboxCommandExitCode,
104
103
  sessionChromeGoalPillState,
105
104
  stripExecBanner,
@@ -111,8 +110,8 @@ import {
111
110
  useThemeType,
112
111
  userMessageLikelyNeedsDisclosure,
113
112
  v4aToGitFileDiff
114
- } from "./chunk-FD72GJMK.js";
115
- import "./chunk-VCBFUSUZ.js";
113
+ } from "./chunk-XCNTXWBB.js";
114
+ import "./chunk-O4VXOBGF.js";
116
115
  import {
117
116
  buildTimeline,
118
117
  creditExhaustedFromEvents,
@@ -120,7 +119,7 @@ import {
120
119
  groupTimeline,
121
120
  sessionStatusFromEvents,
122
121
  toolDisplayName
123
- } from "./chunk-LWR4MXSS.js";
122
+ } from "./chunk-LHLYWG5R.js";
124
123
  import {
125
124
  Actions,
126
125
  AttachButton,
@@ -173,19 +172,20 @@ import {
173
172
  useSlashCommands,
174
173
  useTranscription,
175
174
  useVoiceInput
176
- } from "./chunk-L3EWO3UK.js";
175
+ } from "./chunk-NJ7M2B6Z.js";
177
176
  import {
178
177
  FILE_ONLY_MESSAGE_TEXT,
179
178
  composeSendInput,
180
179
  shouldSteerOnKey,
181
180
  shouldSubmitOnKey,
182
181
  useComposer
183
- } from "./chunk-HFO4ERGQ.js";
182
+ } from "./chunk-FWXGSNXT.js";
184
183
  import {
185
184
  useMutationRunner,
185
+ usePageLiveActivity,
186
186
  usePolledValue,
187
187
  useSessionEventTrigger
188
- } from "./chunk-EB67J347.js";
188
+ } from "./chunk-KZ73RL76.js";
189
189
  import {
190
190
  BillingClassMark,
191
191
  ModelPolicyPicker,
@@ -194,7 +194,7 @@ import {
194
194
  PickerBackHeader,
195
195
  PickerNavRow,
196
196
  defaultModelPolicyPickerMessages
197
- } from "./chunk-EHSYZ4KP.js";
197
+ } from "./chunk-WYA65Y3F.js";
198
198
  import {
199
199
  useOpenGeni,
200
200
  useOpenGeniClient
@@ -222,7 +222,7 @@ import {
222
222
  TooltipContent,
223
223
  TooltipProvider,
224
224
  TooltipTrigger
225
- } from "./chunk-U3CSCMWA.js";
225
+ } from "./chunk-FK6VG4LL.js";
226
226
  import {
227
227
  COMPOSER_PAYMENT_REQUIRED_MESSAGE,
228
228
  CREDIT_EXHAUSTION_MESSAGE,
@@ -237,8 +237,9 @@ import {
237
237
  tryParseJson
238
238
  } from "./chunk-LAKLF4PA.js";
239
239
  import {
240
+ usePortalTokenSource,
240
241
  usePortalTokenStyle
241
- } from "./chunk-JB5725BI.js";
242
+ } from "./chunk-VZTQ73XT.js";
242
243
  import {
243
244
  cn
244
245
  } from "./chunk-22KP6LR5.js";
@@ -423,6 +424,10 @@ function useVariableSets(options = {}) {
423
424
  },
424
425
  [client, workspaceId, run, refresh]
425
426
  );
427
+ const readVariable = useCallback4(
428
+ async (variableSetId, name) => await run(() => client.getVariableSetVariable(workspaceId, variableSetId, name)),
429
+ [client, workspaceId, run]
430
+ );
426
431
  const setVariable = useCallback4(
427
432
  async (variableSetId, name, value) => {
428
433
  const result = await run(
@@ -456,6 +461,7 @@ function useVariableSets(options = {}) {
456
461
  create,
457
462
  update,
458
463
  remove,
464
+ readVariable,
459
465
  setVariable,
460
466
  deleteVariable,
461
467
  mutating,
@@ -478,7 +484,10 @@ function useEnvironments(options = {}) {
478
484
  deleteVariableSetVariable: options.client.deleteEnvironmentVariable ?? options.client.deleteVariableSetVariable
479
485
  };
480
486
  }, [options.client]);
481
- const result = useVariableSets({ ...options, ...legacyClient ? { client: legacyClient } : {} });
487
+ const result = useVariableSets({
488
+ ...options,
489
+ ...legacyClient ? { client: legacyClient } : {}
490
+ });
482
491
  return { ...result, environments: result.variableSets };
483
492
  }
484
493
 
@@ -906,6 +915,8 @@ function rotationsFrom(events) {
906
915
  function useSessionCapabilities(sessionId, options = {}) {
907
916
  const { client, workspaceId } = useOpenGeni(options);
908
917
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
918
+ const pageLive = usePageLiveActivity();
919
+ const lifecycleEnabled = enabled && pageLive;
909
920
  const attachDesktop = options.attachDesktop ?? false;
910
921
  const attachTerminal = options.attachTerminal ?? false;
911
922
  const attachFiles = options.attachFiles ?? false;
@@ -940,7 +951,7 @@ function useSessionCapabilities(sessionId, options = {}) {
940
951
  epochRef.current = 0;
941
952
  viewerIdRef.current = null;
942
953
  }
943
- if (!enabled || !sessionId) {
954
+ if (!lifecycleEnabled || !sessionId) {
944
955
  setState("idle");
945
956
  setCapabilities(null);
946
957
  return;
@@ -953,7 +964,7 @@ function useSessionCapabilities(sessionId, options = {}) {
953
964
  const startedAt = Date.now();
954
965
  const clearTimers = () => {
955
966
  if (pollTimer !== null) clearTimeout(pollTimer);
956
- if (heartbeatTimer !== null) clearInterval(heartbeatTimer);
967
+ if (heartbeatTimer !== null) clearTimeout(heartbeatTimer);
957
968
  pollTimer = null;
958
969
  heartbeatTimer = null;
959
970
  };
@@ -966,19 +977,26 @@ function useSessionCapabilities(sessionId, options = {}) {
966
977
  const startHeartbeat = (caps) => {
967
978
  if (!localViewerId || heartbeatTimer !== null) return;
968
979
  const intervalMs = caps.viewerHeartbeatIntervalMs > 0 ? caps.viewerHeartbeatIntervalMs : 3e4;
969
- heartbeatTimer = setInterval(() => {
980
+ const schedule = () => {
970
981
  if (cancelled || !localViewerId) return;
971
- void client.heartbeatViewer(workspaceId, sessionId, localViewerId, { leaseEpoch: epochRef.current }).then((res) => {
972
- if (!res.alive && !cancelled) {
973
- renegotiate();
974
- }
975
- }).catch((cause) => {
976
- if (cancelled) return;
977
- if (cause instanceof OpenGeniApiError && (cause.status === 409 || cause.status === 410)) {
978
- renegotiate();
979
- }
980
- });
981
- }, intervalMs);
982
+ heartbeatTimer = setTimeout(() => {
983
+ heartbeatTimer = null;
984
+ if (cancelled || !localViewerId) return;
985
+ void client.heartbeatViewer(workspaceId, sessionId, localViewerId, {
986
+ leaseEpoch: epochRef.current
987
+ }).then((res) => {
988
+ if (!res.alive && !cancelled) {
989
+ renegotiate();
990
+ }
991
+ }).catch((cause) => {
992
+ if (cancelled) return;
993
+ if (cause instanceof OpenGeniApiError && (cause.status === 409 || cause.status === 410)) {
994
+ renegotiate();
995
+ }
996
+ }).finally(schedule);
997
+ }, intervalMs);
998
+ };
999
+ schedule();
982
1000
  };
983
1001
  const pollUntilWarm = () => {
984
1002
  pollTimer = setTimeout(() => {
@@ -1113,7 +1131,7 @@ function useSessionCapabilities(sessionId, options = {}) {
1113
1131
  client,
1114
1132
  workspaceId,
1115
1133
  sessionId,
1116
- enabled,
1134
+ lifecycleEnabled,
1117
1135
  attachDesktop,
1118
1136
  attachTerminal,
1119
1137
  attachFiles,
@@ -1816,7 +1834,7 @@ function useSandboxTerminal(sessionId, options) {
1816
1834
  }
1817
1835
 
1818
1836
  // src/hooks/use-sandbox-files.ts
1819
- import { useCallback as useCallback12, useEffect as useEffect8, useRef as useRef7, useState as useState6 } from "react";
1837
+ import { useCallback as useCallback12, useEffect as useEffect8, useMemo as useMemo4, useRef as useRef7, useState as useState6 } from "react";
1820
1838
  var CapturedFileUnavailableError = class extends Error {
1821
1839
  constructor(path, reason) {
1822
1840
  super(
@@ -1838,6 +1856,25 @@ var FileWriteConflictError = class extends Error {
1838
1856
  }
1839
1857
  code = "file_write_conflict";
1840
1858
  };
1859
+ function normalizeRoot(root) {
1860
+ const normalized = root.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
1861
+ return normalized === "." ? "" : normalized;
1862
+ }
1863
+ function qualifyStatusPath(root, path) {
1864
+ const normalized = normalizeRoot(root);
1865
+ return normalized ? `${normalized}/${path}` : path;
1866
+ }
1867
+ function summarizeStatuses(statuses) {
1868
+ const repositories = statuses.filter(({ status }) => status.isRepo);
1869
+ return {
1870
+ branch: repositories.length === 1 ? repositories[0]?.status.head ?? null : null,
1871
+ isRepo: repositories.length > 0,
1872
+ repoCount: repositories.length,
1873
+ ahead: repositories.reduce((sum, { status }) => sum + status.ahead, 0),
1874
+ behind: repositories.reduce((sum, { status }) => sum + status.behind, 0),
1875
+ dirtyCount: repositories.reduce((sum, { status }) => sum + status.files.length, 0)
1876
+ };
1877
+ }
1841
1878
  function parentOf(path) {
1842
1879
  const i = path.lastIndexOf("/");
1843
1880
  return i <= 0 ? "" : path.slice(0, i);
@@ -1881,7 +1918,10 @@ function replaceChildren(nodes, targetPath, children) {
1881
1918
  return { ...node, children };
1882
1919
  }
1883
1920
  if (node.kind === "dir" && node.children && targetPath.startsWith(`${node.path}/`)) {
1884
- return { ...node, children: replaceChildren(node.children, targetPath, children) };
1921
+ return {
1922
+ ...node,
1923
+ children: replaceChildren(node.children, targetPath, children)
1924
+ };
1885
1925
  }
1886
1926
  return node;
1887
1927
  });
@@ -1896,6 +1936,30 @@ function findNodeByPath(nodes, path) {
1896
1936
  }
1897
1937
  return void 0;
1898
1938
  }
1939
+ function loadedDirectoryPaths(nodes) {
1940
+ const paths = [""];
1941
+ const walk = (list) => {
1942
+ for (const node of list) {
1943
+ if (node.kind !== "dir" || node.children === void 0) continue;
1944
+ paths.push(node.path);
1945
+ walk(node.children);
1946
+ }
1947
+ };
1948
+ walk(nodes);
1949
+ return paths;
1950
+ }
1951
+ function initialDirectoryFrontier(rootPath, repoPaths) {
1952
+ const rootParts = normalizeRoot(rootPath).split("/").filter(Boolean);
1953
+ const paths = /* @__PURE__ */ new Set();
1954
+ for (const repoPath of repoPaths) {
1955
+ const parts = normalizeRoot(repoPath).split("/").filter(Boolean);
1956
+ if (rootParts.some((part, index) => parts[index] !== part)) continue;
1957
+ for (let depth = rootParts.length + 1; depth <= parts.length; depth += 1) {
1958
+ paths.add(parts.slice(0, depth).join("/"));
1959
+ }
1960
+ }
1961
+ return [...paths].sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b)).slice(0, 8);
1962
+ }
1899
1963
  function parentIsLoaded(nodes, parent) {
1900
1964
  if (parent === "") return true;
1901
1965
  const node = findNodeByPath(nodes, parent);
@@ -1927,7 +1991,10 @@ function removeNode(nodes, path) {
1927
1991
  return nodes.map((node) => {
1928
1992
  if (node.path === parent) {
1929
1993
  if (node.kind !== "dir" || node.children === void 0) return node;
1930
- return { ...node, children: node.children.filter((n) => n.path !== path) };
1994
+ return {
1995
+ ...node,
1996
+ children: node.children.filter((n) => n.path !== path)
1997
+ };
1931
1998
  }
1932
1999
  if (node.kind === "dir" && node.children && parent.startsWith(`${node.path}/`)) {
1933
2000
  return { ...node, children: removeNode(node.children, path) };
@@ -1996,15 +2063,23 @@ async function assertCapturedHash(bytes, expected) {
1996
2063
  function useSandboxFiles(sessionId, options = {}) {
1997
2064
  const { client, workspaceId } = useOpenGeni(options);
1998
2065
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
2066
+ const active = options.active ?? true;
1999
2067
  const rootPath = options.rootPath ?? "";
2068
+ const repoPathsKey = [
2069
+ ...new Set((options.repoPaths?.length ? options.repoPaths : [rootPath]).map(normalizeRoot))
2070
+ ].join("\0");
2071
+ const repoPaths = useMemo4(() => repoPathsKey.split("\0"), [repoPathsKey]);
2000
2072
  const capture = options.capture ?? null;
2001
2073
  const isLive2 = options.liveness === "warm" || options.liveness === "draining";
2002
- const identityKey = `${workspaceId}\0${sessionId ?? ""}\0${rootPath}`;
2074
+ const identityKey = `${workspaceId}\0${sessionId ?? ""}\0${rootPath}\0${repoPathsKey}`;
2003
2075
  const [tree, setTree] = useState6([]);
2004
2076
  const treeRef = useRef7([]);
2005
2077
  const [loading, setLoading] = useState6(false);
2006
2078
  const [expandingPaths, setExpandingPaths] = useState6(/* @__PURE__ */ new Set());
2007
2079
  const [error, setError] = useState6(null);
2080
+ const [gitSummary, setGitSummary] = useState6(null);
2081
+ const [gitLoading, setGitLoading] = useState6(false);
2082
+ const [contentRevision, setContentRevision] = useState6(0);
2008
2083
  const [stateIdentity, setStateIdentity] = useState6(identityKey);
2009
2084
  const [source, setSource] = useState6(null);
2010
2085
  const sourceRef = useRef7(null);
@@ -2016,7 +2091,9 @@ function useSandboxFiles(sessionId, options = {}) {
2016
2091
  const lastSeqRef = useRef7(0);
2017
2092
  const pendingParentsRef = useRef7(/* @__PURE__ */ new Set());
2018
2093
  const pendingGitRef = useRef7(false);
2094
+ const pendingAgentToolRef = useRef7(false);
2019
2095
  const debounceRef = useRef7(null);
2096
+ const lastCommandRefreshAtRef = useRef7(0);
2020
2097
  const ownRevisionsRef = useRef7(/* @__PURE__ */ new Set());
2021
2098
  const onMutationError = options.onMutationError;
2022
2099
  treeRef.current = tree;
@@ -2050,48 +2127,83 @@ function useSandboxFiles(sessionId, options = {}) {
2050
2127
  refreshAbortRef.current = refreshAbort;
2051
2128
  const generation = refreshGenerationRef.current += 1;
2052
2129
  setLoading(true);
2130
+ setGitLoading(true);
2053
2131
  setError(null);
2054
2132
  try {
2055
- try {
2056
- const status = await client.gitStatus(
2057
- workspaceId,
2058
- sessionId,
2059
- { path: rootPath },
2060
- { signal: refreshAbort.signal }
2061
- );
2062
- if (refreshGenerationRef.current !== generation) return;
2063
- const overlay = /* @__PURE__ */ new Map();
2064
- for (const file of status.files) {
2065
- const code = file.worktree ?? file.index;
2066
- const mapped = code ? GIT_STATUS_TO_TREE[code] : void 0;
2067
- if (mapped) overlay.set(file.path, mapped);
2068
- }
2069
- statusRef.current = overlay;
2070
- } catch {
2071
- if (refreshGenerationRef.current !== generation) return;
2072
- statusRef.current = /* @__PURE__ */ new Map();
2073
- }
2074
- const listed = await client.fsList(
2133
+ const frontierPaths = initialDirectoryFrontier(rootPath, repoPaths);
2134
+ const listRequests = [rootPath, ...frontierPaths].map((path) => ({
2135
+ path,
2136
+ depth: 1
2137
+ }));
2138
+ const statusPromise = repoPaths.length > 0 ? client.gitReadBatch(
2075
2139
  workspaceId,
2076
2140
  sessionId,
2077
- { path: rootPath, depth: 1 },
2141
+ {
2142
+ requests: repoPaths.map((root) => ({
2143
+ status: { path: root }
2144
+ }))
2145
+ },
2146
+ { signal: refreshAbort.signal }
2147
+ ).then(
2148
+ (batch) => batch.results.map((result, index) => ({
2149
+ root: repoPaths[index] ?? "",
2150
+ status: result.status
2151
+ }))
2152
+ ).catch(() => null) : Promise.resolve([]);
2153
+ const listedBatch = await client.fsListBatch(
2154
+ workspaceId,
2155
+ sessionId,
2156
+ { requests: listRequests },
2078
2157
  { signal: refreshAbort.signal }
2079
2158
  );
2080
2159
  if (refreshGenerationRef.current !== generation) return;
2160
+ const listed = listedBatch.results[0];
2161
+ if (!listed) throw new Error("Workspace list batch did not return the root result.");
2081
2162
  const children = (listed.root.children ?? []).map((node) => fsNodeToTree(node));
2082
- setTree(
2083
- (prev) => applyStatus(prev.length === 0 ? children : mergeRootChildren(prev, children))
2084
- );
2163
+ setTree((prev) => {
2164
+ let hydrated = prev.length === 0 ? children : mergeRootChildren(prev, children);
2165
+ for (const [index, path] of frontierPaths.entries()) {
2166
+ const prefetched = listedBatch.results[index + 1];
2167
+ if (!prefetched) continue;
2168
+ hydrated = replaceChildren(
2169
+ hydrated,
2170
+ path,
2171
+ (prefetched.root.children ?? []).map((node) => fsNodeToTree(node))
2172
+ );
2173
+ }
2174
+ return applyStatus(hydrated);
2175
+ });
2085
2176
  sourceRef.current = "live";
2086
2177
  setSource("live");
2178
+ setLoading(false);
2179
+ const statuses = await statusPromise;
2180
+ if (refreshGenerationRef.current !== generation) return;
2181
+ if (statuses) {
2182
+ const overlay = /* @__PURE__ */ new Map();
2183
+ for (const { root, status } of statuses) {
2184
+ for (const file of status.files) {
2185
+ const code = file.worktree ?? file.index;
2186
+ const mapped = code ? GIT_STATUS_TO_TREE[code] : void 0;
2187
+ if (mapped) overlay.set(qualifyStatusPath(root, file.path), mapped);
2188
+ }
2189
+ }
2190
+ statusRef.current = overlay;
2191
+ setGitSummary(summarizeStatuses(statuses));
2192
+ } else {
2193
+ statusRef.current = /* @__PURE__ */ new Map();
2194
+ setGitSummary(null);
2195
+ }
2196
+ setGitLoading(false);
2197
+ setTree((prev) => applyStatus(prev));
2087
2198
  } catch (cause) {
2088
2199
  if (refreshGenerationRef.current !== generation) return;
2089
2200
  setError(cause instanceof Error ? cause : new Error(String(cause)));
2090
2201
  } finally {
2091
2202
  if (refreshGenerationRef.current === generation) setLoading(false);
2203
+ if (refreshGenerationRef.current === generation) setGitLoading(false);
2092
2204
  if (refreshAbortRef.current === refreshAbort) refreshAbortRef.current = null;
2093
2205
  }
2094
- }, [client, workspaceId, sessionId, rootPath, applyStatus]);
2206
+ }, [client, workspaceId, sessionId, rootPath, repoPaths, applyStatus]);
2095
2207
  const seedFromCapture = useCallback12(
2096
2208
  (manifest) => {
2097
2209
  const overlay = /* @__PURE__ */ new Map();
@@ -2107,6 +2219,15 @@ function useSandboxFiles(sessionId, options = {}) {
2107
2219
  );
2108
2220
  sourceRef.current = "capture";
2109
2221
  setSource("capture");
2222
+ setGitSummary({
2223
+ branch: manifest.repos.length === 1 ? manifest.repos[0]?.head ?? null : null,
2224
+ isRepo: manifest.repos.length > 0,
2225
+ repoCount: manifest.repos.length,
2226
+ ahead: manifest.repos.reduce((sum, repo) => sum + repo.ahead, 0),
2227
+ behind: manifest.repos.reduce((sum, repo) => sum + repo.behind, 0),
2228
+ dirtyCount: manifest.files.length
2229
+ });
2230
+ setGitLoading(false);
2110
2231
  setError(null);
2111
2232
  },
2112
2233
  [applyStatus]
@@ -2287,7 +2408,12 @@ function useSandboxFiles(sessionId, options = {}) {
2287
2408
  const identitySignal = identityAbortRef.current.signal;
2288
2409
  const parent = parentOf(path);
2289
2410
  const exists = Boolean(findNodeByPath(tree, path));
2290
- const node = { path, name: leafOf(path), kind: "file", size: content.length };
2411
+ const node = {
2412
+ path,
2413
+ name: leafOf(path),
2414
+ kind: "file",
2415
+ size: content.length
2416
+ };
2291
2417
  return await runOptimistic(
2292
2418
  "write",
2293
2419
  (nodes) => exists ? nodes : insertNode(nodes, parent, node),
@@ -2325,11 +2451,20 @@ function useSandboxFiles(sessionId, options = {}) {
2325
2451
  async (path) => {
2326
2452
  if (!sessionId) throw new Error("no session");
2327
2453
  const parent = parentOf(path);
2328
- const node = { path, name: leafOf(path), kind: "file", size: 0 };
2454
+ const node = {
2455
+ path,
2456
+ name: leafOf(path),
2457
+ kind: "file",
2458
+ size: 0
2459
+ };
2329
2460
  await runOptimistic(
2330
2461
  "create file",
2331
2462
  (nodes) => insertNode(nodes, parent, node),
2332
- () => client.fsWrite(workspaceId, sessionId, { path, content: "", overwrite: false }),
2463
+ () => client.fsWrite(workspaceId, sessionId, {
2464
+ path,
2465
+ content: "",
2466
+ overwrite: false
2467
+ }),
2333
2468
  [parent]
2334
2469
  );
2335
2470
  },
@@ -2339,7 +2474,12 @@ function useSandboxFiles(sessionId, options = {}) {
2339
2474
  async (path) => {
2340
2475
  if (!sessionId) throw new Error("no session");
2341
2476
  const parent = parentOf(path);
2342
- const node = { path, name: leafOf(path), kind: "dir", children: [] };
2477
+ const node = {
2478
+ path,
2479
+ name: leafOf(path),
2480
+ kind: "dir",
2481
+ children: []
2482
+ };
2343
2483
  await runOptimistic(
2344
2484
  "create folder",
2345
2485
  (nodes) => insertNode(nodes, parent, node),
@@ -2401,6 +2541,8 @@ function useSandboxFiles(sessionId, options = {}) {
2401
2541
  lastSeqRef.current = 0;
2402
2542
  pendingParentsRef.current = /* @__PURE__ */ new Set();
2403
2543
  pendingGitRef.current = false;
2544
+ pendingAgentToolRef.current = false;
2545
+ lastCommandRefreshAtRef.current = 0;
2404
2546
  if (debounceRef.current) {
2405
2547
  clearTimeout(debounceRef.current);
2406
2548
  debounceRef.current = null;
@@ -2415,6 +2557,9 @@ function useSandboxFiles(sessionId, options = {}) {
2415
2557
  setSource(null);
2416
2558
  setLoading(false);
2417
2559
  setError(null);
2560
+ setGitSummary(null);
2561
+ setGitLoading(false);
2562
+ setContentRevision(0);
2418
2563
  }
2419
2564
  if (!enabled) {
2420
2565
  return;
@@ -2423,6 +2568,13 @@ function useSandboxFiles(sessionId, options = {}) {
2423
2568
  if (currentCapture && (identityChanged || !isLive2 || sourceRef.current !== "live")) {
2424
2569
  seedFromCapture(currentCapture);
2425
2570
  }
2571
+ if (!active) {
2572
+ if (!active) {
2573
+ setLoading(false);
2574
+ setGitLoading(false);
2575
+ }
2576
+ return;
2577
+ }
2426
2578
  if (isLive2 || !currentCapture) {
2427
2579
  void refresh();
2428
2580
  }
@@ -2431,34 +2583,52 @@ function useSandboxFiles(sessionId, options = {}) {
2431
2583
  refreshAbortRef.current = null;
2432
2584
  refreshGenerationRef.current += 1;
2433
2585
  };
2434
- }, [enabled, isLive2, captureRevision, identityKey, refresh, seedFromCapture]);
2586
+ }, [enabled, active, isLive2, captureRevision, identityKey, refresh, seedFromCapture]);
2435
2587
  const refreshGitOverlay = useCallback12(async () => {
2436
2588
  if (!sessionId) return;
2437
2589
  const identityGeneration = identityGenerationRef.current;
2438
2590
  const identitySignal = identityAbortRef.current.signal;
2591
+ setGitLoading(true);
2439
2592
  try {
2440
- const status = await client.gitStatus(
2593
+ const batch = await client.gitReadBatch(
2441
2594
  workspaceId,
2442
2595
  sessionId,
2443
- { path: rootPath },
2596
+ { requests: repoPaths.map((root) => ({ status: { path: root } })) },
2444
2597
  { signal: identitySignal }
2445
2598
  );
2599
+ const statuses = batch.results.map((result, index) => ({
2600
+ root: repoPaths[index] ?? "",
2601
+ status: result.status
2602
+ }));
2446
2603
  if (identityGenerationRef.current !== identityGeneration) return;
2447
2604
  const overlay = /* @__PURE__ */ new Map();
2448
- for (const file of status.files) {
2449
- const code = file.worktree ?? file.index;
2450
- const mapped = code ? GIT_STATUS_TO_TREE[code] : void 0;
2451
- if (mapped) overlay.set(file.path, mapped);
2605
+ for (const { root, status } of statuses) {
2606
+ for (const file of status.files) {
2607
+ const code = file.worktree ?? file.index;
2608
+ const mapped = code ? GIT_STATUS_TO_TREE[code] : void 0;
2609
+ if (mapped) overlay.set(qualifyStatusPath(root, file.path), mapped);
2610
+ }
2452
2611
  }
2453
2612
  statusRef.current = overlay;
2613
+ setGitSummary(summarizeStatuses(statuses));
2454
2614
  setTree((prev) => applyStatus(prev));
2455
2615
  } catch {
2616
+ } finally {
2617
+ if (identityGenerationRef.current === identityGeneration) setGitLoading(false);
2456
2618
  }
2457
- }, [client, workspaceId, sessionId, rootPath, applyStatus]);
2619
+ }, [client, workspaceId, sessionId, repoPaths, applyStatus]);
2458
2620
  const events = options.events;
2459
2621
  useEffect8(() => {
2460
2622
  if (!enabled || !events) return;
2623
+ if (!active) {
2624
+ for (const event of events) {
2625
+ if (event.sequence > lastSeqRef.current) lastSeqRef.current = event.sequence;
2626
+ }
2627
+ return;
2628
+ }
2461
2629
  let sawNew = false;
2630
+ let contentChanged = false;
2631
+ let sawCommandDelta = false;
2462
2632
  for (const event of events) {
2463
2633
  if (event.sequence <= lastSeqRef.current) continue;
2464
2634
  if (event.type === "fs.changed") {
@@ -2468,6 +2638,7 @@ function useSandboxFiles(sessionId, options = {}) {
2468
2638
  if (payload.source === "write") continue;
2469
2639
  if (typeof payload.revision === "number" && ownRevisionsRef.current.has(payload.revision))
2470
2640
  continue;
2641
+ contentChanged = true;
2471
2642
  for (const change of payload.changes ?? []) {
2472
2643
  pendingParentsRef.current.add(parentOf(change.path));
2473
2644
  if (change.oldPath) pendingParentsRef.current.add(parentOf(change.oldPath));
@@ -2478,30 +2649,58 @@ function useSandboxFiles(sessionId, options = {}) {
2478
2649
  if (payload && typeof payload === "object" && typeof payload.revision === "number" && ownRevisionsRef.current.has(payload.revision))
2479
2650
  continue;
2480
2651
  pendingGitRef.current = true;
2652
+ } else if (event.type === "agent.toolCall.output") {
2653
+ sawNew = true;
2654
+ contentChanged = true;
2655
+ pendingAgentToolRef.current = true;
2656
+ pendingGitRef.current = true;
2657
+ } else if (event.type === "sandbox.command.output.delta") {
2658
+ sawNew = true;
2659
+ contentChanged = true;
2660
+ sawCommandDelta = true;
2661
+ pendingAgentToolRef.current = true;
2662
+ pendingGitRef.current = true;
2481
2663
  }
2482
2664
  }
2483
2665
  for (const event of events)
2484
2666
  if (event.sequence > lastSeqRef.current) lastSeqRef.current = event.sequence;
2485
2667
  if (!sawNew) return;
2668
+ if (contentChanged && !sawCommandDelta) setContentRevision((revision) => revision + 1);
2486
2669
  if (debounceRef.current) clearTimeout(debounceRef.current);
2487
2670
  const identityGeneration = identityGenerationRef.current;
2671
+ const delay = sawCommandDelta ? Math.max(0, 1e3 - (Date.now() - lastCommandRefreshAtRef.current)) : 150;
2488
2672
  debounceRef.current = setTimeout(() => {
2489
2673
  debounceRef.current = null;
2490
2674
  if (identityGenerationRef.current !== identityGeneration) return;
2675
+ if (sawCommandDelta) {
2676
+ lastCommandRefreshAtRef.current = Date.now();
2677
+ setContentRevision((revision) => revision + 1);
2678
+ }
2491
2679
  const parents = pendingParentsRef.current;
2492
2680
  pendingParentsRef.current = /* @__PURE__ */ new Set();
2493
2681
  const wantGit = pendingGitRef.current;
2494
2682
  pendingGitRef.current = false;
2683
+ const wantAgentTool = pendingAgentToolRef.current;
2684
+ pendingAgentToolRef.current = false;
2495
2685
  void (async () => {
2496
- if (wantGit) await refreshGitOverlay();
2497
- if (identityGenerationRef.current !== identityGeneration) return;
2498
- for (const parent of parents) {
2499
- if (identityGenerationRef.current !== identityGeneration) return;
2500
- await reconcilePath(parent);
2501
- }
2686
+ const paths = wantAgentTool ? loadedDirectoryPaths(treeRef.current) : [...parents];
2687
+ await Promise.all([
2688
+ wantGit ? refreshGitOverlay() : Promise.resolve(),
2689
+ Promise.all(paths.map((path) => reconcilePath(path)))
2690
+ ]);
2502
2691
  })();
2503
- }, 150);
2504
- }, [enabled, events, reconcilePath, refreshGitOverlay]);
2692
+ }, delay);
2693
+ }, [enabled, active, events, reconcilePath, refreshGitOverlay]);
2694
+ useEffect8(() => {
2695
+ if (active) return;
2696
+ if (debounceRef.current) {
2697
+ clearTimeout(debounceRef.current);
2698
+ debounceRef.current = null;
2699
+ }
2700
+ pendingParentsRef.current = /* @__PURE__ */ new Set();
2701
+ pendingGitRef.current = false;
2702
+ pendingAgentToolRef.current = false;
2703
+ }, [active]);
2505
2704
  useEffect8(
2506
2705
  () => () => {
2507
2706
  refreshAbortRef.current?.abort();
@@ -2523,25 +2722,28 @@ function useSandboxFiles(sessionId, options = {}) {
2523
2722
  deleteEntry,
2524
2723
  moveEntry,
2525
2724
  refresh,
2725
+ contentRevision: identityMatches ? contentRevision : 0,
2526
2726
  source: identityMatches ? source : null,
2527
2727
  capturedAt: identityMatches && source === "capture" ? capture?.capturedAt ?? null : null,
2728
+ gitSummary: identityMatches ? gitSummary : null,
2729
+ gitLoading: identityMatches && gitLoading,
2528
2730
  loading: identityMatches && loading,
2529
2731
  error: identityMatches ? error : null
2530
2732
  };
2531
2733
  }
2532
2734
 
2533
2735
  // src/hooks/use-sandbox-git.ts
2534
- import { useCallback as useCallback13, useEffect as useEffect9, useMemo as useMemo4, useRef as useRef8, useState as useState7 } from "react";
2535
- function normalizeRoot(root) {
2736
+ import { useCallback as useCallback13, useEffect as useEffect9, useMemo as useMemo5, useRef as useRef8, useState as useState7 } from "react";
2737
+ function normalizeRoot2(root) {
2536
2738
  const normalized = root.replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
2537
2739
  return normalized === "." ? "" : normalized;
2538
2740
  }
2539
2741
  function qualifyPath(repoRoot, path) {
2540
- const root = normalizeRoot(repoRoot);
2742
+ const root = normalizeRoot2(repoRoot);
2541
2743
  return root ? `${root}/${path}` : path;
2542
2744
  }
2543
2745
  function qualifyDiff(repoRoot, file) {
2544
- const root = normalizeRoot(repoRoot);
2746
+ const root = normalizeRoot2(repoRoot);
2545
2747
  return {
2546
2748
  ...file,
2547
2749
  path: qualifyPath(root, file.path),
@@ -2550,11 +2752,11 @@ function qualifyDiff(repoRoot, file) {
2550
2752
  };
2551
2753
  }
2552
2754
  function uniqueRoots(roots) {
2553
- return [...new Set(roots.map(normalizeRoot))].sort((a, b) => a.localeCompare(b));
2755
+ return [...new Set(roots.map(normalizeRoot2))].sort((a, b) => a.localeCompare(b));
2554
2756
  }
2555
2757
  function repoForPath(manifest, repoPath) {
2556
- const want = normalizeRoot(repoPath);
2557
- const exact = manifest.repos.find((r) => normalizeRoot(r.root) === want);
2758
+ const want = normalizeRoot2(repoPath);
2759
+ const exact = manifest.repos.find((r) => normalizeRoot2(r.root) === want);
2558
2760
  if (exact) return exact;
2559
2761
  if (want === "" && manifest.repos.length === 1) return manifest.repos[0] ?? null;
2560
2762
  return null;
@@ -2590,16 +2792,18 @@ function mergeDiffs(current, next) {
2590
2792
  function useSandboxGit(sessionId, options = {}) {
2591
2793
  const { client, workspaceId } = useOpenGeni(options);
2592
2794
  const enabled = (options.enabled ?? true) && Boolean(sessionId);
2795
+ const active = options.active ?? true;
2593
2796
  const repoPath = options.repoPath ?? "";
2594
2797
  const workspaceWide = options.repoPaths !== void 0;
2595
2798
  const repoPathsKey = uniqueRoots(
2596
2799
  options.repoPaths && options.repoPaths.length > 0 ? options.repoPaths : [repoPath]
2597
2800
  ).join("\0");
2598
- const repoPaths = useMemo4(() => repoPathsKey.split("\0"), [repoPathsKey]);
2599
- const staged = options.staged ?? false;
2600
- const capture = options.capture ?? null;
2801
+ const repoPaths = useMemo5(() => repoPathsKey.split("\0"), [repoPathsKey]);
2802
+ const comparison = options.comparison ?? (options.staged ? "staged" : "working");
2803
+ const hasAdvertisedRepoPaths = (options.repoPaths?.length ?? 0) > 0;
2804
+ const capture = comparison === "staged" ? null : options.capture ?? null;
2601
2805
  const isLive2 = options.liveness === "warm" || options.liveness === "draining";
2602
- const identityKey = `${workspaceId}\0${sessionId ?? ""}\0${repoPathsKey}\0${workspaceWide}\0${staged}`;
2806
+ const identityKey = `${workspaceId}\0${sessionId ?? ""}\0${repoPathsKey}\0${workspaceWide}\0${comparison}`;
2603
2807
  const [diff, setDiff] = useState7([]);
2604
2808
  const [branch, setBranch] = useState7(null);
2605
2809
  const [isRepo, setIsRepo] = useState7(false);
@@ -2614,6 +2818,7 @@ function useSandboxGit(sessionId, options = {}) {
2614
2818
  const refreshGenerationRef = useRef8(0);
2615
2819
  const refreshAbortRef = useRef8(null);
2616
2820
  const lastChangeRef = useRef8(0);
2821
+ const commandRefreshTimerRef = useRef8(null);
2617
2822
  const refresh = useCallback13(async () => {
2618
2823
  if (!sessionId) return;
2619
2824
  refreshAbortRef.current?.abort();
@@ -2623,7 +2828,39 @@ function useSandboxGit(sessionId, options = {}) {
2623
2828
  setLoading(true);
2624
2829
  setError(null);
2625
2830
  try {
2626
- const statuses = await Promise.all(
2831
+ const diffRequest = (root) => client.gitDiff(
2832
+ workspaceId,
2833
+ sessionId,
2834
+ {
2835
+ path: root,
2836
+ ...comparison === "branch" ? { fromRef: "origin/HEAD", includeUntracked: true } : comparison === "working" ? { fromRef: "HEAD", includeUntracked: true } : { staged: true, includeUntracked: false }
2837
+ },
2838
+ { signal: refreshAbort.signal }
2839
+ );
2840
+ const prefetched = hasAdvertisedRepoPaths ? await client.gitReadBatch(
2841
+ workspaceId,
2842
+ sessionId,
2843
+ {
2844
+ requests: repoPaths.map((root) => ({
2845
+ status: { path: root },
2846
+ diff: {
2847
+ path: root,
2848
+ ...comparison === "branch" ? { fromRef: "origin/HEAD", includeUntracked: true } : comparison === "working" ? { fromRef: "HEAD", includeUntracked: true } : { staged: true, includeUntracked: false }
2849
+ }
2850
+ }))
2851
+ },
2852
+ { signal: refreshAbort.signal }
2853
+ ).then(
2854
+ (batch) => batch.results.map((item, index) => ({
2855
+ root: repoPaths[index] ?? "",
2856
+ status: item.status,
2857
+ result: item.diff ?? {
2858
+ files: [],
2859
+ revision: item.status.revision
2860
+ }
2861
+ }))
2862
+ ) : null;
2863
+ const statuses = prefetched ?? await Promise.all(
2627
2864
  repoPaths.map(async (root) => ({
2628
2865
  root,
2629
2866
  status: await client.gitStatus(
@@ -2648,19 +2885,11 @@ function useSandboxGit(sessionId, options = {}) {
2648
2885
  setSource("live");
2649
2886
  return;
2650
2887
  }
2888
+ const prefetchedByRoot = new Map(prefetched?.map(({ root, result }) => [root, result]));
2651
2889
  const results = await Promise.all(
2652
2890
  repositories.map(async ({ root }) => ({
2653
2891
  root,
2654
- result: await client.gitDiff(
2655
- workspaceId,
2656
- sessionId,
2657
- {
2658
- path: root,
2659
- staged,
2660
- includeUntracked: !staged
2661
- },
2662
- { signal: refreshAbort.signal }
2663
- )
2892
+ result: prefetchedByRoot.get(root) ?? await diffRequest(root)
2664
2893
  }))
2665
2894
  );
2666
2895
  if (refreshGenerationRef.current !== generation) return;
@@ -2681,10 +2910,21 @@ function useSandboxGit(sessionId, options = {}) {
2681
2910
  if (refreshGenerationRef.current === generation) setLoading(false);
2682
2911
  if (refreshAbortRef.current === refreshAbort) refreshAbortRef.current = null;
2683
2912
  }
2684
- }, [client, workspaceId, sessionId, repoPaths, staged, workspaceWide]);
2913
+ }, [
2914
+ client,
2915
+ workspaceId,
2916
+ sessionId,
2917
+ repoPaths,
2918
+ hasAdvertisedRepoPaths,
2919
+ workspaceWide,
2920
+ comparison
2921
+ ]);
2685
2922
  const seedFromCapture = useCallback13(
2686
2923
  (manifest) => {
2687
2924
  const repositories = workspaceWide ? manifest.repos : [repoForPath(manifest, repoPath)].filter(Boolean);
2925
+ if (comparison === "branch" && repositories.some((repo) => repo.branchDiff === void 0)) {
2926
+ return;
2927
+ }
2688
2928
  if (repositories.length === 0) {
2689
2929
  setIsRepo(false);
2690
2930
  setBranch(null);
@@ -2700,20 +2940,21 @@ function useSandboxGit(sessionId, options = {}) {
2700
2940
  setBranch(repositories.length === 1 ? repositories[0]?.head ?? null : null);
2701
2941
  setAhead(repositories.reduce((sum, repo) => sum + repo.ahead, 0));
2702
2942
  setBehind(repositories.reduce((sum, repo) => sum + repo.behind, 0));
2703
- setRepoRoots(repositories.map((repo) => normalizeRoot(repo.root)));
2943
+ setRepoRoots(repositories.map((repo) => normalizeRoot2(repo.root)));
2704
2944
  setDiff(
2705
2945
  (prev) => mergeDiffs(
2706
2946
  prev,
2707
- repositories.flatMap(
2708
- (repo) => workspaceWide ? repo.diff.map((file) => qualifyDiff(repo.root, file)) : repo.diff
2709
- )
2947
+ repositories.flatMap((repo) => {
2948
+ const capturedDiff = comparison === "branch" ? repo.branchDiff ?? [] : repo.diff;
2949
+ return workspaceWide ? capturedDiff.map((file) => qualifyDiff(repo.root, file)) : capturedDiff;
2950
+ })
2710
2951
  )
2711
2952
  );
2712
2953
  sourceRef.current = "capture";
2713
2954
  setSource("capture");
2714
2955
  setError(null);
2715
2956
  },
2716
- [repoPath, workspaceWide]
2957
+ [comparison, repoPath, workspaceWide]
2717
2958
  );
2718
2959
  const captureRef = useRef8(capture);
2719
2960
  captureRef.current = capture;
@@ -2746,6 +2987,10 @@ function useSandboxGit(sessionId, options = {}) {
2746
2987
  if (currentCapture && (identityChanged || !isLive2 || sourceRef.current !== "live")) {
2747
2988
  seedFromCapture(currentCapture);
2748
2989
  }
2990
+ if (!active) {
2991
+ if (!active) setLoading(false);
2992
+ return;
2993
+ }
2749
2994
  if (isLive2 || !currentCapture) {
2750
2995
  void refresh();
2751
2996
  }
@@ -2754,21 +2999,49 @@ function useSandboxGit(sessionId, options = {}) {
2754
2999
  refreshAbortRef.current = null;
2755
3000
  refreshGenerationRef.current += 1;
2756
3001
  };
2757
- }, [enabled, isLive2, captureRevision, identityKey, refresh, seedFromCapture]);
3002
+ }, [enabled, active, isLive2, captureRevision, identityKey, refresh, seedFromCapture]);
2758
3003
  const events = options.events;
2759
3004
  useEffect9(() => {
2760
- if (!enabled || !events) return;
3005
+ if (!enabled || !active || !events) {
3006
+ if (commandRefreshTimerRef.current) {
3007
+ clearTimeout(commandRefreshTimerRef.current);
3008
+ commandRefreshTimerRef.current = null;
3009
+ }
3010
+ return;
3011
+ }
2761
3012
  let latest = lastChangeRef.current;
3013
+ let immediate = false;
3014
+ let commandDelta = false;
2762
3015
  for (const event of events) {
2763
- if (event.type === "git.changed" && event.sequence > latest) {
3016
+ if (event.sequence <= latest) continue;
3017
+ if (event.type === "git.changed" || event.type === "agent.toolCall.output") {
3018
+ latest = event.sequence;
3019
+ immediate = true;
3020
+ } else if (event.type === "sandbox.command.output.delta") {
2764
3021
  latest = event.sequence;
3022
+ commandDelta = true;
2765
3023
  }
2766
3024
  }
2767
3025
  if (latest > lastChangeRef.current) {
2768
3026
  lastChangeRef.current = latest;
2769
- void refresh();
3027
+ if (immediate) {
3028
+ if (commandRefreshTimerRef.current) clearTimeout(commandRefreshTimerRef.current);
3029
+ commandRefreshTimerRef.current = null;
3030
+ void refresh();
3031
+ } else if (commandDelta && !commandRefreshTimerRef.current) {
3032
+ commandRefreshTimerRef.current = setTimeout(() => {
3033
+ commandRefreshTimerRef.current = null;
3034
+ void refresh();
3035
+ }, 1e3);
3036
+ }
2770
3037
  }
2771
- }, [enabled, events, refresh]);
3038
+ }, [enabled, active, events, refresh]);
3039
+ useEffect9(
3040
+ () => () => {
3041
+ if (commandRefreshTimerRef.current) clearTimeout(commandRefreshTimerRef.current);
3042
+ },
3043
+ []
3044
+ );
2772
3045
  const identityMatches = enabled && stateIdentity === identityKey;
2773
3046
  const visibleRepoRoots = identityMatches ? repoRoots : [];
2774
3047
  return {
@@ -3142,7 +3415,7 @@ function useWorkspaceEdit(sessionId, options = {}) {
3142
3415
  }
3143
3416
 
3144
3417
  // src/hooks/use-machine-chip.ts
3145
- import { useMemo as useMemo5 } from "react";
3418
+ import { useMemo as useMemo6 } from "react";
3146
3419
  function formatAsOf(capturedAt, now) {
3147
3420
  const then = Date.parse(capturedAt);
3148
3421
  if (Number.isNaN(then)) return "recently";
@@ -3179,7 +3452,7 @@ function useMachineChip(input) {
3179
3452
  capturedAt,
3180
3453
  now
3181
3454
  } = input;
3182
- return useMemo5(
3455
+ return useMemo6(
3183
3456
  () => deriveMachineChip({
3184
3457
  liveness,
3185
3458
  capabilitiesState,
@@ -3350,6 +3623,7 @@ import { LayoutGroup, motion } from "motion/react";
3350
3623
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
3351
3624
  function ChatComposer({
3352
3625
  composer,
3626
+ responsiveBasis,
3353
3627
  effectiveControl,
3354
3628
  queuedAheadCount,
3355
3629
  canControlWorkspace,
@@ -3397,7 +3671,7 @@ function ChatComposer({
3397
3671
  attachments || models || controlsLeading || controlsStart || transcription
3398
3672
  );
3399
3673
  const stackActions = hasControls && Boolean(actionsStart);
3400
- return /* @__PURE__ */ jsxs2(Root, { controller, className, children: [
3674
+ return /* @__PURE__ */ jsxs2(Root, { controller, responsiveBasis, className, children: [
3401
3675
  /* @__PURE__ */ jsxs2(Frame, { children: [
3402
3676
  /* @__PURE__ */ jsx2(CommandPalette2, {}),
3403
3677
  /* @__PURE__ */ jsxs2(Surface, { children: [
@@ -3406,62 +3680,69 @@ function ChatComposer({
3406
3680
  /* @__PURE__ */ jsx2(Attachments, {}),
3407
3681
  header,
3408
3682
  /* @__PURE__ */ jsx2(Input, { placeholder, autoFocus }),
3409
- controller.confirmState ? /* @__PURE__ */ jsx2(Confirmation, {}) : /* @__PURE__ */ jsx2(Footer, { className: stackActions ? "max-sm:flex-nowrap sm:flex-wrap" : void 0, children: /* @__PURE__ */ jsxs2(LayoutGroup, { id: "og-composer-footer", children: [
3410
- hasControls ? /* @__PURE__ */ jsxs2(
3411
- Controls,
3412
- {
3413
- className: stackActions ? "min-w-0 max-sm:flex-1 sm:w-auto" : void 0,
3414
- children: [
3415
- controlsLeading,
3416
- /* @__PURE__ */ jsx2(AttachButton, { className: attachButtonClassName }),
3417
- transcription ? /* @__PURE__ */ jsx2(
3418
- ComposerTranscriptionControl,
3419
- {
3420
- ...transcription,
3421
- suppressed: transcriptionSuppressed,
3422
- className: [transcription.className, transcriptionClassName].filter(Boolean).join(" ")
3423
- }
3424
- ) : null,
3425
- models ? /* @__PURE__ */ jsx2(
3426
- motion.span,
3427
- {
3428
- layout: true,
3429
- transition: { duration: 0.36, ease: [0.22, 1, 0.36, 1] },
3430
- className: "inline-flex min-w-0",
3431
- children: /* @__PURE__ */ jsx2(
3432
- ModelPicker2,
3683
+ controller.confirmState ? /* @__PURE__ */ jsx2(Confirmation, {}) : /* @__PURE__ */ jsx2(
3684
+ Footer,
3685
+ {
3686
+ "data-og-stack-actions": stackActions ? "" : void 0,
3687
+ className: stackActions ? "max-sm:flex-nowrap sm:flex-wrap" : void 0,
3688
+ children: /* @__PURE__ */ jsxs2(LayoutGroup, { id: "og-composer-footer", children: [
3689
+ hasControls ? /* @__PURE__ */ jsxs2(
3690
+ Controls,
3691
+ {
3692
+ className: stackActions ? "min-w-0 max-sm:flex-1 sm:w-auto" : void 0,
3693
+ children: [
3694
+ controlsLeading,
3695
+ /* @__PURE__ */ jsx2(AttachButton, { className: attachButtonClassName }),
3696
+ transcription ? /* @__PURE__ */ jsx2(
3697
+ ComposerTranscriptionControl,
3433
3698
  {
3434
- models,
3435
- value: selectedModel,
3436
- onChange: onSelectModel
3699
+ ...transcription,
3700
+ suppressed: transcriptionSuppressed,
3701
+ className: [transcription.className, transcriptionClassName].filter(Boolean).join(" ")
3437
3702
  }
3438
- )
3439
- }
3440
- ) : null,
3441
- controlsStart ? /* @__PURE__ */ jsx2(
3442
- motion.span,
3443
- {
3444
- layout: true,
3445
- transition: { duration: 0.36, ease: [0.22, 1, 0.36, 1] },
3446
- className: "inline-flex min-w-0 items-center gap-1.5",
3447
- children: controlsStart
3448
- }
3449
- ) : null
3450
- ]
3451
- }
3452
- ) : /* @__PURE__ */ jsx2(Hint, { children: hint }),
3453
- /* @__PURE__ */ jsxs2(
3454
- Actions,
3455
- {
3456
- className: stackActions ? "max-sm:shrink-0 sm:w-auto sm:justify-end" : void 0,
3457
- children: [
3458
- actionsStart,
3459
- /* @__PURE__ */ jsx2(PauseButton, {}),
3460
- /* @__PURE__ */ jsx2(SendButton, {})
3461
- ]
3462
- }
3463
- )
3464
- ] }) })
3703
+ ) : null,
3704
+ models ? /* @__PURE__ */ jsx2(
3705
+ motion.span,
3706
+ {
3707
+ layout: true,
3708
+ transition: { duration: 0.36, ease: [0.22, 1, 0.36, 1] },
3709
+ className: "inline-flex min-w-0",
3710
+ children: /* @__PURE__ */ jsx2(
3711
+ ModelPicker2,
3712
+ {
3713
+ models,
3714
+ value: selectedModel,
3715
+ onChange: onSelectModel
3716
+ }
3717
+ )
3718
+ }
3719
+ ) : null,
3720
+ controlsStart ? /* @__PURE__ */ jsx2(
3721
+ motion.span,
3722
+ {
3723
+ layout: true,
3724
+ transition: { duration: 0.36, ease: [0.22, 1, 0.36, 1] },
3725
+ className: "inline-flex min-w-0 items-center gap-1.5",
3726
+ children: controlsStart
3727
+ }
3728
+ ) : null
3729
+ ]
3730
+ }
3731
+ ) : /* @__PURE__ */ jsx2(Hint, { children: hint }),
3732
+ /* @__PURE__ */ jsxs2(
3733
+ Actions,
3734
+ {
3735
+ className: stackActions ? "max-sm:shrink-0 sm:w-auto sm:justify-end" : void 0,
3736
+ children: [
3737
+ actionsStart,
3738
+ /* @__PURE__ */ jsx2(PauseButton, {}),
3739
+ /* @__PURE__ */ jsx2(SendButton, {})
3740
+ ]
3741
+ }
3742
+ )
3743
+ ] })
3744
+ }
3745
+ )
3465
3746
  ] })
3466
3747
  ] }),
3467
3748
  /* @__PURE__ */ jsx2(Help, {}),
@@ -3683,6 +3964,9 @@ async function attachRenderer(prefer, loaders, onTier) {
3683
3964
 
3684
3965
  // src/components/sandbox-terminal.tsx
3685
3966
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
3967
+ function clearTerminalBootStatus(term) {
3968
+ term.write("\r\x1B[2K\x1B[2J\x1B[H");
3969
+ }
3686
3970
  function forcedRendererFailures() {
3687
3971
  const raw = typeof globalThis !== "undefined" ? globalThis.__OG_FORCE_RENDERER_FAIL__ : void 0;
3688
3972
  const set = /* @__PURE__ */ new Set();
@@ -3736,7 +4020,8 @@ function SandboxTerminal({
3736
4020
  }
3737
4021
  });
3738
4022
  const ptyMode = ptyWs && ptyStatus !== "closed";
3739
- const interactive = !readOnly && (ptyMode || result.write !== null);
4023
+ const interactive = !readOnly && (ptyMode ? ptyConnected : result.write !== null);
4024
+ const terminalModeLabel = interactive ? null : readOnly ? "read-only" : ptyMode && ptyStatus === "connecting" ? "connecting" : ptyMode && ptyStatus === "error" ? "connection error" : "output only";
3740
4025
  const warm = liveness === "warm" || liveness === "draining";
3741
4026
  const booting = activated && !ptyMode && !warm && liveness !== null && liveness !== void 0 && result.chunks.length === 0;
3742
4027
  function handleActivate() {
@@ -3873,7 +4158,7 @@ function SandboxTerminal({
3873
4158
  if (!booting) {
3874
4159
  if (bootActiveRef.current) {
3875
4160
  bootActiveRef.current = false;
3876
- if (!wroteFirehoseRef.current) term.clear();
4161
+ if (!wroteFirehoseRef.current) clearTerminalBootStatus(term);
3877
4162
  }
3878
4163
  return;
3879
4164
  }
@@ -3951,7 +4236,14 @@ function SandboxTerminal({
3951
4236
  ] })
3952
4237
  ] }),
3953
4238
  /* @__PURE__ */ jsxs4("span", { className: "flex shrink-0 items-center gap-2", children: [
3954
- !interactive && /* @__PURE__ */ jsx4("span", { className: "rounded-og-sm bg-og-surface-2 px-1.5 py-0.5 text-og-xs uppercase tracking-wide", children: "read-only" }),
4239
+ terminalModeLabel && /* @__PURE__ */ jsx4(
4240
+ "span",
4241
+ {
4242
+ className: "rounded-og-sm bg-og-surface-2 px-1.5 py-0.5 text-og-xs uppercase tracking-wide",
4243
+ role: "status",
4244
+ children: terminalModeLabel
4245
+ }
4246
+ ),
3955
4247
  /* @__PURE__ */ jsx4(
3956
4248
  "button",
3957
4249
  {
@@ -4051,7 +4343,7 @@ import {
4051
4343
  useEffect as useEffect15,
4052
4344
  useId as useId2,
4053
4345
  useLayoutEffect,
4054
- useMemo as useMemo7,
4346
+ useMemo as useMemo8,
4055
4347
  useRef as useRef13,
4056
4348
  useState as useState12
4057
4349
  } from "react";
@@ -4059,11 +4351,11 @@ import { AlertDialog } from "radix-ui";
4059
4351
  import { VList } from "virtua";
4060
4352
 
4061
4353
  // src/lib/use-unicode-fonts.ts
4062
- import { useEffect as useEffect14, useMemo as useMemo6 } from "react";
4354
+ import { useEffect as useEffect14, useMemo as useMemo7 } from "react";
4063
4355
  var japaneseScript = /[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u;
4064
4356
  var arabicScript = /[\u0600-\u06ff\u0750-\u077f\u0870-\u08ff\ufb50-\ufdff\ufe70-\ufefc]/u;
4065
4357
  function useUnicodeFallbackFonts(values) {
4066
- const content = useMemo6(() => collectUnicodeContent(values), [values]);
4358
+ const content = useMemo7(() => collectUnicodeContent(values), [values]);
4067
4359
  useEffect14(() => {
4068
4360
  if (content.japanese) {
4069
4361
  void import("./noto-sans-jp-loader.generated-HFGLYBR3.js").then(({ loadNotoSansJp }) => loadNotoSansJp(content.japanese)).catch(() => void 0);
@@ -4146,10 +4438,11 @@ function FileBrowser({
4146
4438
  const [busy, setBusy] = useState12(false);
4147
4439
  const [coarsePointer, setCoarsePointer] = useState12(false);
4148
4440
  const treeId = useId2();
4149
- const containerRef = useRef13(null);
4441
+ const container = usePortalTokenSource();
4442
+ const containerRef = container.currentRef;
4150
4443
  const deleteReturnFocusRef = useRef13(null);
4151
4444
  const vlistRef = useRef13(null);
4152
- const portalTokenStyle = usePortalTokenStyle(containerRef);
4445
+ const portalTokenStyle = usePortalTokenStyle(container.source);
4153
4446
  const getTreeElement = useCallback16(
4154
4447
  () => typeof document === "undefined" ? null : document.getElementById(treeId),
4155
4448
  [treeId]
@@ -4209,7 +4502,7 @@ function FileBrowser({
4209
4502
  [expanded, expand]
4210
4503
  );
4211
4504
  const cold = result.source !== "live";
4212
- const renderItems = useMemo7(() => {
4505
+ const renderItems = useMemo8(() => {
4213
4506
  const items = [];
4214
4507
  if (draftCreate && draftCreate.parent === "") {
4215
4508
  items.push({ type: "create", parent: "", kind: draftCreate.kind, depth: 0 });
@@ -4240,22 +4533,22 @@ function FileBrowser({
4240
4533
  walk(result.tree, 0);
4241
4534
  return items;
4242
4535
  }, [result.tree, expanded, draftCreate, loadingPaths, result.expandingPaths, cold]);
4243
- const unicodeFontPaths = useMemo7(
4536
+ const unicodeFontPaths = useMemo8(
4244
4537
  () => renderItems.flatMap((item) => item.type === "node" ? [item.node.path] : []),
4245
4538
  [renderItems]
4246
4539
  );
4247
4540
  useUnicodeFallbackFonts(unicodeFontPaths);
4248
- const flatRows = useMemo7(
4541
+ const flatRows = useMemo8(
4249
4542
  () => renderItems.flatMap(
4250
4543
  (item) => item.type === "node" ? [{ node: item.node, depth: item.depth }] : []
4251
4544
  ),
4252
4545
  [renderItems]
4253
4546
  );
4254
- const rowIndexByPath = useMemo7(
4547
+ const rowIndexByPath = useMemo8(
4255
4548
  () => new Map(flatRows.map((row, index) => [row.node.path, index])),
4256
4549
  [flatRows]
4257
4550
  );
4258
- const treePositionByPath = useMemo7(() => {
4551
+ const treePositionByPath = useMemo8(() => {
4259
4552
  const positions = /* @__PURE__ */ new Map();
4260
4553
  const visit = (nodes) => {
4261
4554
  nodes.forEach((node, index) => {
@@ -4517,6 +4810,7 @@ function FileBrowser({
4517
4810
  runDelete,
4518
4811
  supportsMutation,
4519
4812
  coarsePointer,
4813
+ containerRef,
4520
4814
  rowIndexByPath,
4521
4815
  treeId
4522
4816
  ]
@@ -4537,7 +4831,7 @@ function FileBrowser({
4537
4831
  const showDegradedTree = result.error && result.tree.length > 0;
4538
4832
  const showEmpty = !result.loading && result.tree.length === 0 && !draftCreate;
4539
4833
  const showToolbar = supportsMutation || result.loading;
4540
- const usesVirtualTree = !showErrorEmpty && !showEmpty;
4834
+ const usesVirtualTree = !showErrorEmpty && !showEmpty && renderItems.length > 500;
4541
4835
  const renderNodeRow = (node, depth) => {
4542
4836
  const isOpen = expanded.has(node.path);
4543
4837
  if (renderNode) {
@@ -4772,7 +5066,7 @@ function FileBrowser({
4772
5066
  type: "button",
4773
5067
  onClick: () => void result.refresh(),
4774
5068
  disabled: result.loading,
4775
- className: "inline-flex min-h-7 shrink-0 items-center gap-1 rounded-og-sm border border-og-border bg-og-surface-1 px-2 font-medium text-og-fg transition-colors hover:border-og-border-strong focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:min-h-11",
5069
+ className: "inline-flex min-h-7 shrink-0 items-center gap-1 rounded-og-sm border border-og-border bg-og-surface-1 px-2 font-medium text-og-fg transition-colors hover:border-og-border-strong focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:min-h-11",
4776
5070
  children: [
4777
5071
  /* @__PURE__ */ jsx5(
4778
5072
  RefreshCwIcon,
@@ -4791,13 +5085,7 @@ function FileBrowser({
4791
5085
  /* @__PURE__ */ jsx5(
4792
5086
  "div",
4793
5087
  {
4794
- ref: containerRef,
4795
- id: usesVirtualTree ? void 0 : treeId,
4796
- role: usesVirtualTree ? void 0 : "tree",
4797
- "aria-label": usesVirtualTree ? void 0 : "Workspace files",
4798
- "aria-activedescendant": usesVirtualTree ? void 0 : activeDescendantId,
4799
- tabIndex: usesVirtualTree ? void 0 : 0,
4800
- "aria-multiselectable": usesVirtualTree ? void 0 : false,
5088
+ ref: container.ref,
4801
5089
  onFocus: (event) => {
4802
5090
  if (event.target.id === treeId && cursorIndex < 0) {
4803
5091
  const first = flatRows[0];
@@ -4816,7 +5104,7 @@ function FileBrowser({
4816
5104
  void onDropOnto("", src);
4817
5105
  } : void 0,
4818
5106
  className: cn(
4819
- "flex min-h-0 min-w-0 flex-1 flex-col p-1 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent",
5107
+ "flex min-h-0 min-w-0 flex-1 flex-col p-1",
4820
5108
  dragOver === "" && "ring-1 ring-inset ring-og-accent"
4821
5109
  ),
4822
5110
  "data-opengeni-file-tree": true,
@@ -4841,7 +5129,19 @@ function FileBrowser({
4841
5129
  ]
4842
5130
  }
4843
5131
  )
4844
- ] }) : showEmpty ? /* @__PURE__ */ jsx5("div", { className: "p-2 text-og-sm text-og-fg-subtle", children: emptyState ?? "This directory is empty" }) : (
5132
+ ] }) : showEmpty ? /* @__PURE__ */ jsx5("div", { className: "p-2 text-og-sm text-og-fg-subtle", children: emptyState ?? "This directory is empty" }) : !usesVirtualTree ? /* @__PURE__ */ jsx5(
5133
+ "div",
5134
+ {
5135
+ id: treeId,
5136
+ role: "tree",
5137
+ "aria-label": "Workspace files",
5138
+ "aria-activedescendant": activeDescendantId,
5139
+ "aria-multiselectable": false,
5140
+ tabIndex: 0,
5141
+ className: "min-h-0 min-w-0 flex-1 overflow-auto outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent",
5142
+ children: renderItems.map((item) => /* @__PURE__ */ jsx5("div", { className: "min-w-0", children: renderItem(item) }, itemKey(item)))
5143
+ }
5144
+ ) : (
4845
5145
  // Virtualized: only the rows in/near the viewport mount, so a 3k-node
4846
5146
  // tree stays responsive. The flat item list already carries the
4847
5147
  // expanded subtree + synthetic (create/skeleton/residue) rows.
@@ -4855,7 +5155,7 @@ function FileBrowser({
4855
5155
  "aria-activedescendant": activeDescendantId,
4856
5156
  "aria-multiselectable": false,
4857
5157
  tabIndex: 0,
4858
- className: "min-h-0 flex-1 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent",
5158
+ className: "min-h-0 flex-1 outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent",
4859
5159
  itemSize: coarsePointer ? 44 : 28,
4860
5160
  ssrCount: Math.min(32, renderItems.length),
4861
5161
  children: renderItems.map((item) => /* @__PURE__ */ jsx5("div", { className: "min-w-0", children: renderItem(item) }, itemKey(item)))
@@ -4941,11 +5241,11 @@ function DeleteDialog({
4941
5241
  }
4942
5242
  },
4943
5243
  children: /* @__PURE__ */ jsxs5(AlertDialog.Portal, { container: typeof document === "undefined" ? null : document.body, children: [
4944
- /* @__PURE__ */ jsx5(AlertDialog.Overlay, { className: "fixed inset-0 z-[100] bg-black/45 backdrop-blur-[2px]" }),
5244
+ /* @__PURE__ */ jsx5(AlertDialog.Overlay, { className: "og-root fixed inset-0 z-[100] bg-black/45 backdrop-blur-[2px]" }),
4945
5245
  /* @__PURE__ */ jsxs5(
4946
5246
  AlertDialog.Content,
4947
5247
  {
4948
- className: "og-root fixed left-1/2 top-1/2 z-[101] max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-sm -translate-x-1/2 -translate-y-1/2 overflow-y-auto rounded-og-lg border border-og-border-strong bg-og-surface-1 p-4 text-og-fg shadow-2xl outline-none",
5248
+ className: "og-root fixed left-1/2 top-1/2 z-[101] max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-sm -translate-x-1/2 -translate-y-1/2 overflow-y-auto rounded-og-lg border border-og-border-strong bg-og-surface-1 p-4 text-og-fg shadow-2xl outline-hidden",
4949
5249
  style: portalTokenStyle,
4950
5250
  onOpenAutoFocus: (event) => {
4951
5251
  event.preventDefault();
@@ -5090,7 +5390,7 @@ function InlineInput({
5090
5390
  className: cn(
5091
5391
  "min-w-0 flex-1 rounded-og-sm border bg-og-bg px-1 py-0 text-og-sm",
5092
5392
  "border-og-accent text-og-fg",
5093
- "outline-none"
5393
+ "outline-hidden"
5094
5394
  )
5095
5395
  }
5096
5396
  )
@@ -5193,14 +5493,14 @@ import {
5193
5493
  useEffect as useEffect17,
5194
5494
  useId as useId3,
5195
5495
  useLayoutEffect as useLayoutEffect2,
5196
- useMemo as useMemo9,
5496
+ useMemo as useMemo10,
5197
5497
  useRef as useRef15,
5198
5498
  useState as useState14
5199
5499
  } from "react";
5200
5500
  import { VList as VList2 } from "virtua";
5201
5501
 
5202
5502
  // src/hooks/use-windowed-sections.ts
5203
- import { useCallback as useCallback17, useEffect as useEffect16, useMemo as useMemo8, useRef as useRef14, useState as useState13 } from "react";
5503
+ import { useCallback as useCallback17, useEffect as useEffect16, useMemo as useMemo9, useRef as useRef14, useState as useState13 } from "react";
5204
5504
  function computeWindowRange(heights, scrollTop, viewport, overscan) {
5205
5505
  const n = heights.length;
5206
5506
  if (n === 0) return { start: 0, end: 0 };
@@ -5237,10 +5537,10 @@ function useWindowedSections(opts) {
5237
5537
  );
5238
5538
  useEffect16(() => {
5239
5539
  setHeights((prev) => {
5240
- if (prev.length === count) return prev;
5241
- return Array.from({ length: count }, (_, i) => prev[i] ?? estimateHeight(i));
5540
+ const next = Array.from({ length: count }, (_, i) => estimateHeight(i));
5541
+ return prev.length === next.length && prev.every((height, index) => height === next[index]) ? prev : next;
5242
5542
  });
5243
- }, [count]);
5543
+ }, [count, estimateHeight]);
5244
5544
  const heightsRef = useRef14(heights);
5245
5545
  heightsRef.current = heights;
5246
5546
  const [range, setRange] = useState13(() => ({
@@ -5283,7 +5583,7 @@ function useWindowedSections(opts) {
5283
5583
  return next;
5284
5584
  });
5285
5585
  }, []);
5286
- const offsets = useMemo8(() => {
5586
+ const offsets = useMemo9(() => {
5287
5587
  const out = new Array(count + 1);
5288
5588
  out[0] = 0;
5289
5589
  for (let i = 0; i < count; i++) out[i + 1] = (out[i] ?? 0) + Math.max(heights[i] ?? 0, 0);
@@ -5406,12 +5706,12 @@ function WorkbenchChanges({
5406
5706
  const [layout, setLayout] = useState14("unified");
5407
5707
  const [activePath, setActivePath] = useState14(null);
5408
5708
  const pickerId = useId3();
5409
- const unicodeFontPaths = useMemo9(
5709
+ const unicodeFontPaths = useMemo10(
5410
5710
  () => diff.flatMap((file) => file.oldPath ? [file.path, file.oldPath] : [file.path]),
5411
5711
  [diff]
5412
5712
  );
5413
5713
  useUnicodeFallbackFonts(unicodeFontPaths);
5414
- const { orderedFiles, rows, grouped } = useMemo9(() => buildRail(diff), [diff]);
5714
+ const { orderedFiles, rows, grouped } = useMemo10(() => buildRail(diff), [diff]);
5415
5715
  const selectedIndex = orderedFiles.findIndex((file) => file.path === activePath);
5416
5716
  const activeIndex = selectedIndex >= 0 ? selectedIndex : 0;
5417
5717
  useChangesLayoutEffect(() => {
@@ -5441,8 +5741,8 @@ function WorkbenchChanges({
5441
5741
  return () => query.removeListener(update);
5442
5742
  }
5443
5743
  }, []);
5444
- const additions = useMemo9(() => diff.reduce((sum, f) => sum + f.additions, 0), [diff]);
5445
- const deletions = useMemo9(() => diff.reduce((sum, f) => sum + f.deletions, 0), [diff]);
5744
+ const additions = useMemo10(() => diff.reduce((sum, f) => sum + f.additions, 0), [diff]);
5745
+ const deletions = useMemo10(() => diff.reduce((sum, f) => sum + f.deletions, 0), [diff]);
5446
5746
  const estimateHeight = useCallback18(
5447
5747
  (index) => {
5448
5748
  const file = orderedFiles[index];
@@ -5555,7 +5855,7 @@ function WorkbenchChanges({
5555
5855
  },
5556
5856
  title: activeFile?.path,
5557
5857
  "data-compact-file-picker": true,
5558
- className: "h-11 w-full rounded-og-md border border-og-border bg-og-bg px-3 font-og-mono text-og-sm text-og-fg outline-none transition-colors focus:border-og-accent focus:ring-2 focus:ring-og-accent-soft",
5858
+ className: "h-11 w-full rounded-og-md border border-og-border bg-og-bg px-3 font-og-mono text-og-sm text-og-fg outline-hidden transition-colors focus:border-og-accent focus:ring-2 focus:ring-og-accent-soft",
5559
5859
  children: orderedFiles.map((file, index) => /* @__PURE__ */ jsx6("option", { value: index, children: compactFileLabel(file) }, file.path))
5560
5860
  }
5561
5861
  )
@@ -5676,7 +5976,7 @@ function RailFileRow({
5676
5976
  title: file.path,
5677
5977
  "data-rail-file": true,
5678
5978
  className: cn(
5679
- "relative flex min-h-7 w-full items-center gap-1.5 truncate px-2 py-0.5 text-left text-og-sm transition-colors hover:bg-og-surface-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent pointer-coarse:min-h-11",
5979
+ "relative flex min-h-7 w-full items-center gap-1.5 truncate px-2 py-0.5 text-left text-og-sm transition-colors hover:bg-og-surface-2 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent pointer-coarse:min-h-11",
5680
5980
  grouped && "pl-3",
5681
5981
  active && "bg-og-accent-soft text-og-fg before:absolute before:inset-y-1 before:left-0 before:w-0.5 before:rounded-r-full before:bg-og-accent"
5682
5982
  ),
@@ -5774,7 +6074,7 @@ function GuardBody({
5774
6074
  {
5775
6075
  type: "button",
5776
6076
  onClick: () => onOpenFile(file.path),
5777
- className: "inline-flex min-h-8 shrink-0 items-center gap-1.5 rounded-og-sm border border-og-border px-2 py-1 text-og-xs font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:bg-og-surface-2 hover:text-og-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:min-h-11",
6077
+ className: "inline-flex min-h-8 shrink-0 items-center gap-1.5 rounded-og-sm border border-og-border px-2 py-1 text-og-xs font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:bg-og-surface-2 hover:text-og-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent pointer-coarse:min-h-11",
5778
6078
  children: [
5779
6079
  "Open in Files",
5780
6080
  /* @__PURE__ */ jsx6(ArrowUpRightIcon, { className: "size-3.5", "aria-hidden": true })
@@ -5793,7 +6093,7 @@ function LayoutToggle({
5793
6093
  type: "button",
5794
6094
  onClick: () => onChange(value),
5795
6095
  className: cn(
5796
- "min-h-7 rounded-og-xs px-1.5 py-0.5 text-og-xs capitalize focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-w-11 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
6096
+ "min-h-7 rounded-og-xs px-1.5 py-0.5 text-og-xs capitalize focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-w-11 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
5797
6097
  layout === value ? "bg-og-accent-soft text-og-fg" : "text-og-fg-subtle hover:text-og-fg"
5798
6098
  ),
5799
6099
  children: value
@@ -5912,7 +6212,7 @@ import { Loader2Icon, SaveIcon } from "lucide-react";
5912
6212
  import {
5913
6213
  useCallback as useCallback19,
5914
6214
  useEffect as useEffect19,
5915
- useMemo as useMemo10,
6215
+ useMemo as useMemo11,
5916
6216
  useRef as useRef16,
5917
6217
  useState as useState16
5918
6218
  } from "react";
@@ -6005,7 +6305,7 @@ function CodeEditor({
6005
6305
  },
6006
6306
  [baseline, onEditIntent]
6007
6307
  );
6008
- const langKey = useMemo10(() => languageForPath(path), [path]);
6308
+ const langKey = useMemo11(() => languageForPath(path), [path]);
6009
6309
  useEffect19(() => {
6010
6310
  let cancelled = false;
6011
6311
  void (async () => {
@@ -6093,7 +6393,7 @@ function CodeEditor({
6093
6393
  return () => clearTimeout(t);
6094
6394
  }, [savedTick]);
6095
6395
  const fileName = path.split("/").filter(Boolean).pop() ?? path;
6096
- const cmExtensions = useMemo10(() => {
6396
+ const cmExtensions = useMemo11(() => {
6097
6397
  if (!bundle) return [];
6098
6398
  const exts = [bundle.saveKeymapExtension(() => saveRef.current())];
6099
6399
  if (bundle.languageExtension) exts.push(bundle.languageExtension);
@@ -6239,7 +6539,7 @@ function PlainTextarea({
6239
6539
  }
6240
6540
  },
6241
6541
  "aria-label": "Plain text file editor",
6242
- className: "h-full w-full resize-none bg-transparent p-2 font-og-mono text-og-sm text-og-fg outline-none"
6542
+ className: "h-full w-full resize-none bg-transparent p-2 font-og-mono text-og-sm text-og-fg outline-hidden"
6243
6543
  }
6244
6544
  );
6245
6545
  }
@@ -6250,6 +6550,7 @@ function EditorSkeleton() {
6250
6550
  // src/components/sandbox-files.tsx
6251
6551
  import { FileCode2Icon, FileWarningIcon as FileWarningIcon2, LoaderCircleIcon } from "lucide-react";
6252
6552
  import { useCallback as useCallback20, useEffect as useEffect20, useRef as useRef17, useState as useState17 } from "react";
6553
+ import { Group, Panel, Separator } from "react-resizable-panels";
6253
6554
  import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
6254
6555
  function SandboxFiles({
6255
6556
  files,
@@ -6297,17 +6598,30 @@ function SandboxFiles({
6297
6598
  const [wide, setWide] = useState17(false);
6298
6599
  useEffect20(() => {
6299
6600
  const el = rootRef.current;
6300
- if (!el || typeof ResizeObserver === "undefined") return;
6301
- const observer = new ResizeObserver((entries) => {
6302
- const w = entries[0]?.contentRect.width ?? 0;
6303
- setWide(w >= 720);
6304
- });
6601
+ if (!el) return;
6602
+ const update = () => setWide(el.getBoundingClientRect().width >= 720);
6603
+ update();
6604
+ if (typeof ResizeObserver === "undefined") return;
6605
+ const observer = new ResizeObserver(update);
6305
6606
  observer.observe(el);
6306
- return () => observer.disconnect();
6607
+ const surface = el.closest("[data-workspace-surface]");
6608
+ const surfaceObserver = surface && typeof MutationObserver !== "undefined" ? new MutationObserver(update) : null;
6609
+ surfaceObserver?.observe(surface, {
6610
+ attributes: true,
6611
+ attributeFilter: ["class", "style"]
6612
+ });
6613
+ return () => {
6614
+ observer.disconnect();
6615
+ surfaceObserver?.disconnect();
6616
+ };
6307
6617
  }, []);
6308
6618
  const resolvedTheme = useThemeType(themeType);
6309
6619
  const viewPath = selected;
6310
- const fileView = useFileView(viewPath, files.readFile, viewReloadRevision);
6620
+ const fileView = useFileView(
6621
+ viewPath,
6622
+ files.readFile,
6623
+ (files.contentRevision ?? 0) + viewReloadRevision
6624
+ );
6311
6625
  const selectFile = useCallback20((path) => {
6312
6626
  if (pendingRequestRef.current !== null) {
6313
6627
  handledRequestRef.current = pendingRequestRef.current;
@@ -6322,18 +6636,6 @@ function SandboxFiles({
6322
6636
  const showEditor = canEdit && editMode;
6323
6637
  const captureFileUnavailable = fileView.error instanceof CapturedFileUnavailableError ? fileView.error : null;
6324
6638
  const waitingForSelectedFile = liveRequestedPath === viewPath && (!liveWorkspaceReady || files.loading) && captureFileUnavailable !== null;
6325
- if (!fileSystemAvailable) {
6326
- return /* @__PURE__ */ jsx10(
6327
- Notice,
6328
- {
6329
- className,
6330
- icon: /* @__PURE__ */ jsx10(FileWarningIcon2, { className: "size-5", "aria-hidden": true }),
6331
- title: "Files unavailable",
6332
- announce: "alert",
6333
- children: "This sandbox does not expose a file system."
6334
- }
6335
- );
6336
- }
6337
6639
  if (workspaceResting || workspaceWaking) {
6338
6640
  return /* @__PURE__ */ jsx10("div", { className: cn("h-full", className), "data-opengeni-workspace-resting": true, children: /* @__PURE__ */ jsxs8(
6339
6641
  Notice,
@@ -6354,162 +6656,206 @@ function SandboxFiles({
6354
6656
  }
6355
6657
  ) });
6356
6658
  }
6659
+ if (!fileSystemAvailable) {
6660
+ return /* @__PURE__ */ jsx10(
6661
+ Notice,
6662
+ {
6663
+ className,
6664
+ icon: /* @__PURE__ */ jsx10(FileWarningIcon2, { className: "size-5", "aria-hidden": true }),
6665
+ title: "Files unavailable",
6666
+ announce: "alert",
6667
+ children: "This sandbox does not expose a file system."
6668
+ }
6669
+ );
6670
+ }
6357
6671
  return /* @__PURE__ */ jsxs8("div", { ref: rootRef, className: cn("flex h-full min-h-0 min-w-0 flex-col", className), children: [
6358
- /* @__PURE__ */ jsx10(GitHeader, { git, dirtyCount: git.diff.length }),
6359
- /* @__PURE__ */ jsxs8("div", { className: cn("flex min-h-0 flex-1", wide ? "flex-row" : "flex-col"), children: [
6360
- /* @__PURE__ */ jsx10(
6361
- "div",
6362
- {
6363
- className: cn(
6364
- "flex min-h-0 flex-col",
6365
- wide ? "w-[280px] shrink-0 border-r border-og-border" : "flex-1"
6366
- ),
6367
- children: /* @__PURE__ */ jsx10(
6368
- FileBrowser,
6369
- {
6370
- result: files,
6371
- selectedPath: selected ?? void 0,
6372
- onSelectFile: selectFile,
6373
- editable,
6374
- emptyState: "This directory is empty",
6375
- className: "min-w-0 flex-1"
6376
- }
6377
- )
6672
+ /* @__PURE__ */ jsx10(
6673
+ GitHeader,
6674
+ {
6675
+ loading: Boolean(files.loading || files.gitLoading || files.source === null || git?.loading) && files.gitSummary === null,
6676
+ git: files.gitSummary ?? {
6677
+ branch: git?.branch ?? null,
6678
+ isRepo: git?.isRepo ?? false,
6679
+ repoCount: git?.repoCount ?? 0,
6680
+ ahead: git?.ahead ?? 0,
6681
+ behind: git?.behind ?? 0,
6682
+ dirtyCount: git?.diff.length ?? 0
6378
6683
  }
6379
- ),
6380
- /* @__PURE__ */ jsxs8(
6381
- "div",
6382
- {
6383
- className: cn(
6384
- "flex min-h-0 min-w-0 flex-col",
6385
- wide ? "flex-1" : "flex-[1.4] border-t border-og-border"
6386
- ),
6387
- children: [
6388
- /* @__PURE__ */ jsxs8("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-og-border bg-og-surface-1 px-2 py-1", children: [
6389
- /* @__PURE__ */ jsx10(
6390
- "span",
6391
- {
6392
- "data-opengeni-selected-file": true,
6393
- className: "min-w-0 truncate font-og-mono text-og-xs text-og-fg-muted",
6394
- children: selected ?? "No file selected"
6395
- }
6396
- ),
6397
- canEdit && /* @__PURE__ */ jsx10(
6398
- Segmented,
6684
+ }
6685
+ ),
6686
+ /* @__PURE__ */ jsxs8(
6687
+ Group,
6688
+ {
6689
+ orientation: wide ? "horizontal" : "vertical",
6690
+ className: cn("min-h-0 flex-1", wide ? "flex-row" : "flex-col"),
6691
+ children: [
6692
+ /* @__PURE__ */ jsx10(
6693
+ Panel,
6694
+ {
6695
+ id: "sandbox-files-tree",
6696
+ defaultSize: wide ? "280px" : "40%",
6697
+ minSize: wide ? "180px" : "120px",
6698
+ maxSize: wide ? "60%" : "70%",
6699
+ className: "flex min-h-0 min-w-0 flex-col",
6700
+ children: /* @__PURE__ */ jsx10(
6701
+ FileBrowser,
6399
6702
  {
6400
- options: [
6401
- { value: "view", label: "View" },
6402
- { value: "edit", label: "Edit" }
6403
- ],
6404
- value: editMode ? "edit" : "view",
6405
- onChange: (v) => setEditMode(v === "edit")
6703
+ result: files,
6704
+ selectedPath: selected ?? void 0,
6705
+ onSelectFile: selectFile,
6706
+ editable,
6707
+ emptyState: "This directory is empty",
6708
+ className: "min-w-0 flex-1"
6406
6709
  }
6407
6710
  )
6408
- ] }),
6409
- /* @__PURE__ */ jsx10("div", { className: "min-h-0 flex-1 overflow-auto", children: viewPath ? showEditor && fileView.content !== null ? /* @__PURE__ */ jsx10(
6410
- CodeEditor,
6411
- {
6412
- path: viewPath,
6413
- initialContents: fileView.content,
6414
- themeType: resolvedTheme,
6415
- onSave: (contents) => files.writeFile(viewPath, contents, { expectedContent: fileView.content }),
6416
- onOverwrite: (contents) => files.writeFile(viewPath, contents, { force: true }),
6417
- onReload: () => setViewReloadRevision((revision) => revision + 1),
6418
- ...onEditIntent ? { onEditIntent } : {},
6419
- className: "h-full"
6420
- },
6421
- viewPath
6422
- ) : waitingForSelectedFile ? /* @__PURE__ */ jsxs8(
6423
- Notice,
6424
- {
6425
- icon: /* @__PURE__ */ jsx10(
6426
- LoaderCircleIcon,
6711
+ }
6712
+ ),
6713
+ /* @__PURE__ */ jsx10(
6714
+ Separator,
6715
+ {
6716
+ "aria-label": "Resize file tree",
6717
+ className: cn(
6718
+ "group relative z-10 shrink-0 cursor-col-resize outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent",
6719
+ "w-px bg-og-border after:absolute after:inset-y-0 after:left-1/2 after:w-3 after:-translate-x-1/2",
6720
+ "hover:bg-og-accent focus-visible:bg-og-accent data-[separator-state=dragging]:bg-og-accent",
6721
+ !wide && "h-px w-full cursor-row-resize after:inset-x-0 after:inset-y-auto after:top-1/2 after:h-3 after:w-full after:-translate-x-0 after:-translate-y-1/2"
6722
+ )
6723
+ }
6724
+ ),
6725
+ /* @__PURE__ */ jsxs8(
6726
+ Panel,
6727
+ {
6728
+ id: "sandbox-files-viewer",
6729
+ minSize: wide ? "240px" : "160px",
6730
+ className: cn("flex min-h-0 min-w-0 flex-col", !wide && "border-t border-og-border"),
6731
+ children: [
6732
+ /* @__PURE__ */ jsxs8("div", { className: "flex shrink-0 items-center justify-between gap-2 border-b border-og-border bg-og-surface-1 px-2 py-1", children: [
6733
+ /* @__PURE__ */ jsx10(
6734
+ "span",
6735
+ {
6736
+ "data-opengeni-selected-file": true,
6737
+ className: "min-w-0 truncate font-og-mono text-og-xs text-og-fg-muted",
6738
+ children: selected ?? "No file selected"
6739
+ }
6740
+ ),
6741
+ canEdit && /* @__PURE__ */ jsx10(
6742
+ Segmented,
6743
+ {
6744
+ options: [
6745
+ { value: "view", label: "View" },
6746
+ { value: "edit", label: "Edit" }
6747
+ ],
6748
+ value: editMode ? "edit" : "view",
6749
+ onChange: (v) => setEditMode(v === "edit")
6750
+ }
6751
+ )
6752
+ ] }),
6753
+ /* @__PURE__ */ jsx10("div", { className: "min-h-0 flex-1 overflow-auto", children: viewPath ? showEditor && fileView.content !== null ? /* @__PURE__ */ jsx10(
6754
+ CodeEditor,
6427
6755
  {
6428
- className: "size-5 animate-spin motion-reduce:animate-none",
6429
- "aria-hidden": true
6756
+ path: viewPath,
6757
+ initialContents: fileView.content,
6758
+ themeType: resolvedTheme,
6759
+ onSave: (contents) => files.writeFile(viewPath, contents, { expectedContent: fileView.content }),
6760
+ onOverwrite: (contents) => files.writeFile(viewPath, contents, { force: true }),
6761
+ onReload: () => setViewReloadRevision((revision) => revision + 1),
6762
+ ...onEditIntent ? { onEditIntent } : {},
6763
+ className: "h-full"
6764
+ },
6765
+ viewPath
6766
+ ) : waitingForSelectedFile ? /* @__PURE__ */ jsxs8(
6767
+ Notice,
6768
+ {
6769
+ icon: /* @__PURE__ */ jsx10(
6770
+ LoaderCircleIcon,
6771
+ {
6772
+ className: "size-5 animate-spin motion-reduce:animate-none",
6773
+ "aria-hidden": true
6774
+ }
6775
+ ),
6776
+ title: "Waking workspace",
6777
+ announce: "status",
6778
+ children: [
6779
+ "Opening ",
6780
+ viewPath,
6781
+ " when the live file system is ready\u2026"
6782
+ ]
6430
6783
  }
6431
- ),
6432
- title: "Waking workspace",
6433
- announce: "status",
6434
- children: [
6435
- "Opening ",
6784
+ ) : captureFileUnavailable ? /* @__PURE__ */ jsxs8(Notice, { icon: /* @__PURE__ */ jsx10(FileCode2Icon, { className: "size-5", "aria-hidden": true }), title: "On machine", children: [
6785
+ /* @__PURE__ */ jsx10("p", { children: captureFileUnavailable.reason === "too-large" ? "This file is larger than the captured preview limit." : captureFileUnavailable.reason === "content-missing" ? "The captured copy is no longer available." : "This file was indexed, but it was not changed in the captured turn." }),
6786
+ onWakeWorkspace ? /* @__PURE__ */ jsx10(
6787
+ WakeButton,
6788
+ {
6789
+ onClick: () => {
6790
+ setLiveRequestedPath(viewPath);
6791
+ if (liveWorkspaceReady) void files.refresh();
6792
+ else onWakeWorkspace();
6793
+ },
6794
+ children: liveWorkspaceReady ? "Retry live file" : "Open live file"
6795
+ }
6796
+ ) : null
6797
+ ] }) : fileView.error ? /* @__PURE__ */ jsxs8(Notice, { announce: "alert", children: [
6798
+ "Could not open ",
6436
6799
  viewPath,
6437
- " when the live file system is ready\u2026"
6438
- ]
6439
- }
6440
- ) : captureFileUnavailable ? /* @__PURE__ */ jsxs8(Notice, { icon: /* @__PURE__ */ jsx10(FileCode2Icon, { className: "size-5", "aria-hidden": true }), title: "On machine", children: [
6441
- /* @__PURE__ */ jsx10("p", { children: captureFileUnavailable.reason === "too-large" ? "This file is larger than the captured preview limit." : captureFileUnavailable.reason === "content-missing" ? "The captured copy is no longer available." : "This file was indexed, but it was not changed in the captured turn." }),
6442
- onWakeWorkspace ? /* @__PURE__ */ jsx10(
6443
- WakeButton,
6444
- {
6445
- onClick: () => {
6446
- setLiveRequestedPath(viewPath);
6447
- if (liveWorkspaceReady) void files.refresh();
6448
- else onWakeWorkspace();
6449
- },
6450
- children: liveWorkspaceReady ? "Retry live file" : "Open live file"
6451
- }
6452
- ) : null
6453
- ] }) : fileView.error ? /* @__PURE__ */ jsxs8(Notice, { announce: "alert", children: [
6454
- "Could not open ",
6455
- viewPath,
6456
- ": ",
6457
- fileView.error.message
6458
- ] }) : fileView.loading ? /* @__PURE__ */ jsxs8(Notice, { announce: "status", children: [
6459
- "Loading ",
6460
- viewPath,
6461
- "\u2026"
6462
- ] }) : fileView.isBinary ? /* @__PURE__ */ jsxs8(Notice, { children: [
6463
- viewPath,
6464
- " is a binary file (",
6465
- fileView.sizeBytes ?? 0,
6466
- " bytes)."
6467
- ] }) : fileView.content !== null ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
6468
- fileView.truncated && /* @__PURE__ */ jsxs8("div", { className: "border-b border-og-border bg-og-surface-1 px-2 py-1 text-og-xs text-og-status-running", children: [
6469
- "Large file \u2014 showing a truncated preview (",
6470
- fileView.sizeBytes ?? 0,
6471
- " bytes loaded). Editing is disabled to avoid corrupting the file."
6472
- ] }),
6473
- usePierre ? /* @__PURE__ */ jsx10(
6474
- PierreFile,
6475
- {
6476
- path: viewPath,
6477
- contents: fileView.content,
6478
- themeType: resolvedTheme,
6479
- fallback: /* @__PURE__ */ jsx10("pre", { className: "overflow-auto whitespace-pre p-2 font-og-mono text-og-sm text-og-fg", children: fileView.content }),
6480
- className: "p-1"
6481
- }
6482
- ) : /* @__PURE__ */ jsx10("pre", { className: "overflow-auto whitespace-pre p-2 font-og-mono text-og-sm text-og-fg", children: fileView.content })
6483
- ] }) : /* @__PURE__ */ jsxs8(Notice, { announce: "status", children: [
6484
- "Loading ",
6485
- viewPath,
6486
- "\u2026"
6487
- ] }) : (
6488
- // Nothing selected — the tree shows the whole workspace; pick a file.
6489
- requestedPath && !requestedPathReady ? /* @__PURE__ */ jsxs8(
6490
- Notice,
6491
- {
6492
- icon: /* @__PURE__ */ jsx10(
6493
- LoaderCircleIcon,
6800
+ ": ",
6801
+ fileView.error.message
6802
+ ] }) : fileView.loading ? /* @__PURE__ */ jsxs8(Notice, { announce: "status", children: [
6803
+ "Loading ",
6804
+ viewPath,
6805
+ "\u2026"
6806
+ ] }) : fileView.isBinary ? /* @__PURE__ */ jsxs8(Notice, { children: [
6807
+ viewPath,
6808
+ " is a binary file (",
6809
+ fileView.sizeBytes ?? 0,
6810
+ " bytes)."
6811
+ ] }) : fileView.content !== null ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
6812
+ fileView.truncated && /* @__PURE__ */ jsxs8("div", { className: "border-b border-og-border bg-og-surface-1 px-2 py-1 text-og-xs text-og-status-running", children: [
6813
+ "Large file \u2014 showing a truncated preview (",
6814
+ fileView.sizeBytes ?? 0,
6815
+ " bytes loaded). Editing is disabled to avoid corrupting the file."
6816
+ ] }),
6817
+ usePierre ? /* @__PURE__ */ jsx10(
6818
+ PierreFile,
6494
6819
  {
6495
- className: "size-5 animate-spin motion-reduce:animate-none",
6496
- "aria-hidden": true
6820
+ path: viewPath,
6821
+ contents: fileView.content,
6822
+ themeType: resolvedTheme,
6823
+ fallback: /* @__PURE__ */ jsx10("pre", { className: "overflow-auto whitespace-pre p-2 font-og-mono text-og-sm text-og-fg", children: fileView.content }),
6824
+ className: "p-1"
6497
6825
  }
6498
- ),
6499
- title: "Waking sandbox",
6500
- announce: "status",
6501
- children: [
6502
- "Opening ",
6503
- requestedPath,
6504
- " when the live workspace is ready\u2026"
6505
- ]
6506
- }
6507
- ) : /* @__PURE__ */ jsx10(Notice, { icon: /* @__PURE__ */ jsx10(FileCode2Icon, { className: "size-5", "aria-hidden": true }), title: "Choose a file", children: "Select a file in the tree to preview it." })
6508
- ) })
6509
- ]
6510
- }
6511
- )
6512
- ] })
6826
+ ) : /* @__PURE__ */ jsx10("pre", { className: "overflow-auto whitespace-pre p-2 font-og-mono text-og-sm text-og-fg", children: fileView.content })
6827
+ ] }) : /* @__PURE__ */ jsxs8(Notice, { announce: "status", children: [
6828
+ "Loading ",
6829
+ viewPath,
6830
+ "\u2026"
6831
+ ] }) : (
6832
+ // Nothing selected — the tree shows the whole workspace; pick a file.
6833
+ requestedPath && !requestedPathReady ? /* @__PURE__ */ jsxs8(
6834
+ Notice,
6835
+ {
6836
+ icon: /* @__PURE__ */ jsx10(
6837
+ LoaderCircleIcon,
6838
+ {
6839
+ className: "size-5 animate-spin motion-reduce:animate-none",
6840
+ "aria-hidden": true
6841
+ }
6842
+ ),
6843
+ title: "Waking sandbox",
6844
+ announce: "status",
6845
+ children: [
6846
+ "Opening ",
6847
+ requestedPath,
6848
+ " when the live workspace is ready\u2026"
6849
+ ]
6850
+ }
6851
+ ) : /* @__PURE__ */ jsx10(Notice, { icon: /* @__PURE__ */ jsx10(FileCode2Icon, { className: "size-5", "aria-hidden": true }), title: "Choose a file", children: "Select a file in the tree to preview it." })
6852
+ ) })
6853
+ ]
6854
+ }
6855
+ )
6856
+ ]
6857
+ }
6858
+ )
6513
6859
  ] });
6514
6860
  }
6515
6861
  function WakeButton({ children, onClick }) {
@@ -6518,13 +6864,33 @@ function WakeButton({ children, onClick }) {
6518
6864
  {
6519
6865
  type: "button",
6520
6866
  onClick,
6521
- className: "mt-1 inline-flex min-h-11 items-center justify-center rounded-og-md bg-og-accent-deep px-3 py-2 text-og-sm font-medium text-og-accent-fg shadow-sm transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent focus-visible:ring-offset-2 focus-visible:ring-offset-og-bg",
6867
+ className: "mt-1 inline-flex min-h-11 items-center justify-center rounded-og-md bg-og-accent-deep px-3 py-2 text-og-sm font-medium text-og-accent-fg shadow-sm transition-opacity hover:opacity-90 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent focus-visible:ring-offset-2 focus-visible:ring-offset-og-bg",
6522
6868
  children
6523
6869
  }
6524
6870
  );
6525
6871
  }
6526
- function GitHeader({ git, dirtyCount }) {
6527
- const dirty = dirtyCount > 0;
6872
+ function GitHeader({ git, loading }) {
6873
+ if (loading) {
6874
+ return /* @__PURE__ */ jsxs8(
6875
+ "div",
6876
+ {
6877
+ className: "flex shrink-0 items-center gap-2 border-b border-og-border bg-og-surface-1 px-2 py-1 text-og-sm text-og-fg-muted",
6878
+ role: "status",
6879
+ "aria-live": "polite",
6880
+ children: [
6881
+ /* @__PURE__ */ jsx10(
6882
+ LoaderCircleIcon,
6883
+ {
6884
+ className: "size-3.5 shrink-0 animate-spin motion-reduce:animate-none",
6885
+ "aria-hidden": true
6886
+ }
6887
+ ),
6888
+ /* @__PURE__ */ jsx10("span", { children: "Loading repository\u2026" })
6889
+ ]
6890
+ }
6891
+ );
6892
+ }
6893
+ const dirty = git.dirtyCount > 0;
6528
6894
  return /* @__PURE__ */ jsxs8("div", { className: "flex shrink-0 items-center gap-2 border-b border-og-border bg-og-surface-1 px-2 py-1 text-og-sm", children: [
6529
6895
  /* @__PURE__ */ jsx10(
6530
6896
  "span",
@@ -6536,7 +6902,7 @@ function GitHeader({ git, dirtyCount }) {
6536
6902
  )
6537
6903
  }
6538
6904
  ),
6539
- /* @__PURE__ */ jsx10("span", { className: "sr-only", children: dirty ? `Working tree has ${dirtyCount} changed files` : "Working tree clean" }),
6905
+ /* @__PURE__ */ jsx10("span", { className: "sr-only", children: dirty ? `Uncommitted changes in ${git.dirtyCount} files` : "No uncommitted changes" }),
6540
6906
  /* @__PURE__ */ jsx10("span", { className: "truncate font-og-mono text-og-fg", children: git.repoCount > 1 ? `${git.repoCount} repositories` : git.branch ?? (git.isRepo ? "(detached)" : "no repo") }),
6541
6907
  (git.ahead > 0 || git.behind > 0) && /* @__PURE__ */ jsxs8("span", { className: "flex shrink-0 items-center gap-1.5 text-og-xs text-og-fg-subtle", children: [
6542
6908
  git.ahead > 0 && /* @__PURE__ */ jsxs8("span", { children: [
@@ -6555,7 +6921,7 @@ function GitHeader({ git, dirtyCount }) {
6555
6921
  "data-contrast-audited": true,
6556
6922
  className: "ml-auto shrink-0 text-og-xs text-og-fg-subtle",
6557
6923
  children: [
6558
- dirtyCount,
6924
+ git.dirtyCount,
6559
6925
  " changed"
6560
6926
  ]
6561
6927
  }
@@ -6573,7 +6939,7 @@ function Segmented({
6573
6939
  type: "button",
6574
6940
  onClick: () => onChange(opt.value),
6575
6941
  className: cn(
6576
- "min-h-7 rounded-og-xs px-1.5 py-0.5 text-og-xs focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 max-[1023px]:min-w-11 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
6942
+ "min-h-7 rounded-og-xs px-1.5 py-0.5 text-og-xs focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 max-[1023px]:min-w-11 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
6577
6943
  opt.value === value ? "bg-og-accent-soft text-og-fg" : "text-og-fg-subtle hover:text-og-fg"
6578
6944
  ),
6579
6945
  children: opt.label
@@ -6582,6 +6948,7 @@ function Segmented({
6582
6948
  )) });
6583
6949
  }
6584
6950
  function useFileView(path, readFile, reloadRevision = 0) {
6951
+ const previousPathRef = useRef17(null);
6585
6952
  const [state, setState] = useState17({
6586
6953
  content: null,
6587
6954
  isBinary: false,
@@ -6592,6 +6959,7 @@ function useFileView(path, readFile, reloadRevision = 0) {
6592
6959
  });
6593
6960
  useEffect20(() => {
6594
6961
  if (!path) {
6962
+ previousPathRef.current = null;
6595
6963
  setState({
6596
6964
  content: null,
6597
6965
  isBinary: false,
@@ -6602,16 +6970,22 @@ function useFileView(path, readFile, reloadRevision = 0) {
6602
6970
  });
6603
6971
  return;
6604
6972
  }
6973
+ const pathChanged = previousPathRef.current !== path;
6974
+ previousPathRef.current = path;
6605
6975
  let cancelled = false;
6606
6976
  const abort = new AbortController();
6607
- setState({
6608
- content: null,
6609
- isBinary: false,
6610
- truncated: false,
6611
- sizeBytes: null,
6612
- loading: true,
6613
- error: null
6614
- });
6977
+ if (pathChanged) {
6978
+ setState({
6979
+ content: null,
6980
+ isBinary: false,
6981
+ truncated: false,
6982
+ sizeBytes: null,
6983
+ loading: true,
6984
+ error: null
6985
+ });
6986
+ } else {
6987
+ setState((previous) => ({ ...previous, error: null }));
6988
+ }
6615
6989
  void readFile(path, { signal: abort.signal }).then((res) => {
6616
6990
  if (cancelled) return;
6617
6991
  const content = res.isBinary ? null : res.encoding === "base64" ? decodeBase64Utf8(res.content) : res.content;
@@ -6625,14 +6999,17 @@ function useFileView(path, readFile, reloadRevision = 0) {
6625
6999
  });
6626
7000
  }).catch((cause) => {
6627
7001
  if (cancelled) return;
6628
- setState({
6629
- content: null,
6630
- isBinary: false,
6631
- truncated: false,
6632
- sizeBytes: null,
6633
- loading: false,
6634
- error: cause instanceof Error ? cause : new Error(String(cause))
6635
- });
7002
+ const error = cause instanceof Error ? cause : new Error(String(cause));
7003
+ setState(
7004
+ (previous) => pathChanged ? {
7005
+ content: null,
7006
+ isBinary: false,
7007
+ truncated: false,
7008
+ sizeBytes: null,
7009
+ loading: false,
7010
+ error
7011
+ } : { ...previous, loading: false, error }
7012
+ );
6636
7013
  });
6637
7014
  return () => {
6638
7015
  cancelled = true;
@@ -6924,7 +7301,7 @@ function TakeControlCallToAction({
6924
7301
  "border-og-border",
6925
7302
  "bg-og-bg/85 backdrop-blur-md",
6926
7303
  "shadow-og-lg",
6927
- "outline-none transition-all duration-150 ease-out",
7304
+ "outline-hidden transition-all duration-150 ease-out",
6928
7305
  "focus-visible:ring-2 focus-visible:ring-og-accent focus-visible:ring-offset-2 focus-visible:ring-offset-og-bg",
6929
7306
  disabled ? "cursor-not-allowed opacity-60" : cn(
6930
7307
  "cursor-pointer opacity-90 hover:-translate-y-0.5 hover:opacity-100",
@@ -6972,7 +7349,7 @@ function InControlBar({ shared, onRelease }) {
6972
7349
  className: cn(
6973
7350
  "inline-flex items-center gap-1.5 rounded-og-sm border px-2.5 py-1 text-og-xs font-semibold shadow-og-md transition-colors",
6974
7351
  "border-og-accent bg-og-bg/90 text-og-fg backdrop-blur-sm",
6975
- "outline-none hover:bg-og-accent hover:text-og-accent-fg focus-visible:ring-2 focus-visible:ring-og-accent"
7352
+ "outline-hidden hover:bg-og-accent hover:text-og-accent-fg focus-visible:ring-2 focus-visible:ring-og-accent"
6976
7353
  ),
6977
7354
  children: [
6978
7355
  /* @__PURE__ */ jsx11(MonitorIcon, { className: "size-3.5", strokeWidth: 2, "aria-hidden": true }),
@@ -7075,9 +7452,9 @@ import {
7075
7452
  XIcon as XIcon2
7076
7453
  } from "lucide-react";
7077
7454
  import {
7078
- Group,
7079
- Panel,
7080
- Separator,
7455
+ Group as Group2,
7456
+ Panel as Panel2,
7457
+ Separator as Separator2,
7081
7458
  useDefaultLayout,
7082
7459
  usePanelRef
7083
7460
  } from "react-resizable-panels";
@@ -7122,6 +7499,7 @@ function WorkspaceDock({
7122
7499
  onActiveTabChange,
7123
7500
  collapsed: collapsedProp,
7124
7501
  onCollapsedChange,
7502
+ showCollapseControl = false,
7125
7503
  headerAccessory,
7126
7504
  mobileLeadingControl,
7127
7505
  autoSaveId = "og.session.dock",
@@ -7298,6 +7676,11 @@ function WorkspaceDock({
7298
7676
  className: "fixed inset-0 z-40 flex flex-col bg-og-bg",
7299
7677
  hidden: collapsed,
7300
7678
  style: {
7679
+ // Author utility classes can override the user-agent `[hidden]`
7680
+ // rule (for example this surface's `flex` class). Keep an inline
7681
+ // display guard so a collapsed mobile workspace is actually gone
7682
+ // visually as well as from the accessibility tree.
7683
+ display: collapsed ? "none" : void 0,
7301
7684
  paddingTop: "env(safe-area-inset-top)",
7302
7685
  paddingBottom: "env(safe-area-inset-bottom)"
7303
7686
  },
@@ -7322,7 +7705,7 @@ function WorkspaceDock({
7322
7705
  onClick: expand,
7323
7706
  title: "Open workspace",
7324
7707
  "aria-label": "Open workspace",
7325
- className: "absolute right-3 top-3 z-30 inline-flex size-11 items-center justify-center rounded-og-md border border-og-border bg-og-surface-1 text-og-fg-muted shadow-lg transition-colors hover:border-og-border-strong hover:text-og-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent",
7708
+ className: "absolute right-3 top-3 z-30 inline-flex size-11 items-center justify-center rounded-og-md border border-og-border bg-og-surface-1 text-og-fg-muted shadow-lg transition-colors hover:border-og-border-strong hover:text-og-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent",
7326
7709
  style: { marginTop: "env(safe-area-inset-top)" },
7327
7710
  children: /* @__PURE__ */ jsx12(ChevronsLeftRightIcon, { className: "size-4", "aria-hidden": true })
7328
7711
  }
@@ -7346,20 +7729,20 @@ function WorkspaceDock({
7346
7729
  children: maximized ? /* @__PURE__ */ jsx12(Minimize2Icon, { className: "size-3.5" }) : /* @__PURE__ */ jsx12(Maximize2Icon, { className: "size-3.5" })
7347
7730
  }
7348
7731
  ),
7349
- hostControlled ? null : /* @__PURE__ */ jsx12(ChromeButton, { onClick: collapse, title: "Collapse", label: "Collapse dock", children: /* @__PURE__ */ jsx12(PanelRightCloseIcon, { className: "size-3.5" }) })
7732
+ hostControlled && !showCollapseControl ? null : /* @__PURE__ */ jsx12(ChromeButton, { onClick: collapse, title: "Collapse", label: "Collapse dock", children: /* @__PURE__ */ jsx12(PanelRightCloseIcon, { className: "size-3.5" }) })
7350
7733
  ] })
7351
7734
  }
7352
7735
  );
7353
7736
  return /* @__PURE__ */ jsxs10("div", { ref: rootRef, className: cn("relative flex h-full min-h-0 w-full min-w-0", className), children: [
7354
7737
  /* @__PURE__ */ jsxs10(
7355
- Group,
7738
+ Group2,
7356
7739
  {
7357
7740
  orientation: "horizontal",
7358
7741
  className: "min-h-0 flex-1",
7359
7742
  ...defaultLayout ? { defaultLayout } : {},
7360
7743
  onLayoutChanged,
7361
7744
  children: [
7362
- /* @__PURE__ */ jsx12(Panel, { id: "primary", minSize: "30%", className: "min-h-0 min-w-0", children: /* @__PURE__ */ jsx12(
7745
+ /* @__PURE__ */ jsx12(Panel2, { id: "primary", minSize: "30%", className: "min-h-0 min-w-0", children: /* @__PURE__ */ jsx12(
7363
7746
  "div",
7364
7747
  {
7365
7748
  "data-workspace-primary": true,
@@ -7369,9 +7752,9 @@ function WorkspaceDock({
7369
7752
  children: primary
7370
7753
  }
7371
7754
  ) }),
7372
- !collapsed && /* @__PURE__ */ jsx12(Separator, { className: "group relative z-10 w-1.5 shrink-0 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent max-[1023px]:-mx-[19px] max-[1023px]:w-11 pointer-coarse:-mx-[19px] pointer-coarse:w-11", children: /* @__PURE__ */ jsx12("span", { className: "absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-og-border transition-colors group-hover:bg-og-accent group-focus-visible:bg-og-accent group-data-[separator-state=dragging]:bg-og-accent" }) }),
7755
+ !collapsed && /* @__PURE__ */ jsx12(Separator2, { className: "group relative z-10 w-1.5 shrink-0 outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent max-[1023px]:-mx-[19px] max-[1023px]:w-11 pointer-coarse:-mx-[19px] pointer-coarse:w-11", children: /* @__PURE__ */ jsx12("span", { className: "absolute inset-y-0 left-1/2 w-px -translate-x-1/2 bg-og-border transition-colors group-hover:bg-og-accent group-focus-visible:bg-og-accent group-data-[separator-state=dragging]:bg-og-accent" }) }),
7373
7756
  /* @__PURE__ */ jsx12(
7374
- Panel,
7757
+ Panel2,
7375
7758
  {
7376
7759
  id: "dock",
7377
7760
  panelRef: dockPanelRef,
@@ -7435,7 +7818,7 @@ function ChromeButton({
7435
7818
  onClick,
7436
7819
  title,
7437
7820
  "aria-label": label,
7438
- className: "inline-flex size-7 items-center justify-center rounded-og-sm p-1 transition-colors hover:bg-og-surface-2 hover:text-og-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:size-11 pointer-coarse:size-11",
7821
+ className: "inline-flex size-7 items-center justify-center rounded-og-sm p-1 transition-colors hover:bg-og-surface-2 hover:text-og-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:size-11 pointer-coarse:size-11",
7439
7822
  children
7440
7823
  }
7441
7824
  );
@@ -7547,7 +7930,7 @@ function DockChrome({
7547
7930
  onClick: () => onTab(tab.id),
7548
7931
  onKeyDown: (event) => onTabKeyDown(event, index),
7549
7932
  className: cn(
7550
- "flex min-h-7 shrink-0 items-center justify-center gap-1 rounded-og-sm px-2 py-1 text-og-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 max-[1023px]:min-w-11 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
7933
+ "flex min-h-7 shrink-0 items-center justify-center gap-1 rounded-og-sm px-2 py-1 text-og-xs font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 max-[1023px]:min-w-11 pointer-coarse:min-h-11 pointer-coarse:min-w-11",
7551
7934
  tab.id === current ? "bg-og-accent-soft text-og-fg" : "text-og-fg-subtle hover:text-og-fg"
7552
7935
  ),
7553
7936
  children: [
@@ -7582,14 +7965,16 @@ function DockChrome({
7582
7965
  }
7583
7966
 
7584
7967
  // src/components/sandbox-workspace.tsx
7585
- import { useCallback as useCallback22, useEffect as useEffect23, useMemo as useMemo11, useRef as useRef20, useState as useState20 } from "react";
7586
- import { Popover } from "radix-ui";
7968
+ import { useCallback as useCallback22, useEffect as useEffect23, useMemo as useMemo12, useRef as useRef20, useState as useState20 } from "react";
7587
7969
  import {
7588
7970
  CircleCheckIcon,
7589
7971
  CpuIcon,
7590
- LaptopIcon,
7972
+ FileCode2Icon as FileCode2Icon2,
7973
+ GitCompareArrowsIcon,
7591
7974
  LoaderCircleIcon as LoaderCircleIcon3,
7975
+ MonitorIcon as MonitorIcon2,
7592
7976
  RefreshCwIcon as RefreshCwIcon2,
7977
+ SquareTerminalIcon,
7593
7978
  TriangleAlertIcon as TriangleAlertIcon2
7594
7979
  } from "lucide-react";
7595
7980
  import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
@@ -7624,6 +8009,12 @@ function captureDegradedMessage(reason) {
7624
8009
  return "Workspace capture is incomplete because repository discovery failed. Live files remain authoritative.";
7625
8010
  }
7626
8011
  }
8012
+ function WorkbenchTabLabel({ icon, children }) {
8013
+ return /* @__PURE__ */ jsxs11("span", { className: "inline-flex items-center gap-1.5", children: [
8014
+ /* @__PURE__ */ jsx13("span", { className: "[&>svg]:size-3.5", "aria-hidden": true, children: icon }),
8015
+ /* @__PURE__ */ jsx13("span", { children })
8016
+ ] });
8017
+ }
7627
8018
  function initialWorkspaceTab(events, override) {
7628
8019
  if (override) return override;
7629
8020
  let bestSeq = -1;
@@ -7666,6 +8057,7 @@ function useSandboxWorkspaceTabs(options) {
7666
8057
  const { client, workspaceId } = useOpenGeni(options);
7667
8058
  const { sessionId, events, onNotify, requestedFilePath, requestedFileRequestId, onOpenFile } = options;
7668
8059
  const initialTab = options.initialTab ?? null;
8060
+ const activeTab = options.activeTab ?? initialTab;
7669
8061
  const requestedSurfaces = options.surfaces ?? WORKBENCH_SURFACES;
7670
8062
  const surfaceSet = new Set(requestedSurfaces);
7671
8063
  const changesEnabled = surfaceSet.has(WORKBENCH_TAB_CHANGES);
@@ -7674,6 +8066,9 @@ function useSandboxWorkspaceTabs(options) {
7674
8066
  const desktopEnabled = surfaceSet.has(WORKBENCH_TAB_DESKTOP);
7675
8067
  const workspaceDataEnabled = changesEnabled || filesEnabled;
7676
8068
  const anySurfaceEnabled = workspaceDataEnabled || terminalEnabled || desktopEnabled;
8069
+ const resolvedActiveTab = activeTab ?? initialTab;
8070
+ const changesActive = resolvedActiveTab === WORKBENCH_TAB_CHANGES;
8071
+ const filesActive = resolvedActiveTab === WORKBENCH_TAB_FILES;
7677
8072
  const surfaceIdentity = WORKBENCH_SURFACES.filter((surface) => surfaceSet.has(surface)).join(",");
7678
8073
  const [storedWarmIntents, setStoredWarmIntents] = useState20(
7679
8074
  () => emptyWarmIntents(sessionId)
@@ -7737,7 +8132,7 @@ function useSandboxWorkspaceTabs(options) {
7737
8132
  const captureAvailable = captureState.available;
7738
8133
  const liveWorkspaceExpected = liveness === "warm" || liveness === "draining";
7739
8134
  const capturePending = !liveWorkspaceExpected && (captureState.fileCount === null || captureState.loading);
7740
- const repoPaths = useMemo11(() => {
8135
+ const repoPaths = useMemo12(() => {
7741
8136
  const advertised = capabilities?.Git.repos ?? [];
7742
8137
  if (advertised.length > 0) return advertised;
7743
8138
  return captureState.capture?.repos.map((repo) => repo.root) ?? [];
@@ -7760,6 +8155,8 @@ function useSandboxWorkspaceTabs(options) {
7760
8155
  // No passive Channel-A reads while cold, even after a conclusive capture
7761
8156
  // miss. A missing capture gets an explicit "Open live workspace" gate.
7762
8157
  enabled: filesEnabled && (captureAvailable || fileSystemOn && liveWorkspaceExpected),
8158
+ active: filesActive,
8159
+ repoPaths,
7763
8160
  liveness,
7764
8161
  capture: captureState.capture,
7765
8162
  // A reverted optimistic mutation (e.g. a 409 rename collision) surfaces as a
@@ -7769,23 +8166,30 @@ function useSandboxWorkspaceTabs(options) {
7769
8166
  message: `Could not ${op}: ${error.message}`
7770
8167
  })
7771
8168
  });
8169
+ const [storedChangesComparison, setStoredChangesComparison] = useState20(null);
8170
+ const captureSupportsBranch = captureState.capture !== null && captureState.capture.repos.length > 0 && captureState.capture.repos.every((repo) => repo.branchDiff !== void 0);
8171
+ const changesComparison = storedChangesComparison?.sessionId === sessionId ? storedChangesComparison.value : liveWorkspaceExpected || captureSupportsBranch ? "branch" : "working";
8172
+ const setChangesComparison = useCallback22(
8173
+ (value) => setStoredChangesComparison({ sessionId, value }),
8174
+ [sessionId]
8175
+ );
7772
8176
  const git = useSandboxGit(sessionId, {
7773
8177
  events,
7774
- enabled: workspaceDataEnabled && (captureAvailable || gitOn && liveWorkspaceExpected),
8178
+ enabled: changesComparison === "working" ? workspaceDataEnabled && (captureAvailable || gitOn && liveWorkspaceExpected) : changesComparison === "branch" ? workspaceDataEnabled && (captureSupportsBranch || gitOn && liveWorkspaceExpected) : workspaceDataEnabled && gitOn && liveWorkspaceExpected,
8179
+ // Before the dock chooses its source-driven default, Git is the sole probe
8180
+ // that may need to resolve that choice on a live workspace. Files remains
8181
+ // dormant, avoiding the old duplicate full-tree read.
8182
+ active: changesActive || resolvedActiveTab === null,
7775
8183
  repoPaths,
7776
8184
  liveness,
7777
- capture: captureState.capture
7778
- });
7779
- const stagedGit = useSandboxGit(sessionId, {
7780
- events,
7781
- // Captures intentionally expose one combined review diff, not a staged
7782
- // index. Never probe the live index while the lease is cold.
7783
- enabled: filesEnabled && gitOn && liveWorkspaceExpected,
7784
- staged: true,
7785
- repoPaths,
7786
- liveness,
7787
- capture: captureState.capture
8185
+ comparison: changesComparison,
8186
+ capture: changesComparison === "staged" ? null : captureState.capture
7788
8187
  });
8188
+ useEffect23(() => {
8189
+ if (changesComparison === "branch" && git.error && storedChangesComparison?.sessionId !== sessionId) {
8190
+ setStoredChangesComparison({ sessionId, value: "working" });
8191
+ }
8192
+ }, [changesComparison, git.error, sessionId, storedChangesComparison?.sessionId]);
7789
8193
  const [xtermTheme, setXtermTheme] = useState20(void 0);
7790
8194
  useEffect23(() => {
7791
8195
  if (!terminalEnabled || typeof document === "undefined") return;
@@ -7838,6 +8242,7 @@ function useSandboxWorkspaceTabs(options) {
7838
8242
  }
7839
8243
  const captureIsAuthoritative = !liveWorkspaceExpected && (liveness !== void 0 || caps.error !== null);
7840
8244
  const captureUnavailable = captureState.fileCount === 0 || captureState.error !== null;
8245
+ const implicitBranchFallbackPending = changesComparison === "branch" && git.error !== null && storedChangesComparison?.sessionId !== sessionId;
7841
8246
  if (defaultTabRef.current.value === null) {
7842
8247
  if (initialTab && (!isWorkbenchSurface(initialTab) || surfaceSet.has(initialTab))) {
7843
8248
  defaultTabRef.current.value = initialTab;
@@ -7865,22 +8270,24 @@ function useSandboxWorkspaceTabs(options) {
7865
8270
  changesEnabled,
7866
8271
  filesEnabled
7867
8272
  );
7868
- } else if (captureUnavailable && git.error) {
8273
+ } else if (captureUnavailable && git.error && !implicitBranchFallbackPending) {
7869
8274
  defaultTabRef.current.value = sourceDrivenDefaultTab(false, changesEnabled, filesEnabled);
7870
8275
  }
7871
8276
  }
7872
8277
  const defaultTab = defaultTabRef.current.value;
7873
- const tabs = useMemo11(() => {
8278
+ const tabs = useMemo12(() => {
7874
8279
  const list = [];
7875
8280
  if (changesEnabled)
7876
8281
  list.push({
7877
8282
  id: WORKBENCH_TAB_CHANGES,
7878
- label: "Changes",
8283
+ label: /* @__PURE__ */ jsx13(WorkbenchTabLabel, { icon: /* @__PURE__ */ jsx13(GitCompareArrowsIcon, {}), children: "Changes" }),
7879
8284
  badge: dirtyCount > 0 ? /* @__PURE__ */ jsx13(DirtyBadge, { count: dirtyCount }) : void 0,
7880
8285
  content: /* @__PURE__ */ jsx13(
7881
8286
  ChangesTabBody,
7882
8287
  {
7883
8288
  git,
8289
+ comparison: changesComparison,
8290
+ onComparisonChange: setChangesComparison,
7884
8291
  captureAvailable,
7885
8292
  captureRevision: captureState.revision,
7886
8293
  capturePending,
@@ -7902,13 +8309,12 @@ function useSandboxWorkspaceTabs(options) {
7902
8309
  if (filesEnabled)
7903
8310
  list.push({
7904
8311
  id: WORKBENCH_TAB_FILES,
7905
- label: "Files",
8312
+ label: /* @__PURE__ */ jsx13(WorkbenchTabLabel, { icon: /* @__PURE__ */ jsx13(FileCode2Icon2, {}), children: "Files" }),
7906
8313
  content: /* @__PURE__ */ jsx13(
7907
8314
  SandboxFiles,
7908
8315
  {
7909
8316
  files,
7910
8317
  git,
7911
- stagedGit,
7912
8318
  fileSystemAvailable: fileSystemOn || captureAvailable,
7913
8319
  editable: filesEditable && files.source === "live" && files.error === null && !files.loading,
7914
8320
  workspaceResting: !liveWorkspaceExpected && liveness !== void 0 && !capturePending && !captureAvailable && !workspaceWaking,
@@ -7929,7 +8335,7 @@ function useSandboxWorkspaceTabs(options) {
7929
8335
  if (terminalOn) {
7930
8336
  list.push({
7931
8337
  id: WORKBENCH_TAB_TERMINAL,
7932
- label: "Terminal",
8338
+ label: /* @__PURE__ */ jsx13(WorkbenchTabLabel, { icon: /* @__PURE__ */ jsx13(SquareTerminalIcon, {}), children: "Terminal" }),
7933
8339
  content: /* @__PURE__ */ jsx13("div", { className: "h-full bg-og-bg p-1", children: /* @__PURE__ */ jsx13(
7934
8340
  SandboxTerminal,
7935
8341
  {
@@ -7947,7 +8353,7 @@ function useSandboxWorkspaceTabs(options) {
7947
8353
  if (desktopAdvertised) {
7948
8354
  list.push({
7949
8355
  id: WORKBENCH_TAB_DESKTOP,
7950
- label: "Desktop",
8356
+ label: /* @__PURE__ */ jsx13(WorkbenchTabLabel, { icon: /* @__PURE__ */ jsx13(MonitorIcon2, {}), children: "Desktop" }),
7951
8357
  badge: watchDesktop ? /* @__PURE__ */ jsx13("span", { className: "rounded-og-xs bg-og-status-running/20 px-1 text-og-xs text-og-status-running", children: "Live" }) : void 0,
7952
8358
  content: /* @__PURE__ */ jsx13(
7953
8359
  DesktopViewer,
@@ -7980,7 +8386,7 @@ function useSandboxWorkspaceTabs(options) {
7980
8386
  sessionId,
7981
8387
  files,
7982
8388
  git,
7983
- stagedGit,
8389
+ changesComparison,
7984
8390
  terminal,
7985
8391
  xtermTheme,
7986
8392
  capabilities,
@@ -8015,6 +8421,7 @@ function SandboxWorkspace(props) {
8015
8421
  onNotify,
8016
8422
  collapsed,
8017
8423
  onCollapsedChange,
8424
+ showCollapseControl,
8018
8425
  mobileLeadingControl,
8019
8426
  autoSaveId,
8020
8427
  defaultSize,
@@ -8025,6 +8432,8 @@ function SandboxWorkspace(props) {
8025
8432
  const [storedSelection, setStoredSelection] = useState20(null);
8026
8433
  const [requestedFile, setRequestedFile] = useState20(null);
8027
8434
  const nextFileRequestId = useRef20(0);
8435
+ const selectedTab = storedSelection?.sessionId === sessionId ? storedSelection.tab : null;
8436
+ const activeTabHint = selectedTab ?? initialTab ?? leadingTabs?.[0]?.id ?? null;
8028
8437
  const openFile = useCallback22(
8029
8438
  (path) => {
8030
8439
  nextFileRequestId.current += 1;
@@ -8044,13 +8453,13 @@ function SandboxWorkspace(props) {
8044
8453
  events,
8045
8454
  ...surfaces ? { surfaces } : {},
8046
8455
  ...initialTab ? { initialTab } : {},
8456
+ activeTab: activeTabHint,
8047
8457
  ...onNotify ? { onNotify } : {},
8048
8458
  requestedFilePath: requestedFile?.sessionId === sessionId ? requestedFile.path : null,
8049
8459
  requestedFileRequestId: requestedFile?.sessionId === sessionId ? requestedFile.requestId : null,
8050
8460
  onOpenFile: openFile
8051
8461
  });
8052
8462
  const tabs = [...leadingTabs ?? [], ...workbenchTabs, ...trailingTabs ?? []];
8053
- const selectedTab = storedSelection?.sessionId === sessionId ? storedSelection.tab : null;
8054
8463
  const preferredTab = selectedTab ?? defaultTab;
8055
8464
  const activeTab = preferredTab && tabs.some((tab) => tab.id === preferredTab) ? preferredTab : tabs[0]?.id;
8056
8465
  const selectTab = useCallback22(
@@ -8065,19 +8474,12 @@ function SandboxWorkspace(props) {
8065
8474
  ...activeTab !== void 0 ? { activeTab } : {},
8066
8475
  onActiveTabChange: selectTab,
8067
8476
  ...machine.enabled ? {
8068
- headerAccessory: /* @__PURE__ */ jsx13(
8069
- MachineStateChip,
8070
- {
8071
- chip: machine.chip,
8072
- activeMachine: machine.activeMachine,
8073
- error: machine.error,
8074
- onRetry: () => void machine.refresh()
8075
- }
8076
- )
8477
+ headerAccessory: /* @__PURE__ */ jsx13(MachineStateChip, { chip: machine.chip })
8077
8478
  } : {},
8078
8479
  ...mobileLeadingControl !== void 0 ? { mobileLeadingControl } : {},
8079
8480
  ...collapsed !== void 0 ? { collapsed } : {},
8080
8481
  ...onCollapsedChange ? { onCollapsedChange } : {},
8482
+ ...showCollapseControl !== void 0 ? { showCollapseControl } : {},
8081
8483
  ...autoSaveId !== void 0 ? { autoSaveId } : {},
8082
8484
  ...defaultSize !== void 0 ? { defaultSize } : {},
8083
8485
  ...minSize !== void 0 ? { minSize } : {},
@@ -8089,94 +8491,30 @@ function SandboxWorkspace(props) {
8089
8491
  function DirtyBadge({ count }) {
8090
8492
  return /* @__PURE__ */ jsx13("span", { className: "rounded-og-xs bg-og-accent-soft px-1 text-og-xs text-og-fg-muted", children: count });
8091
8493
  }
8092
- function MachineKindIcon({
8093
- kind,
8094
- className
8095
- }) {
8096
- const Icon = kind === "selfhosted" ? LaptopIcon : CpuIcon;
8097
- return /* @__PURE__ */ jsx13(Icon, { className: cn("size-3.5 shrink-0 text-og-fg-subtle", className), "aria-hidden": true });
8098
- }
8099
8494
  function chipDotClass(state) {
8100
8495
  if (state === "live") return "bg-og-status-running";
8101
8496
  if (state === "waking") return "bg-og-status-idle animate-pulse motion-reduce:animate-none";
8102
8497
  return "bg-og-fg-subtle";
8103
8498
  }
8104
- function MachineStateChip({
8105
- chip,
8106
- activeMachine,
8107
- error,
8108
- onRetry
8109
- }) {
8110
- const triggerRef = useRef20(null);
8111
- const portalStyle = usePortalTokenStyle(triggerRef);
8112
- return /* @__PURE__ */ jsxs11(Popover.Root, { children: [
8113
- /* @__PURE__ */ jsx13(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs11(
8114
- "button",
8115
- {
8116
- ref: triggerRef,
8117
- type: "button",
8118
- "aria-label": `Machine: ${chip.label}`,
8119
- className: "inline-flex min-h-7 items-center gap-1.5 rounded-og-sm px-2 py-1 text-og-xs font-medium text-og-fg-muted transition-colors hover:bg-og-surface-2 hover:text-og-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 pointer-coarse:min-h-11",
8120
- children: [
8121
- /* @__PURE__ */ jsx13(
8122
- "span",
8123
- {
8124
- className: cn("size-1.5 shrink-0 rounded-full", chipDotClass(chip.state)),
8125
- "aria-hidden": true
8126
- }
8127
- ),
8128
- /* @__PURE__ */ jsx13("span", { className: "max-w-[11rem] truncate", children: chip.label })
8129
- ]
8130
- }
8131
- ) }),
8132
- /* @__PURE__ */ jsx13(Popover.Portal, { forceMount: true, children: /* @__PURE__ */ jsxs11(
8133
- Popover.Content,
8134
- {
8135
- forceMount: true,
8136
- "data-machine-state-popover": true,
8137
- align: "end",
8138
- sideOffset: 6,
8139
- collisionPadding: 8,
8140
- className: "og-root z-50 w-64 max-w-[calc(100vw-1rem)] rounded-og-md border border-og-border bg-og-surface-1 p-3 text-og-sm text-og-fg shadow-lg outline-none",
8141
- style: portalStyle,
8142
- children: [
8143
- /* @__PURE__ */ jsxs11("div", { className: "flex min-w-0 items-center gap-1.5", children: [
8144
- /* @__PURE__ */ jsx13(MachineKindIcon, { kind: activeMachine?.kind }),
8145
- /* @__PURE__ */ jsx13("span", { className: "min-w-0 truncate font-medium", children: activeMachine?.name ?? "Sandbox" })
8146
- ] }),
8147
- /* @__PURE__ */ jsxs11("div", { className: "mt-2.5 flex items-center justify-between gap-2", children: [
8148
- /* @__PURE__ */ jsx13("span", { className: "text-og-xs text-og-fg-subtle", children: "Connection" }),
8149
- activeMachine ? /* @__PURE__ */ jsx13(
8150
- ConnectionStatusPill,
8151
- {
8152
- status: connectionStatusForState(activeMachine.state),
8153
- size: "sm"
8154
- }
8155
- ) : /* @__PURE__ */ jsx13("span", { className: "text-og-xs text-og-fg-muted", children: chip.label })
8156
- ] }),
8157
- chip.asOf ? /* @__PURE__ */ jsxs11("p", { className: "mt-2.5 text-og-xs leading-4 text-og-fg-subtle", children: [
8158
- "Workspace shown as of ",
8159
- formatAsOf(chip.asOf, Date.now()),
8160
- " \u2014 the machine is not live."
8161
- ] }) : null,
8162
- activeMachine && activeMachine.sharedSessionCount > 1 ? /* @__PURE__ */ jsx13("div", { className: "mt-2.5", children: /* @__PURE__ */ jsx13(
8163
- SharedMachineDisclosure,
8164
- {
8165
- sharedSessionCount: activeMachine.sharedSessionCount,
8166
- density: "full"
8167
- }
8168
- ) }) : null,
8169
- error ? /* @__PURE__ */ jsxs11("div", { className: "mt-2.5 space-y-1.5 border-t border-og-border pt-2.5", children: [
8170
- /* @__PURE__ */ jsx13("p", { className: "text-og-xs leading-4 text-og-danger", children: "Couldn't reach the sandbox for this session." }),
8171
- /* @__PURE__ */ jsxs11(DockActionButton, { onClick: onRetry, children: [
8172
- /* @__PURE__ */ jsx13(RefreshCwIcon2, { className: "size-3" }),
8173
- "Retry"
8174
- ] })
8175
- ] }) : null
8176
- ]
8177
- }
8178
- ) })
8179
- ] });
8499
+ function MachineStateChip({ chip }) {
8500
+ return /* @__PURE__ */ jsxs11(
8501
+ "div",
8502
+ {
8503
+ role: "status",
8504
+ "aria-label": `Machine: ${chip.label}`,
8505
+ className: "inline-flex min-h-7 items-center gap-1.5 px-2 py-1 text-og-xs font-medium text-og-fg-muted max-[1023px]:min-h-11 pointer-coarse:min-h-11",
8506
+ children: [
8507
+ /* @__PURE__ */ jsx13(
8508
+ "span",
8509
+ {
8510
+ className: cn("size-1.5 shrink-0 rounded-full", chipDotClass(chip.state)),
8511
+ "aria-hidden": true
8512
+ }
8513
+ ),
8514
+ /* @__PURE__ */ jsx13("span", { className: "max-w-[11rem] truncate", children: chip.label })
8515
+ ]
8516
+ }
8517
+ );
8180
8518
  }
8181
8519
  function DockActionButton({ onClick, children }) {
8182
8520
  return /* @__PURE__ */ jsx13(
@@ -8184,13 +8522,41 @@ function DockActionButton({ onClick, children }) {
8184
8522
  {
8185
8523
  type: "button",
8186
8524
  onClick,
8187
- className: "inline-flex items-center gap-1.5 rounded-og-sm border border-og-border px-2 py-1 text-og-xs font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:text-og-fg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 pointer-coarse:min-h-11",
8525
+ className: "inline-flex items-center gap-1.5 rounded-og-sm border border-og-border px-2 py-1 text-og-xs font-medium text-og-fg-muted transition-colors hover:border-og-border-strong hover:text-og-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 pointer-coarse:min-h-11",
8188
8526
  children
8189
8527
  }
8190
8528
  );
8191
8529
  }
8192
8530
  function ChangesTabBody({
8531
+ comparison,
8532
+ onComparisonChange,
8533
+ ...props
8534
+ }) {
8535
+ return /* @__PURE__ */ jsxs11("div", { className: "flex h-full min-h-0 flex-col", children: [
8536
+ /* @__PURE__ */ jsx13("div", { className: "flex shrink-0 items-center gap-1 border-b border-og-border bg-og-surface-1 px-2 py-1.5", children: [
8537
+ ["branch", "Branch"],
8538
+ ["working", "Uncommitted"],
8539
+ ["staged", "Staged"]
8540
+ ].map(([value, label]) => /* @__PURE__ */ jsx13(
8541
+ "button",
8542
+ {
8543
+ type: "button",
8544
+ "aria-pressed": comparison === value,
8545
+ onClick: () => onComparisonChange(value),
8546
+ className: cn(
8547
+ "rounded-og-sm px-2 py-1 text-og-xs font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent max-[1023px]:min-h-11 pointer-coarse:min-h-11",
8548
+ comparison === value ? "bg-og-surface-2 text-og-fg shadow-sm" : "text-og-fg-muted hover:bg-og-surface-2 hover:text-og-fg"
8549
+ ),
8550
+ children: label
8551
+ },
8552
+ value
8553
+ )) }),
8554
+ /* @__PURE__ */ jsx13("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx13(ChangesTabContent, { comparison, ...props }) })
8555
+ ] });
8556
+ }
8557
+ function ChangesTabContent({
8193
8558
  git,
8559
+ comparison,
8194
8560
  captureAvailable,
8195
8561
  captureRevision,
8196
8562
  capturePending,
@@ -8221,7 +8587,7 @@ function ChangesTabBody({
8221
8587
  type: "button",
8222
8588
  onClick: () => void git.refresh(),
8223
8589
  disabled: git.loading,
8224
- className: "inline-flex min-h-7 shrink-0 items-center gap-1 rounded-og-sm border border-og-border bg-og-surface-1 px-2 font-medium text-og-fg transition-colors hover:border-og-border-strong focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-og-accent disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:min-h-11",
8590
+ className: "inline-flex min-h-7 shrink-0 items-center gap-1 rounded-og-sm border border-og-border bg-og-surface-1 px-2 font-medium text-og-fg transition-colors hover:border-og-border-strong focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-og-accent disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:min-h-11",
8225
8591
  children: [
8226
8592
  /* @__PURE__ */ jsx13(
8227
8593
  RefreshCwIcon2,
@@ -8277,7 +8643,7 @@ function ChangesTabBody({
8277
8643
  }
8278
8644
  );
8279
8645
  }
8280
- if (!liveWorkspaceExpected && !captureAvailable) {
8646
+ if (!liveWorkspaceExpected && (comparison !== "working" || !captureAvailable)) {
8281
8647
  return /* @__PURE__ */ jsxs11(
8282
8648
  CenteredState,
8283
8649
  {
@@ -8290,7 +8656,7 @@ function ChangesTabBody({
8290
8656
  ) : /* @__PURE__ */ jsx13(CpuIcon, { className: "size-5", "aria-hidden": true }),
8291
8657
  children: [
8292
8658
  /* @__PURE__ */ jsx13("p", { className: "text-og-sm font-medium text-og-fg", children: workspaceWaking ? "Waking workspace" : "Workspace is resting" }),
8293
- /* @__PURE__ */ jsx13("p", { className: "text-og-sm leading-5 text-og-fg-subtle", children: workspaceWaking ? "Connecting to the live working tree\u2026" : "No captured revision is available yet. Wake the sandbox to inspect current changes." }),
8659
+ /* @__PURE__ */ jsx13("p", { className: "text-og-sm leading-5 text-og-fg-subtle", children: workspaceWaking ? "Connecting to the live working tree\u2026" : comparison === "branch" ? "Wake the sandbox to compare this branch with the remote default branch." : comparison === "staged" ? "Wake the sandbox to inspect staged changes." : "No captured revision is available yet. Wake the sandbox to inspect uncommitted changes." }),
8294
8660
  !workspaceWaking ? /* @__PURE__ */ jsxs11(DockActionButton, { onClick: onWake, children: [
8295
8661
  /* @__PURE__ */ jsx13(CpuIcon, { className: "size-3" }),
8296
8662
  "Open live workspace"
@@ -8318,8 +8684,8 @@ function ChangesTabBody({
8318
8684
  );
8319
8685
  }
8320
8686
  return /* @__PURE__ */ jsxs11(CenteredState, { icon: /* @__PURE__ */ jsx13(CircleCheckIcon, { className: "size-5", "aria-hidden": true }), tone: "success", children: [
8321
- /* @__PURE__ */ jsx13("p", { className: "text-og-sm font-medium text-og-fg", children: "Working tree is clean" }),
8322
- /* @__PURE__ */ jsx13("p", { className: "text-og-sm leading-5 text-og-fg-subtle", children: "File edits from future turns will appear here." })
8687
+ /* @__PURE__ */ jsx13("p", { className: "text-og-sm font-medium text-og-fg", children: comparison === "branch" ? "No branch changes" : comparison === "staged" ? "No staged changes" : "No uncommitted changes" }),
8688
+ /* @__PURE__ */ jsx13("p", { className: "text-og-sm leading-5 text-og-fg-subtle", children: comparison === "branch" ? "This branch matches the remote default branch." : comparison === "staged" ? "Stage files to review the next commit here." : "Local edits not yet committed will appear here." })
8323
8689
  ] });
8324
8690
  }
8325
8691
  function CenteredState({
@@ -8356,7 +8722,7 @@ function CenteredState({
8356
8722
  // src/hooks/use-codex-accounts.ts
8357
8723
  import { useCallback as useCallback23, useState as useState21 } from "react";
8358
8724
  function isCodexAccountEvent(event) {
8359
- return event.type === "codex.account.switched" || event.type === "turn.started";
8725
+ return event.type === "codex.account.switched";
8360
8726
  }
8361
8727
  var EMPTY_SETTINGS = {
8362
8728
  rotationEnabled: false,
@@ -8636,7 +9002,6 @@ export {
8636
9002
  projectPendingHumanInputRequests,
8637
9003
  projectPickerRows,
8638
9004
  rawTypeOf,
8639
- redactSecrets,
8640
9005
  runnableLatencyModesForModel,
8641
9006
  sandboxCommandExitCode,
8642
9007
  sessionChromeGoalPillState,
@@ -8671,6 +9036,7 @@ export {
8671
9036
  useOpenGeni,
8672
9037
  useOpenGeniClient,
8673
9038
  usePacks,
9039
+ usePageLiveActivity,
8674
9040
  useRelayFrameStream,
8675
9041
  useRig,
8676
9042
  useRigChanges,