@opengeni/react 0.12.0 → 0.13.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 (85) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-5TIXRCSI.js → chunk-NFYVQWIB.js} +150 -64
  3. package/dist/chunk-NFYVQWIB.js.map +1 -0
  4. package/dist/index.d.ts +760 -142
  5. package/dist/index.js +5345 -2146
  6. package/dist/index.js.map +1 -1
  7. package/dist/{machines-BeZ3bD3t.d.ts → machines-CnlMb7E-.d.ts} +5 -5
  8. package/dist/machines.d.ts +1 -1
  9. package/dist/machines.js +1 -1
  10. package/package.json +17 -12
  11. package/src/approvals.ts +16 -4
  12. package/src/client.ts +32 -5
  13. package/src/commands/index.ts +1 -7
  14. package/src/commands/registry.ts +46 -12
  15. package/src/components/chat-composer.tsx +135 -39
  16. package/src/components/code-editor.tsx +28 -36
  17. package/src/components/command-palette.tsx +26 -5
  18. package/src/components/desktop-viewer.tsx +29 -16
  19. package/src/components/diff-view.tsx +27 -189
  20. package/src/components/enrollment-consent.tsx +28 -10
  21. package/src/components/enrollment-device-flow.tsx +8 -5
  22. package/src/components/file-browser.tsx +190 -56
  23. package/src/components/fleet-tile.tsx +13 -4
  24. package/src/components/machine-card.tsx +16 -4
  25. package/src/components/machine-dock-bar.tsx +20 -8
  26. package/src/components/machine-metrics.tsx +31 -8
  27. package/src/components/machine-status-pill.tsx +26 -5
  28. package/src/components/machines-dashboard.tsx +8 -2
  29. package/src/components/markdown.tsx +39 -9
  30. package/src/components/message-timeline.tsx +633 -109
  31. package/src/components/pierre-diff.tsx +83 -15
  32. package/src/components/pierre-file.tsx +10 -9
  33. package/src/components/sandbox-files.tsx +110 -227
  34. package/src/components/sandbox-terminal.tsx +256 -88
  35. package/src/components/sandbox-workspace.tsx +823 -0
  36. package/src/components/session-status.tsx +28 -2
  37. package/src/components/workbench-changes.tsx +541 -0
  38. package/src/components/workspace-dock.tsx +85 -22
  39. package/src/hooks/internal.ts +5 -2
  40. package/src/hooks/use-available-models.ts +7 -2
  41. package/src/hooks/use-billing-usage.ts +4 -1
  42. package/src/hooks/use-codex-accounts.ts +74 -44
  43. package/src/hooks/use-composer.ts +57 -36
  44. package/src/hooks/use-environments.ts +92 -47
  45. package/src/hooks/use-file-attachments.ts +101 -55
  46. package/src/hooks/use-goal.ts +52 -16
  47. package/src/hooks/use-machine-chip.ts +134 -0
  48. package/src/hooks/use-machines.ts +34 -16
  49. package/src/hooks/use-packs.ts +24 -19
  50. package/src/hooks/use-relay-frame-stream.ts +5 -2
  51. package/src/hooks/use-rigs.ts +335 -0
  52. package/src/hooks/use-sandbox-files.ts +213 -39
  53. package/src/hooks/use-sandbox-git.ts +188 -11
  54. package/src/hooks/use-sandbox-terminal.ts +18 -14
  55. package/src/hooks/use-scheduled-tasks.ts +10 -2
  56. package/src/hooks/use-session-capabilities.ts +77 -20
  57. package/src/hooks/use-session-control.ts +49 -21
  58. package/src/hooks/use-session-events.ts +48 -20
  59. package/src/hooks/use-session-lineage.ts +119 -0
  60. package/src/hooks/use-session.ts +43 -28
  61. package/src/hooks/use-slash-commands.ts +6 -4
  62. package/src/hooks/use-turn-queue.ts +74 -147
  63. package/src/hooks/use-windowed-sections.ts +204 -0
  64. package/src/hooks/use-workspace-capture.ts +233 -0
  65. package/src/hooks/use-workspace-edit.ts +231 -0
  66. package/src/hooks/use-workspace-sessions.ts +48 -5
  67. package/src/hooks/use-workspaces.ts +18 -15
  68. package/src/index.ts +127 -23
  69. package/src/lib/format.ts +3 -3
  70. package/src/lib/xterm-renderer.ts +65 -0
  71. package/src/lib/xterm-theme.ts +224 -18
  72. package/src/machines.ts +13 -3
  73. package/src/provider.tsx +3 -1
  74. package/src/timeline/activity-rail.tsx +213 -54
  75. package/src/timeline/disclosure-context.tsx +12 -2
  76. package/src/timeline/index.ts +21 -2
  77. package/src/timeline/parsers.ts +44 -10
  78. package/src/timeline/projection.ts +388 -85
  79. package/src/timeline/shared.tsx +111 -24
  80. package/src/timeline/tool-diff.tsx +24 -20
  81. package/src/timeline/tool-renderers.tsx +98 -21
  82. package/src/timeline/turn-summary.tsx +76 -23
  83. package/src/timeline/types.ts +96 -12
  84. package/styles/tokens.css +2 -2
  85. package/dist/chunk-5TIXRCSI.js.map +0 -1
