@opengeni/react 0.11.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.
- package/README.md +1 -1
- package/dist/{chunk-5C7RGAWA.js → chunk-NFYVQWIB.js} +170 -64
- package/dist/chunk-NFYVQWIB.js.map +1 -0
- package/dist/index.d.ts +791 -145
- package/dist/index.js +5447 -2213
- package/dist/index.js.map +1 -1
- package/dist/{machines-BeZ3bD3t.d.ts → machines-CnlMb7E-.d.ts} +5 -5
- package/dist/machines.d.ts +1 -1
- package/dist/machines.js +1 -1
- package/package.json +17 -12
- package/src/approvals.ts +16 -4
- package/src/client.ts +32 -5
- package/src/commands/index.ts +1 -7
- package/src/commands/registry.ts +46 -12
- package/src/components/chat-composer.tsx +135 -39
- package/src/components/code-editor.tsx +28 -36
- package/src/components/command-palette.tsx +26 -5
- package/src/components/desktop-viewer.tsx +29 -16
- package/src/components/diff-view.tsx +27 -189
- package/src/components/enrollment-consent.tsx +28 -10
- package/src/components/enrollment-device-flow.tsx +8 -5
- package/src/components/file-browser.tsx +190 -56
- package/src/components/fleet-tile.tsx +13 -4
- package/src/components/machine-card.tsx +16 -4
- package/src/components/machine-dock-bar.tsx +20 -8
- package/src/components/machine-metrics.tsx +31 -8
- package/src/components/machine-status-pill.tsx +26 -5
- package/src/components/machines-dashboard.tsx +8 -2
- package/src/components/markdown.tsx +39 -9
- package/src/components/message-timeline.tsx +633 -109
- package/src/components/pierre-diff.tsx +83 -15
- package/src/components/pierre-file.tsx +10 -9
- package/src/components/sandbox-files.tsx +110 -227
- package/src/components/sandbox-terminal.tsx +256 -88
- package/src/components/sandbox-workspace.tsx +823 -0
- package/src/components/session-status.tsx +28 -2
- package/src/components/workbench-changes.tsx +541 -0
- package/src/components/workspace-dock.tsx +85 -22
- package/src/hooks/internal.ts +5 -2
- package/src/hooks/use-available-models.ts +7 -2
- package/src/hooks/use-billing-usage.ts +4 -1
- package/src/hooks/use-codex-accounts.ts +74 -44
- package/src/hooks/use-composer.ts +57 -36
- package/src/hooks/use-environments.ts +92 -47
- package/src/hooks/use-file-attachments.ts +101 -55
- package/src/hooks/use-goal.ts +52 -16
- package/src/hooks/use-machine-chip.ts +134 -0
- package/src/hooks/use-machines.ts +34 -16
- package/src/hooks/use-packs.ts +24 -19
- package/src/hooks/use-relay-frame-stream.ts +5 -2
- package/src/hooks/use-rigs.ts +335 -0
- package/src/hooks/use-sandbox-files.ts +213 -39
- package/src/hooks/use-sandbox-git.ts +188 -11
- package/src/hooks/use-sandbox-terminal.ts +18 -14
- package/src/hooks/use-scheduled-tasks.ts +10 -2
- package/src/hooks/use-session-capabilities.ts +77 -20
- package/src/hooks/use-session-control.ts +49 -21
- package/src/hooks/use-session-events.ts +48 -20
- package/src/hooks/use-session-lineage.ts +119 -0
- package/src/hooks/use-session.ts +43 -28
- package/src/hooks/use-slash-commands.ts +6 -4
- package/src/hooks/use-turn-queue.ts +74 -147
- package/src/hooks/use-windowed-sections.ts +204 -0
- package/src/hooks/use-workspace-capture.ts +233 -0
- package/src/hooks/use-workspace-edit.ts +231 -0
- package/src/hooks/use-workspace-sessions.ts +48 -5
- package/src/hooks/use-workspaces.ts +18 -15
- package/src/index.ts +128 -23
- package/src/lib/format.ts +44 -6
- package/src/lib/xterm-renderer.ts +65 -0
- package/src/lib/xterm-theme.ts +224 -18
- package/src/machines.ts +13 -3
- package/src/provider.tsx +3 -1
- package/src/timeline/activity-rail.tsx +213 -54
- package/src/timeline/disclosure-context.tsx +12 -2
- package/src/timeline/index.ts +21 -2
- package/src/timeline/parsers.ts +44 -10
- package/src/timeline/projection.ts +435 -84
- package/src/timeline/shared.tsx +111 -24
- package/src/timeline/tool-diff.tsx +24 -20
- package/src/timeline/tool-renderers.tsx +98 -21
- package/src/timeline/turn-summary.tsx +76 -23
- package/src/timeline/types.ts +96 -12
- package/styles/tokens.css +2 -2
- package/dist/chunk-5C7RGAWA.js.map +0 -1
|
@@ -1,85 +1,60 @@
|
|
|
1
|
-
import type { FsReadResponse
|
|
2
|
-
import {
|
|
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
|
|
14
|
+
/** From `useSandboxGit(...)` — drives the branch/dirty header only. */
|
|
18
15
|
git: UseSandboxGitResult;
|
|
19
|
-
/**
|
|
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
|
|
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
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
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
|
-
|
|
75
|
-
|
|
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,
|
|
82
|
-
// stacked (tree over
|
|
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
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
//
|
|
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
|
|
130
|
-
//
|
|
131
|
-
//
|
|
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={
|
|
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:
|
|
156
|
-
|
|
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
|
|
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={
|
|
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
|
-
{/*
|
|
219
|
-
side-by-side, sits below the tree when
|
|
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
|
-
{
|
|
142
|
+
{selected ?? "No file selected"}
|
|
231
143
|
</span>
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
{
|
|
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>
|
|
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>
|
|
177
|
+
<Notice>
|
|
178
|
+
{viewPath} is a binary file ({fileView.sizeBytes ?? 0} bytes).
|
|
179
|
+
</Notice>
|
|
305
180
|
) : fileView.content !== null ? (
|
|
306
181
|
<>
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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 —
|
|
335
|
-
|
|
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-
|
|
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({
|
|
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({
|
|
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;
|