@@ -1,85 +1,60 @@
1
- import type { FsReadResponse, GitFileDiff } from "@opengeni/sdk";
2
- import { FileIcon } from "lucide-react";
3
- import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
1
+ import type { FsReadResponse } from "@opengeni/sdk";
2
+ import { type ReactNode, useCallback, useEffect, useRef, useState } from "react";
4
3
  import { cn } from "../lib/cn";
5
4
  import { useThemeType } from "../lib/use-theme-type";
6
5
  import type { UseSandboxFilesResult } from "../hooks/use-sandbox-files";
7
6
  import type { UseSandboxGitResult } from "../hooks/use-sandbox-git";
8
7
  import { CodeEditor } from "./code-editor";
9
8
  import { FileBrowser } from "./file-browser";
10
- import { DiffView } from "./diff-view";
11
- import { PierreDiff } from "./pierre-diff";
12
9
  import { PierreFile } from "./pierre-file";
13
10
 
14
11
  export type SandboxFilesProps = {
15
12
  /** From `useSandboxFiles(...)`. */
16
13
  files: UseSandboxFilesResult;
17
- /** From `useSandboxGit(...)` — the working-tree diff. */
14
+ /** From `useSandboxGit(...)` — drives the branch/dirty header only. */
18
15
  git: UseSandboxGitResult;
19
- /** From a second `useSandboxGit(..., { staged: true })` the staged diff. */
16
+ /** @deprecated Diffs live in the dedicated Changes tab now; accepted for
17
+ * source-compat but unused (the Files surface is a browser + viewer). */
20
18
  stagedGit?: UseSandboxGitResult | undefined;
21
19
  /** Whether a FileSystem surface is advertised (drives the unavailable notice). */
22
20
  fileSystemAvailable?: boolean | undefined;
23
- /** Use Pierre's Shiki-highlighted diff (default true; falls back to built-in). */
21
+ /** Use Pierre's Shiki highlighter for the viewer (default true; plain fallback). */
24
22
  usePierre?: boolean | undefined;
25
23
  /** Allow in-place editing of tree files (CodeMirror). Default true. When false
26
24
  * the surface is review-only: every text file opens in the read-only viewer. */
27
25
  editable?: boolean | undefined;
26
+ /** Fired once when the user first edits an open file (wake-on-edit intent). The
27
+ * dock warms the box on this so the save lands fast; opening/reading never fires
28
+ * it. Browsing the tree/diff must not warm a box. */
29
+ onEditIntent?: (() => void) | undefined;
28
30
  themeType?: "dark" | "light" | undefined;
29
31
  className?: string | undefined;
30
32
  };
31
33
 
32
- const STATUS_TINT: Record<GitFileDiff["status"], string> = {
33
- added: "text-og-status-idle",
34
- modified: "text-og-status-running",
35
- deleted: "text-og-status-failed",
36
- renamed: "text-og-accent",
37
- copied: "text-og-accent",
38
- untracked: "text-og-fg-subtle",
39
- ignored: "text-og-fg-subtle",
40
- conflicted: "text-og-status-failed",
41
- typechange: "text-og-status-running",
42
- };
43
-
44
- const STATUS_LETTER: Record<GitFileDiff["status"], string> = {
45
- added: "A",
46
- modified: "M",
47
- deleted: "D",
48
- renamed: "R",
49
- copied: "C",
50
- untracked: "U",
51
- ignored: "I",
52
- conflicted: "!",
53
- typechange: "T",
54
- };
55
-
56
34
  /**
57
- * The review-first Files surface: a sticky branch/dirty header, a "Changes"
58
- * group (changed files vs HEAD, badged), the full lazy file tree for context,
59
- * and an inline diff pane below for the selected changed file. The agent
60
- * commits; the human reviews there is no stage/commit/push UI here (power-git
61
- * lives in the terminal).
35
+ * The Files surface: a branch/dirty header, the full lazy file tree, and a
36
+ * viewer/editor pane for the selected file. This is the workspace BROWSER pick
37
+ * a file to read or edit it. Diff review lives in the dedicated Changes tab (this
38
+ * surface deliberately does NOT replicate the changed-files list, and does not
39
+ * diff here one job per tab). The agent commits; the human reviews.
62
40
  */
63
41
  export function SandboxFiles({
64
42
  files,
65
43
  git,
66
- stagedGit,
67
44
  fileSystemAvailable = true,
68
45
  usePierre = true,
69
46
  editable = true,
47
+ onEditIntent,
70
48
  themeType,
71
49
  className,
72
50
  }: SandboxFilesProps) {
73
51
  const [selected, setSelected] = useState<string | null>(null);
74
- const [staged, setStaged] = useState(false);
75
- const [layout, setLayout] = useState<"unified" | "split">("unified");
76
- // View vs Edit for the read-only-viewer branch (a tree file that is NOT a
77
- // diff). Resets to View on every new selection so opening a file never lands
78
- // you in a stale dirty editor for a different path.
52
+ // View vs Edit for the selected file. Resets to View on every new selection so
53
+ // opening a file never lands you in a stale dirty editor for a different path.
79
54
  const [editMode, setEditMode] = useState(false);
80
55
 
81
- // Side-by-side (tree left, diff right) once the surface is wide enough;
82
- // stacked (tree over diff) on a narrow dock. Tracked off the container so it
56
+ // Side-by-side (tree left, viewer right) once the surface is wide enough;
57
+ // stacked (tree over viewer) on a narrow dock. Tracked off the container so it
83
58
  // reacts to the dock resize, not just the viewport.
84
59
  const rootRef = useRef<HTMLDivElement | null>(null);
85
60
  const [wide, setWide] = useState(false);
@@ -94,42 +69,27 @@ export function SandboxFiles({
94
69
  return () => observer.disconnect();
95
70
  }, []);
96
71
 
97
- // Resolve the effective diff theme from the host palette (the `data-og-theme`
98
- // attribute the demo/app sets), defaulting to dark, unless the caller forced
99
- // one. Keeps the Pierre diff dark-on-dark instead of a white slab.
72
+ // Resolve the effective viewer theme from the host palette (the `data-og-theme`
73
+ // attribute the demo/app sets), defaulting to dark, unless the caller forced one.
100
74
  const resolvedTheme = useThemeType(themeType);
101
75
 
102
- const activeGit = staged && stagedGit ? stagedGit : git;
103
- const changed = activeGit.diff;
104
- const changedPaths = useMemo(() => new Set(changed.map((f) => f.path)), [changed]);
105
-
106
- // The selection is EITHER a changed file (-> diff pane) or any tree file
107
- // (-> read-only viewer pane). An explicit pick wins; otherwise default to the
108
- // first changed file so a review-first dock opens on a diff. Crucially this is
109
- // NOT gated on a repo: with no changes (or no repo) the pane simply waits for a
110
- // tree click, and a clicked tree file always resolves to the viewer.
111
- const effectiveSelected = selected ?? changed[0]?.path ?? null;
112
- const selectedDiff = changed.find((f) => f.path === effectiveSelected) ?? null;
113
- // A selected path that is not a changed file is viewed (fs.read), not diffed.
114
- const viewPath = effectiveSelected && !selectedDiff ? effectiveSelected : null;
115
-
116
- // Read the selected file's contents for the viewer pane (text/base64). The
117
- // hook caps size + flags binary; we surface a notice for binary rather than
118
- // dumping base64 into the highlighter.
76
+ // The selected tree file opens in the viewer (read-only) or the editor (when
77
+ // writable). Nothing is auto-selected — the pane waits for a tree click, so the
78
+ // Files tab opens as a calm browser, not a diff.
79
+ const viewPath = selected;
119
80
  const fileView = useFileView(viewPath, files.readFile);
120
81
 
121
- // Selecting a (different) file always returns to View — never drop the user
122
- // into an editor whose buffer belongs to the previously-selected path.
82
+ // Selecting a (different) file always returns to View — never drop the user into
83
+ // an editor whose buffer belongs to the previously-selected path.
123
84
  const selectFile = useCallback((path: string) => {
124
85
  setSelected(path);
125
86
  setEditMode(false);
126
87
  }, []);
127
88
 
128
89
  // A tree file is editable only when it is a real, fully-loaded text file: not
129
- // binary (would corrupt on save) and not truncated (we only hold a PREFIX, so
130
- // a save would write the prefix back and lose the tail). The editor is then
131
- // additionally gated on the `editable` prop. Anything failing this opens
132
- // read-only in the viewer.
90
+ // binary (would corrupt on save) and not truncated (we only hold a PREFIX). The
91
+ // editor is then additionally gated on the `editable` prop; anything failing this
92
+ // opens read-only in the viewer.
133
93
  const canEdit =
134
94
  editable &&
135
95
  viewPath !== null &&
@@ -141,152 +101,62 @@ export function SandboxFiles({
141
101
  const showEditor = canEdit && editMode;
142
102
 
143
103
  if (!fileSystemAvailable) {
144
- return (
145
- <Notice className={className}>This sandbox does not expose a file system.</Notice>
146
- );
104
+ return <Notice className={className}>This sandbox does not expose a file system.</Notice>;
147
105
  }
148
106
 
149
107
  return (
150
108
  <div ref={rootRef} className={cn("flex h-full min-h-0 min-w-0 flex-col", className)}>
151
- {/* Branch + dirty header */}
152
- <GitHeader git={git} dirtyCount={changedPaths.size} />
109
+ {/* Branch + dirty header (context; NOT the changed-files list). */}
110
+ <GitHeader git={git} dirtyCount={git.diff.length} />
153
111
 
154
112
  <div className={cn("flex min-h-0 flex-1", wide ? "flex-row" : "flex-col")}>
155
- {/* Tree pane: Changes group + full tree. A fixed left column when wide,
156
- a top band when narrow. */}
113
+ {/* Tree pane: the full lazy file tree. A fixed left column when wide, a top
114
+ band when narrow. */}
157
115
  <div
158
116
  className={cn(
159
- "min-h-0 overflow-auto",
160
- wide
161
- ? "w-[280px] shrink-0 border-r border-og-border"
162
- : "flex-1",
117
+ "flex min-h-0 flex-col",
118
+ wide ? "w-[280px] shrink-0 border-r border-og-border" : "flex-1",
163
119
  )}
164
120
  >
165
- {changed.length > 0 && (
166
- <div className="border-b border-og-border">
167
- <div className="px-2 py-1 text-og-xs font-medium uppercase tracking-wide text-og-fg-subtle">
168
- Changes · {changed.length}
169
- </div>
170
- <ul className="pb-1">
171
- {changed.map((file) => (
172
- <li key={file.path}>
173
- <button
174
- type="button"
175
- onClick={() => selectFile(file.path)}
176
- className={cn(
177
- "flex w-full items-center gap-1.5 truncate px-2 py-0.5 text-left text-og-sm hover:bg-og-surface-2 pointer-coarse:min-h-10",
178
- file.path === effectiveSelected && "bg-og-surface-2",
179
- )}
180
- >
181
- <span
182
- className={cn(
183
- "w-3 shrink-0 text-center font-og-mono text-og-xs",
184
- STATUS_TINT[file.status],
185
- )}
186
- >
187
- {STATUS_LETTER[file.status]}
188
- </span>
189
- <FileIcon className="size-3.5 shrink-0 opacity-70" />
190
- <span className="truncate">{file.path}</span>
191
- <span className="ml-auto flex shrink-0 items-center gap-1.5 pl-2 text-og-xs">
192
- <span className="text-og-status-idle">
193
- +{file.additions}
194
- </span>
195
- <span className="text-og-status-failed">
196
- −{file.deletions}
197
- </span>
198
- </span>
199
- </button>
200
- </li>
201
- ))}
202
- </ul>
203
- </div>
204
- )}
205
-
206
- <div className="px-2 py-1 text-og-xs font-medium uppercase tracking-wide text-og-fg-subtle">
207
- Files
208
- </div>
209
121
  <FileBrowser
210
122
  result={files}
211
- selectedPath={effectiveSelected ?? undefined}
123
+ selectedPath={selected ?? undefined}
212
124
  onSelectFile={selectFile}
125
+ editable={editable}
213
126
  emptyState="This directory is empty"
214
- className="min-w-0"
127
+ className="min-w-0 flex-1"
215
128
  />
216
129
  </div>
217
130
 
218
- {/* Diff pane: the selected file's diff. Fills the remaining width when
219
- side-by-side, sits below the tree when stacked. */}
131
+ {/* Viewer pane: the selected file's contents (read-only) or the editor.
132
+ Fills the remaining width when side-by-side, sits below the tree when
133
+ stacked. */}
220
134
  <div
221
135
  className={cn(
222
136
  "flex min-h-0 min-w-0 flex-col",
223
- wide
224
- ? "flex-1"
225
- : "flex-[1.4] border-t border-og-border",
137
+ wide ? "flex-1" : "flex-[1.4] border-t border-og-border",
226
138
  )}
227
139
  >
228
140
  <div className="flex shrink-0 items-center justify-between gap-2 border-b border-og-border bg-og-surface-1 px-2 py-1">
229
141
  <span className="min-w-0 truncate font-og-mono text-og-xs text-og-fg-muted">
230
- {effectiveSelected ?? "No file selected"}
142
+ {selected ?? "No file selected"}
231
143
  </span>
232
- <div className="flex min-w-0 shrink-0 flex-wrap items-center justify-end gap-1">
233
- {/* The Working/Staged + Unified/Split toggles only apply to a diff.
234
- Hide them in the read-only viewer (a non-changed tree file). */}
235
- {selectedDiff && stagedGit && (
236
- <Segmented
237
- options={[
238
- { value: "working", label: "Working tree" },
239
- { value: "staged", label: "Staged" },
240
- ]}
241
- value={staged ? "staged" : "working"}
242
- onChange={(v) => setStaged(v === "staged")}
243
- />
244
- )}
245
- {selectedDiff && (
246
- <Segmented
247
- options={[
248
- { value: "unified", label: "Unified" },
249
- { value: "split", label: "Split" },
250
- ]}
251
- value={layout}
252
- onChange={(v) => setLayout(v as "unified" | "split")}
253
- />
254
- )}
255
- {/* View/Edit toggle — only for a real, fully-loaded text file the
256
- editor can safely round-trip. Binary/truncated files never get
257
- an Edit affordance (they'd corrupt on save). */}
258
- {!selectedDiff && canEdit && (
259
- <Segmented
260
- options={[
261
- { value: "view", label: "View" },
262
- { value: "edit", label: "Edit" },
263
- ]}
264
- value={editMode ? "edit" : "view"}
265
- onChange={(v) => setEditMode(v === "edit")}
266
- />
267
- )}
268
- </div>
144
+ {/* View/Edit toggle only for a real, fully-loaded text file the editor
145
+ can safely round-trip. Binary/truncated/read-only files never get an
146
+ Edit affordance (they'd corrupt on save or can't be written). */}
147
+ {canEdit && (
148
+ <Segmented
149
+ options={[
150
+ { value: "view", label: "View" },
151
+ { value: "edit", label: "Edit" },
152
+ ]}
153
+ value={editMode ? "edit" : "view"}
154
+ onChange={(v) => setEditMode(v === "edit")}
155
+ />
156
+ )}
269
157
  </div>
270
158
  <div className="min-h-0 flex-1 overflow-auto">
271
- {selectedDiff ? (
272
- // A changed file -> diff pane (HEAD vs working/staged).
273
- usePierre ? (
274
- <PierreDiff
275
- diff={[selectedDiff]}
276
- layout={layout}
277
- themeType={resolvedTheme}
278
- fallback={
279
- <DiffView diff={[selectedDiff]} layout={layout} isRepo={git.isRepo} className="p-1" />
280
- }
281
- className="p-1"
282
- />
283
- ) : (
284
- <DiffView diff={[selectedDiff]} layout={layout} isRepo={git.isRepo} className="p-1" />
285
- )
286
- ) : viewPath ? (
287
- // Any other tree file -> editable editor (Edit mode) or read-only
288
- // single-file viewer (fs.read). This works with NO repo — viewing
289
- // and editing files never requires git.
159
+ {viewPath ? (
290
160
  showEditor && fileView.content !== null ? (
291
161
  <CodeEditor
292
162
  key={viewPath}
@@ -294,50 +164,51 @@ export function SandboxFiles({
294
164
  initialContents={fileView.content}
295
165
  themeType={resolvedTheme}
296
166
  onSave={(contents) => files.writeFile(viewPath, contents)}
167
+ {...(onEditIntent ? { onEditIntent } : {})}
297
168
  className="h-full"
298
169
  />
299
170
  ) : fileView.error ? (
300
- <Notice>Could not open {viewPath}: {fileView.error.message}</Notice>
171
+ <Notice>
172
+ Could not open {viewPath}: {fileView.error.message}
173
+ </Notice>
301
174
  ) : fileView.loading ? (
302
175
  <Notice>Loading {viewPath}…</Notice>
303
176
  ) : fileView.isBinary ? (
304
- <Notice>{viewPath} is a binary file ({fileView.sizeBytes ?? 0} bytes).</Notice>
177
+ <Notice>
178
+ {viewPath} is a binary file ({fileView.sizeBytes ?? 0} bytes).
179
+ </Notice>
305
180
  ) : fileView.content !== null ? (
306
181
  <>
307
- {fileView.truncated && (
308
- <div className="border-b border-og-border bg-og-surface-1 px-2 py-1 text-og-xs text-og-status-running">
309
- Large file — showing a truncated preview ({fileView.sizeBytes ?? 0} bytes loaded). Editing is disabled to avoid corrupting the file.
310
- </div>
311
- )}
312
- {usePierre ? (
313
- <PierreFile
314
- path={viewPath}
315
- contents={fileView.content}
316
- themeType={resolvedTheme}
317
- fallback={
318
- <pre className="overflow-auto whitespace-pre p-2 font-og-mono text-og-sm text-og-fg">
319
- {fileView.content}
320
- </pre>
321
- }
322
- className="p-1"
323
- />
324
- ) : (
325
- <pre className="overflow-auto whitespace-pre p-2 font-og-mono text-og-sm text-og-fg">
326
- {fileView.content}
327
- </pre>
328
- )}
182
+ {fileView.truncated && (
183
+ <div className="border-b border-og-border bg-og-surface-1 px-2 py-1 text-og-xs text-og-status-running">
184
+ Large file — showing a truncated preview ({fileView.sizeBytes ?? 0} bytes
185
+ loaded). Editing is disabled to avoid corrupting the file.
186
+ </div>
187
+ )}
188
+ {usePierre ? (
189
+ <PierreFile
190
+ path={viewPath}
191
+ contents={fileView.content}
192
+ themeType={resolvedTheme}
193
+ fallback={
194
+ <pre className="overflow-auto whitespace-pre p-2 font-og-mono text-og-sm text-og-fg">
195
+ {fileView.content}
196
+ </pre>
197
+ }
198
+ className="p-1"
199
+ />
200
+ ) : (
201
+ <pre className="overflow-auto whitespace-pre p-2 font-og-mono text-og-sm text-og-fg">
202
+ {fileView.content}
203
+ </pre>
204
+ )}
329
205
  </>
330
206
  ) : (
331
207
  <Notice>Loading {viewPath}…</Notice>
332
208
  )
333
209
  ) : (
334
- // Nothing selected — never claim a repo is required; the tree shows
335
- // the whole workspace regardless of git.
336
- <Notice>
337
- {changed.length > 0
338
- ? "Select a changed file to review its diff, or a file in the tree to view it."
339
- : "Select a file in the tree to view it."}
340
- </Notice>
210
+ // Nothing selected — the tree shows the whole workspace; pick a file.
211
+ <Notice>Select a file in the tree to view it.</Notice>
341
212
  )}
342
213
  </div>
343
214
  </div>
@@ -367,9 +238,7 @@ function GitHeader({ git, dirtyCount }: { git: UseSandboxGitResult; dirtyCount:
367
238
  </span>
368
239
  )}
369
240
  {dirty && (
370
- <span className="ml-auto shrink-0 text-og-xs text-og-fg-subtle">
371
- {dirtyCount} changed
372
- </span>
241
+ <span className="ml-auto shrink-0 text-og-xs text-og-fg-subtle">{dirtyCount} changed</span>
373
242
  )}
374
243
  </div>
375
244
  );
@@ -392,7 +261,7 @@ function Segmented({
392
261
  type="button"
393
262
  onClick={() => onChange(opt.value)}
394
263
  className={cn(
395
- "rounded-og-xs px-1.5 py-0.5 text-og-xs pointer-coarse:min-h-10",
264
+ "min-h-7 rounded-og-xs px-1.5 py-0.5 text-og-xs max-[1023px]:min-h-11 pointer-coarse:min-h-11",
396
265
  opt.value === value
397
266
  ? "bg-og-accent-soft text-og-fg"
398
267
  : "text-og-fg-subtle hover:text-og-fg",
@@ -437,11 +306,25 @@ function useFileView(
437
306
  });
438
307
  useEffect(() => {
439
308
  if (!path) {
440
- setState({ content: null, isBinary: false, truncated: false, sizeBytes: null, loading: false, error: null });
309
+ setState({
310
+ content: null,
311
+ isBinary: false,
312
+ truncated: false,
313
+ sizeBytes: null,
314
+ loading: false,
315
+ error: null,
316
+ });
441
317
  return;
442
318
  }
443
319
  let cancelled = false;
444
- setState({ content: null, isBinary: false, truncated: false, sizeBytes: null, loading: true, error: null });
320
+ setState({
321
+ content: null,
322
+ isBinary: false,
323
+ truncated: false,
324
+ sizeBytes: null,
325
+ loading: true,
326
+ error: null,
327
+ });
445
328
  void readFile(path)
446
329
  .then((res) => {
447
330
  if (cancelled) return;