@opengeni/react 0.13.0 → 0.20.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 +129 -23
- package/dist/chunk-6BXBGJ3B.js +1956 -0
- package/dist/chunk-6BXBGJ3B.js.map +1 -0
- package/dist/chunk-6UT5OC3P.js +2133 -0
- package/dist/chunk-6UT5OC3P.js.map +1 -0
- package/dist/chunk-EBTT3MYN.js +92 -0
- package/dist/chunk-EBTT3MYN.js.map +1 -0
- package/dist/chunk-HHFA4AFX.js +425 -0
- package/dist/chunk-HHFA4AFX.js.map +1 -0
- package/dist/chunk-LAZ2A743.js +2393 -0
- package/dist/chunk-LAZ2A743.js.map +1 -0
- package/dist/chunk-MRSECXRP.js +563 -0
- package/dist/chunk-MRSECXRP.js.map +1 -0
- package/dist/chunk-TK7G6XLT.js +18 -0
- package/dist/chunk-TK7G6XLT.js.map +1 -0
- package/dist/composer-DvUaa5ki.d.ts +585 -0
- package/dist/composer.d.ts +6 -0
- package/dist/composer.js +56 -0
- package/dist/composer.js.map +1 -0
- package/dist/index.d.ts +642 -1080
- package/dist/index.js +7672 -8005
- package/dist/index.js.map +1 -1
- package/dist/machines.d.ts +412 -3
- package/dist/machines.js +25 -1
- package/dist/noto-sans-arabic-loader-IA2T4X2A.js +21 -0
- package/dist/noto-sans-arabic-loader-IA2T4X2A.js.map +1 -0
- package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js +292 -0
- package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js.map +1 -0
- package/dist/queue-surface-implementation-NQMV2ML3.js +890 -0
- package/dist/queue-surface-implementation-NQMV2ML3.js.map +1 -0
- package/dist/session-Gd4iVPYw.d.ts +425 -0
- package/dist/session-context-DIFFlXKc.d.ts +52 -0
- package/dist/session.d.ts +4 -0
- package/dist/session.js +50 -0
- package/dist/session.js.map +1 -0
- package/dist/use-composer-BCfm4mzX.d.ts +112 -0
- package/package.json +23 -4
- package/src/approvals.ts +5 -3
- package/src/client.ts +50 -1
- package/src/components/approval-surface.tsx +198 -0
- package/src/components/chat-composer.tsx +104 -751
- package/src/components/code-editor.tsx +104 -26
- package/src/components/command-palette.tsx +8 -2
- package/src/components/composer-transcription-control.tsx +211 -0
- package/src/components/composer.tsx +1536 -0
- package/src/components/desktop-viewer.tsx +3 -3
- package/src/components/enrollment-consent.tsx +2 -2
- package/src/components/file-browser.tsx +367 -29
- package/src/components/human-input-form.tsx +417 -0
- package/src/components/machine-card.tsx +82 -16
- package/src/components/machine-health-pill.tsx +68 -0
- package/src/components/machine-metrics.tsx +10 -24
- package/src/components/machines/health.ts +146 -0
- package/src/components/machines/machine-detail.tsx +220 -0
- package/src/components/machines/metric-history-chart.tsx +298 -0
- package/src/components/machines/metric-sparkline.tsx +76 -0
- package/src/components/machines/series.ts +113 -0
- package/src/components/machines-dashboard.tsx +16 -4
- package/src/components/message-timeline.tsx +15 -7
- package/src/components/model-picker.tsx +12 -3
- package/src/components/pierre-diff.tsx +32 -6
- package/src/components/queue-surface-implementation.tsx +1041 -0
- package/src/components/queue-surface-state.tsx +94 -0
- package/src/components/queue-surface.tsx +60 -0
- package/src/components/sandbox-files.tsx +230 -21
- package/src/components/sandbox-terminal.tsx +8 -2
- package/src/components/sandbox-workspace.tsx +489 -135
- package/src/components/session-status.tsx +0 -6
- package/src/components/workbench-changes.tsx +145 -50
- package/src/components/workspace-dock.tsx +329 -68
- package/src/composer.ts +60 -0
- package/src/hooks/internal.ts +115 -34
- package/src/hooks/use-composer.ts +635 -74
- package/src/hooks/use-human-input.ts +131 -0
- package/src/hooks/use-machine-chip.ts +2 -2
- package/src/hooks/use-machines.ts +27 -11
- package/src/hooks/use-sandbox-files.ts +310 -62
- package/src/hooks/use-sandbox-git.ts +154 -40
- package/src/hooks/use-sandbox-terminal.ts +28 -6
- package/src/hooks/use-session-capabilities.ts +38 -9
- package/src/hooks/use-session-control.ts +43 -15
- package/src/hooks/use-session-events.ts +523 -48
- package/src/hooks/use-session-lineage.ts +15 -6
- package/src/hooks/use-session.ts +10 -2
- package/src/hooks/use-slash-commands.ts +38 -38
- package/src/hooks/use-transcription.ts +757 -0
- package/src/hooks/use-turn-queue.ts +273 -77
- package/src/hooks/use-windowed-sections.ts +2 -2
- package/src/hooks/use-workspace-capture.ts +146 -40
- package/src/hooks/use-workspace-edit.ts +50 -14
- package/src/hooks/use-workspace-sessions.ts +3 -0
- package/src/human-input.ts +72 -0
- package/src/index.ts +88 -12
- package/src/lib/noto-sans-arabic-loader.ts +21 -0
- package/src/lib/noto-sans-jp-loader.generated.ts +295 -0
- package/src/lib/use-portal-token-style.ts +51 -0
- package/src/lib/use-unicode-fonts.ts +57 -0
- package/src/machines.ts +16 -0
- package/src/provider.tsx +163 -41
- package/src/session-context.ts +135 -0
- package/src/session.ts +79 -0
- package/src/timeline/parsers.ts +78 -6
- package/src/timeline/projection.ts +36 -6
- package/src/timeline/tool-renderers.tsx +41 -0
- package/src/timeline/turn-summary.tsx +2 -2
- package/src/timeline/types.ts +16 -8
- package/styles/index.css +78 -0
- package/styles/index.d.ts +2 -0
- package/styles/tokens.css +11 -6
- package/styles/tokens.d.ts +2 -0
- package/dist/chunk-NFYVQWIB.js +0 -1377
- package/dist/chunk-NFYVQWIB.js.map +0 -1
- package/dist/machines-CnlMb7E-.d.ts +0 -329
|
@@ -35,12 +35,6 @@ export const SESSION_STATUS_META: Record<SessionStatusValue, SessionStatusMeta>
|
|
|
35
35
|
badgeClassName: "text-og-status-waiting border-og-status-waiting/35 bg-og-status-waiting/10",
|
|
36
36
|
pulse: true,
|
|
37
37
|
},
|
|
38
|
-
paused: {
|
|
39
|
-
label: "Paused",
|
|
40
|
-
dotClassName: "bg-og-status-cancelled",
|
|
41
|
-
badgeClassName: "text-og-fg-subtle border-og-border bg-og-status-cancelled/10",
|
|
42
|
-
pulse: false,
|
|
43
|
-
},
|
|
44
38
|
idle: {
|
|
45
39
|
label: "Idle",
|
|
46
40
|
dotClassName: "bg-og-status-idle",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { GitFileDiff } from "@opengeni/sdk";
|
|
2
|
-
import { FileWarningIcon, HistoryIcon } from "lucide-react";
|
|
2
|
+
import { ArrowUpRightIcon, FileWarningIcon, HistoryIcon } from "lucide-react";
|
|
3
3
|
import {
|
|
4
4
|
type ReactNode,
|
|
5
5
|
useCallback,
|
|
@@ -12,25 +12,28 @@ import {
|
|
|
12
12
|
} from "react";
|
|
13
13
|
import { VList } from "virtua";
|
|
14
14
|
import { cn } from "../lib/cn";
|
|
15
|
+
import { useUnicodeFallbackFonts } from "../lib/use-unicode-fonts";
|
|
15
16
|
import { useThemeType } from "../lib/use-theme-type";
|
|
16
17
|
import { formatAsOf } from "../hooks/use-machine-chip";
|
|
18
|
+
import type { SandboxGitFileDiff } from "../hooks/use-sandbox-git";
|
|
17
19
|
import { useWindowedSections } from "../hooks/use-windowed-sections";
|
|
18
20
|
import { PierreDiff } from "./pierre-diff";
|
|
19
21
|
|
|
20
22
|
/* ----------------------------------------------------------------------------
|
|
21
23
|
Changes tab — a PR-review surface.
|
|
22
24
|
|
|
23
|
-
A file rail (status glyph, ±counts, grouped by
|
|
24
|
-
|
|
25
|
+
A file rail (status glyph, ±counts, grouped by repository when metadata is
|
|
26
|
+
available, otherwise by top-level directory for a large legacy input) on the
|
|
27
|
+
left; a stacked, WINDOWED diff pane on the right. Only
|
|
25
28
|
the file sections inside the visible window ± overscan mount a Pierre/Shiki
|
|
26
29
|
highlighter (the one renderer), so a 40- or 400-file change set never mounts N
|
|
27
|
-
highlighters at once (
|
|
30
|
+
highlighters at once (D2). A per-file size guard (binary / diff too
|
|
28
31
|
large) degrades to an "open live" affordance instead of a diff body.
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
threshold
|
|
33
|
+
Workspace-wide Git results carry an explicit `repoRoot`, so grouping never
|
|
34
|
+
guesses repository ownership from a directory name. Multi-repo results group
|
|
35
|
+
at every size; legacy unscoped inputs group by top-level directory only past
|
|
36
|
+
the threshold.
|
|
34
37
|
-------------------------------------------------------------------------- */
|
|
35
38
|
|
|
36
39
|
/** Group the rail (and reorder the pane) once the change set is larger than this. */
|
|
@@ -67,9 +70,16 @@ const STATUS_LETTER: Record<GitFileDiff["status"], string> = {
|
|
|
67
70
|
typechange: "T",
|
|
68
71
|
};
|
|
69
72
|
|
|
73
|
+
function compactFileLabel(file: SandboxGitFileDiff): string {
|
|
74
|
+
const slash = file.path.lastIndexOf("/");
|
|
75
|
+
const basename = slash >= 0 ? file.path.slice(slash + 1) : file.path;
|
|
76
|
+
const parent = slash >= 0 ? file.path.slice(0, slash + 1) : "";
|
|
77
|
+
return `${STATUS_LETTER[file.status]} · ${basename}${parent ? ` — ${parent}` : ""}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
70
80
|
export type WorkbenchChangesProps = {
|
|
71
81
|
/** The changed files (`useSandboxGit().diff`). Assumed non-empty by the caller. */
|
|
72
|
-
diff:
|
|
82
|
+
diff: SandboxGitFileDiff[];
|
|
73
83
|
/** Which source the diff came from — drives the "live" vs "as of turn" badge. */
|
|
74
84
|
source: "live" | "capture" | null;
|
|
75
85
|
/** When the served capture was taken (ISO), when `source === "capture"`. */
|
|
@@ -77,33 +87,48 @@ export type WorkbenchChangesProps = {
|
|
|
77
87
|
/** The capture's turn revision, for the "as of turn N" badge. */
|
|
78
88
|
captureRevision?: number | null | undefined;
|
|
79
89
|
themeType?: "dark" | "light" | undefined;
|
|
90
|
+
/** Route a guarded binary/oversize file into the live Files surface. */
|
|
91
|
+
onOpenFile?: ((path: string) => void) | undefined;
|
|
80
92
|
className?: string | undefined;
|
|
81
93
|
};
|
|
82
94
|
|
|
83
95
|
type RailRow =
|
|
84
96
|
| { kind: "group"; label: string; count: number }
|
|
85
|
-
| { kind: "file"; file:
|
|
97
|
+
| { kind: "file"; file: SandboxGitFileDiff; index: number };
|
|
86
98
|
|
|
87
99
|
/** Order the files and build the rail rows (grouped past the threshold). The
|
|
88
100
|
* returned `orderedFiles` drives BOTH the rail and the diff pane so a rail row's
|
|
89
101
|
* `index` addresses the matching pane section. Exported for tests. */
|
|
90
|
-
export function buildRail(files:
|
|
91
|
-
|
|
102
|
+
export function buildRail(files: SandboxGitFileDiff[]): {
|
|
103
|
+
orderedFiles: SandboxGitFileDiff[];
|
|
104
|
+
rows: RailRow[];
|
|
105
|
+
grouped: boolean;
|
|
106
|
+
} {
|
|
107
|
+
const roots = new Set(
|
|
108
|
+
files.flatMap((file) => (file.repoRoot === undefined ? [] : [file.repoRoot])),
|
|
109
|
+
);
|
|
110
|
+
const groupByRepo = roots.size > 1;
|
|
111
|
+
if (!groupByRepo && files.length <= GROUP_THRESHOLD) {
|
|
92
112
|
return {
|
|
93
113
|
orderedFiles: files,
|
|
94
114
|
rows: files.map((file, index) => ({ kind: "file", file, index })),
|
|
115
|
+
grouped: false,
|
|
95
116
|
};
|
|
96
117
|
}
|
|
97
|
-
const groups = new Map<string,
|
|
118
|
+
const groups = new Map<string, SandboxGitFileDiff[]>();
|
|
98
119
|
for (const file of files) {
|
|
99
120
|
const slash = file.path.indexOf("/");
|
|
100
|
-
const key =
|
|
121
|
+
const key = groupByRepo
|
|
122
|
+
? file.repoRoot || "(workspace)"
|
|
123
|
+
: slash > 0
|
|
124
|
+
? file.path.slice(0, slash)
|
|
125
|
+
: "(root)";
|
|
101
126
|
const bucket = groups.get(key);
|
|
102
127
|
if (bucket) bucket.push(file);
|
|
103
128
|
else groups.set(key, [file]);
|
|
104
129
|
}
|
|
105
130
|
const labels = [...groups.keys()].sort((a, b) => a.localeCompare(b));
|
|
106
|
-
const orderedFiles:
|
|
131
|
+
const orderedFiles: SandboxGitFileDiff[] = [];
|
|
107
132
|
const rows: RailRow[] = [];
|
|
108
133
|
for (const label of labels) {
|
|
109
134
|
const bucket = (groups.get(label) ?? []).slice().sort((a, b) => a.path.localeCompare(b.path));
|
|
@@ -113,7 +138,7 @@ export function buildRail(files: GitFileDiff[]): { orderedFiles: GitFileDiff[];
|
|
|
113
138
|
orderedFiles.push(file);
|
|
114
139
|
}
|
|
115
140
|
}
|
|
116
|
-
return { orderedFiles, rows };
|
|
141
|
+
return { orderedFiles, rows, grouped: true };
|
|
117
142
|
}
|
|
118
143
|
|
|
119
144
|
/** A file's diff body is "too large to inline" when it's binary or the diff guard
|
|
@@ -137,6 +162,7 @@ export function WorkbenchChanges({
|
|
|
137
162
|
capturedAt,
|
|
138
163
|
captureRevision,
|
|
139
164
|
themeType,
|
|
165
|
+
onOpenFile,
|
|
140
166
|
className,
|
|
141
167
|
}: WorkbenchChangesProps) {
|
|
142
168
|
const rootRef = useRef<HTMLDivElement | null>(null);
|
|
@@ -147,8 +173,13 @@ export function WorkbenchChanges({
|
|
|
147
173
|
const [activePath, setActivePath] = useState<string | null>(null);
|
|
148
174
|
const pickerId = useId();
|
|
149
175
|
|
|
150
|
-
const
|
|
151
|
-
|
|
176
|
+
const unicodeFontPaths = useMemo(
|
|
177
|
+
() => diff.flatMap((file) => (file.oldPath ? [file.path, file.oldPath] : [file.path])),
|
|
178
|
+
[diff],
|
|
179
|
+
);
|
|
180
|
+
useUnicodeFallbackFonts(unicodeFontPaths);
|
|
181
|
+
|
|
182
|
+
const { orderedFiles, rows, grouped } = useMemo(() => buildRail(diff), [diff]);
|
|
152
183
|
const selectedIndex = orderedFiles.findIndex((file) => file.path === activePath);
|
|
153
184
|
const activeIndex = selectedIndex >= 0 ? selectedIndex : 0;
|
|
154
185
|
|
|
@@ -211,7 +242,9 @@ export function WorkbenchChanges({
|
|
|
211
242
|
// Track which section is at the top of the pane so the rail highlights it.
|
|
212
243
|
const onPaneScroll = useCallback(() => {
|
|
213
244
|
const el = windowed.scrollRef.current;
|
|
214
|
-
if (!el)
|
|
245
|
+
if (!el) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
215
248
|
const top = el.scrollTop;
|
|
216
249
|
const offsets = windowed.offsets;
|
|
217
250
|
let idx = 0;
|
|
@@ -240,13 +273,33 @@ export function WorkbenchChanges({
|
|
|
240
273
|
data-workbench-changes-layout={compact ? "compact" : "rail"}
|
|
241
274
|
>
|
|
242
275
|
{/* Summary + source badge + layout toggle. */}
|
|
243
|
-
<div
|
|
244
|
-
|
|
276
|
+
<div
|
|
277
|
+
className={cn(
|
|
278
|
+
"flex shrink-0 items-center justify-between gap-2 border-b border-og-border px-3 py-1.5",
|
|
279
|
+
compact && source === "capture" && "flex-col items-stretch gap-1.5 py-2",
|
|
280
|
+
)}
|
|
281
|
+
>
|
|
282
|
+
<span
|
|
283
|
+
role="status"
|
|
284
|
+
aria-atomic="true"
|
|
285
|
+
aria-live="polite"
|
|
286
|
+
data-contrast-audited
|
|
287
|
+
className="min-w-0 text-og-xs text-og-fg-muted"
|
|
288
|
+
>
|
|
245
289
|
{diff.length} {diff.length === 1 ? "file" : "files"} changed
|
|
246
|
-
<span className="ml-2 text-og-status-idle"
|
|
247
|
-
|
|
290
|
+
<span data-contrast-audited className="ml-2 text-og-status-idle">
|
|
291
|
+
+{additions}
|
|
292
|
+
</span>
|
|
293
|
+
<span data-contrast-audited className="ml-1 text-og-status-failed">
|
|
294
|
+
−{deletions}
|
|
295
|
+
</span>
|
|
248
296
|
</span>
|
|
249
|
-
<div
|
|
297
|
+
<div
|
|
298
|
+
className={cn(
|
|
299
|
+
"flex shrink-0 items-center gap-2",
|
|
300
|
+
compact && source === "capture" && "self-start",
|
|
301
|
+
)}
|
|
302
|
+
>
|
|
250
303
|
{compact ? null : <LayoutToggle layout={layout} onChange={setLayout} />}
|
|
251
304
|
<SourceBadge source={source} capturedAt={capturedAt} label={sourceBadge} />
|
|
252
305
|
</div>
|
|
@@ -266,26 +319,32 @@ export function WorkbenchChanges({
|
|
|
266
319
|
const index = Number(event.currentTarget.value);
|
|
267
320
|
setActivePath(orderedFiles[index]?.path ?? null);
|
|
268
321
|
}}
|
|
322
|
+
title={activeFile?.path}
|
|
269
323
|
data-compact-file-picker
|
|
270
324
|
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"
|
|
271
325
|
>
|
|
272
326
|
{orderedFiles.map((file, index) => (
|
|
273
327
|
<option key={file.path} value={index}>
|
|
274
|
-
{
|
|
328
|
+
{compactFileLabel(file)}
|
|
275
329
|
</option>
|
|
276
330
|
))}
|
|
277
331
|
</select>
|
|
278
332
|
</div>
|
|
279
333
|
<div className="min-h-0 min-w-0 flex-1 overflow-auto" data-opengeni-changes-pane>
|
|
280
334
|
{activeFile ? (
|
|
281
|
-
<DiffSection
|
|
335
|
+
<DiffSection
|
|
336
|
+
file={activeFile}
|
|
337
|
+
layout="unified"
|
|
338
|
+
themeType={resolvedTheme}
|
|
339
|
+
{...(onOpenFile ? { onOpenFile } : {})}
|
|
340
|
+
/>
|
|
282
341
|
) : null}
|
|
283
342
|
</div>
|
|
284
343
|
</div>
|
|
285
344
|
) : (
|
|
286
345
|
<div className="flex min-h-0 flex-1">
|
|
287
346
|
{/* File rail — virtualized (virtua): a dense change set is fine. */}
|
|
288
|
-
<div className="w-[
|
|
347
|
+
<div className="w-[clamp(12rem,28%,15rem)] shrink-0 border-r border-og-border bg-og-surface-1/35">
|
|
289
348
|
<VList
|
|
290
349
|
className="h-full"
|
|
291
350
|
itemSize={coarsePointer ? 44 : 28}
|
|
@@ -296,7 +355,7 @@ export function WorkbenchChanges({
|
|
|
296
355
|
<div
|
|
297
356
|
key={`g:${row.label}`}
|
|
298
357
|
data-rail-group
|
|
299
|
-
className="flex items-center gap-1.5 px-2 pb-0.5 pt-2 text-
|
|
358
|
+
className="flex items-center gap-1.5 px-2 pb-0.5 pt-2 text-og-xs font-medium uppercase tracking-wide text-og-fg-subtle"
|
|
300
359
|
>
|
|
301
360
|
<span className="min-w-0 truncate">{row.label}</span>
|
|
302
361
|
<span className="shrink-0">{row.count}</span>
|
|
@@ -332,7 +391,12 @@ export function WorkbenchChanges({
|
|
|
332
391
|
top={windowed.offsets[index] ?? 0}
|
|
333
392
|
onMeasure={windowed.measure}
|
|
334
393
|
>
|
|
335
|
-
<DiffSection
|
|
394
|
+
<DiffSection
|
|
395
|
+
file={file}
|
|
396
|
+
layout={layout}
|
|
397
|
+
themeType={resolvedTheme}
|
|
398
|
+
{...(onOpenFile ? { onOpenFile } : {})}
|
|
399
|
+
/>
|
|
336
400
|
</MeasuredSection>
|
|
337
401
|
);
|
|
338
402
|
})}
|
|
@@ -362,7 +426,7 @@ function SourceBadge({
|
|
|
362
426
|
if (source === "capture" && capturedAt) {
|
|
363
427
|
return (
|
|
364
428
|
<span
|
|
365
|
-
className="inline-flex items-center gap-1 rounded-og-xs border border-og-border px-1.5 py-px text-
|
|
429
|
+
className="inline-flex items-center gap-1 rounded-og-xs border border-og-border px-1.5 py-px text-og-xs text-og-fg-muted"
|
|
366
430
|
title={new Date(capturedAt).toLocaleString()}
|
|
367
431
|
>
|
|
368
432
|
<HistoryIcon className="size-3 shrink-0 text-og-status-running" aria-hidden />
|
|
@@ -371,19 +435,19 @@ function SourceBadge({
|
|
|
371
435
|
);
|
|
372
436
|
}
|
|
373
437
|
return (
|
|
374
|
-
<span className="rounded-og-xs bg-og-surface-2 px-1.5 py-px text-
|
|
438
|
+
<span className="rounded-og-xs bg-og-surface-2 px-1.5 py-px text-og-xs text-og-fg-subtle">
|
|
375
439
|
{label}
|
|
376
440
|
</span>
|
|
377
441
|
);
|
|
378
442
|
}
|
|
379
443
|
|
|
380
|
-
/** "
|
|
444
|
+
/** "Live diff" · "as of turn 7 · 14:32" · "as of 14:32" (no revision). */
|
|
381
445
|
function describeSource(
|
|
382
446
|
source: "live" | "capture" | null,
|
|
383
447
|
capturedAt: string | null,
|
|
384
448
|
revision: number | null,
|
|
385
449
|
): string {
|
|
386
|
-
if (source !== "capture" || !capturedAt) return "
|
|
450
|
+
if (source !== "capture" || !capturedAt) return "Live diff";
|
|
387
451
|
const time = formatAsOf(capturedAt, Date.now());
|
|
388
452
|
return revision !== null ? `as of turn ${revision} · ${time}` : `as of ${time}`;
|
|
389
453
|
}
|
|
@@ -394,14 +458,21 @@ function RailFileRow({
|
|
|
394
458
|
active,
|
|
395
459
|
onClick,
|
|
396
460
|
}: {
|
|
397
|
-
file:
|
|
461
|
+
file: SandboxGitFileDiff;
|
|
398
462
|
grouped: boolean;
|
|
399
463
|
active: boolean;
|
|
400
464
|
onClick: () => void;
|
|
401
465
|
}) {
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
const
|
|
466
|
+
// A real repository group strips the exact repo root. Legacy top-directory
|
|
467
|
+
// grouping strips only that first segment.
|
|
468
|
+
const repoPrefix = file.repoRoot ? `${file.repoRoot}/` : null;
|
|
469
|
+
const shown = grouped
|
|
470
|
+
? file.repoRoot !== undefined
|
|
471
|
+
? repoPrefix && file.path.startsWith(repoPrefix)
|
|
472
|
+
? file.path.slice(repoPrefix.length)
|
|
473
|
+
: file.path
|
|
474
|
+
: file.path.slice(file.path.indexOf("/") + 1) || file.path
|
|
475
|
+
: file.path;
|
|
405
476
|
return (
|
|
406
477
|
<button
|
|
407
478
|
type="button"
|
|
@@ -409,20 +480,26 @@ function RailFileRow({
|
|
|
409
480
|
title={file.path}
|
|
410
481
|
data-rail-file
|
|
411
482
|
className={cn(
|
|
412
|
-
"flex min-h-7 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-11",
|
|
483
|
+
"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",
|
|
413
484
|
grouped && "pl-3",
|
|
414
|
-
active &&
|
|
485
|
+
active &&
|
|
486
|
+
"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",
|
|
415
487
|
)}
|
|
416
488
|
>
|
|
417
489
|
<span
|
|
490
|
+
data-contrast-audited
|
|
418
491
|
className={cn("w-3 shrink-0 text-center font-og-mono text-og-xs", STATUS_TINT[file.status])}
|
|
419
492
|
>
|
|
420
493
|
{STATUS_LETTER[file.status]}
|
|
421
494
|
</span>
|
|
422
495
|
<span className="min-w-0 flex-1 truncate">{shown}</span>
|
|
423
|
-
<span className="ml-auto flex shrink-0 items-center gap-1 pl-1 font-og-mono text-
|
|
424
|
-
<span className="text-og-status-idle"
|
|
425
|
-
|
|
496
|
+
<span className="ml-auto flex shrink-0 items-center gap-1 pl-1 font-og-mono text-og-xs">
|
|
497
|
+
<span data-contrast-audited className="text-og-status-idle">
|
|
498
|
+
+{file.additions}
|
|
499
|
+
</span>
|
|
500
|
+
<span data-contrast-audited className="text-og-status-failed">
|
|
501
|
+
−{file.deletions}
|
|
502
|
+
</span>
|
|
426
503
|
</span>
|
|
427
504
|
</button>
|
|
428
505
|
);
|
|
@@ -468,10 +545,12 @@ function DiffSection({
|
|
|
468
545
|
file,
|
|
469
546
|
layout,
|
|
470
547
|
themeType,
|
|
548
|
+
onOpenFile,
|
|
471
549
|
}: {
|
|
472
550
|
file: GitFileDiff;
|
|
473
551
|
layout: "unified" | "split";
|
|
474
552
|
themeType: "dark" | "light";
|
|
553
|
+
onOpenFile?: ((path: string) => void) | undefined;
|
|
475
554
|
}) {
|
|
476
555
|
// The guard files (binary / over-cap) get a minimal header + "open live" body
|
|
477
556
|
// since Pierre has nothing to render; real diffs use Pierre's own sticky file
|
|
@@ -484,17 +563,17 @@ function DiffSection({
|
|
|
484
563
|
<span className="min-w-0 truncate font-og-mono text-og-xs">
|
|
485
564
|
{renamed ? `${file.oldPath} → ${file.path}` : file.path}
|
|
486
565
|
</span>
|
|
487
|
-
<span className="flex shrink-0 items-center gap-2 font-og-mono text-
|
|
566
|
+
<span className="flex shrink-0 items-center gap-2 font-og-mono text-og-xs">
|
|
488
567
|
<span className="text-og-status-idle">+{file.additions}</span>
|
|
489
568
|
<span className="text-og-status-failed">−{file.deletions}</span>
|
|
490
569
|
</span>
|
|
491
570
|
</header>
|
|
492
|
-
<GuardBody file={file} />
|
|
571
|
+
<GuardBody file={file} {...(onOpenFile ? { onOpenFile } : {})} />
|
|
493
572
|
</section>
|
|
494
573
|
);
|
|
495
574
|
}
|
|
496
575
|
return (
|
|
497
|
-
<section className="border-b border-og-border pb-2">
|
|
576
|
+
<section data-pierre-section className="border-b border-og-border pb-2">
|
|
498
577
|
<PierreDiff diff={[file]} layout={layout} themeType={themeType} className="px-1" />
|
|
499
578
|
</section>
|
|
500
579
|
);
|
|
@@ -502,12 +581,28 @@ function DiffSection({
|
|
|
502
581
|
|
|
503
582
|
/** The per-file guard: a binary file or an over-cap diff opens live rather than
|
|
504
583
|
* inlining a body we don't (fully) have. */
|
|
505
|
-
function GuardBody({
|
|
584
|
+
function GuardBody({
|
|
585
|
+
file,
|
|
586
|
+
onOpenFile,
|
|
587
|
+
}: {
|
|
588
|
+
file: GitFileDiff;
|
|
589
|
+
onOpenFile?: ((path: string) => void) | undefined;
|
|
590
|
+
}) {
|
|
506
591
|
const reason = file.isBinary ? "Binary file" : "Diff too large to show here";
|
|
507
592
|
return (
|
|
508
|
-
<div className="flex items-center gap-2 px-3 py-3 text-og-sm text-og-fg-subtle">
|
|
509
|
-
<FileWarningIcon className="size-3.5 shrink-0" />
|
|
510
|
-
<span className="min-w-0">{reason}
|
|
593
|
+
<div className="flex flex-wrap items-center gap-2 px-3 py-3 text-og-sm text-og-fg-subtle">
|
|
594
|
+
<FileWarningIcon className="size-3.5 shrink-0" aria-hidden />
|
|
595
|
+
<span className="min-w-0 flex-1">{reason}.</span>
|
|
596
|
+
{onOpenFile ? (
|
|
597
|
+
<button
|
|
598
|
+
type="button"
|
|
599
|
+
onClick={() => onOpenFile(file.path)}
|
|
600
|
+
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"
|
|
601
|
+
>
|
|
602
|
+
Open in Files
|
|
603
|
+
<ArrowUpRightIcon className="size-3.5" aria-hidden />
|
|
604
|
+
</button>
|
|
605
|
+
) : null}
|
|
511
606
|
</div>
|
|
512
607
|
);
|
|
513
608
|
}
|
|
@@ -527,7 +622,7 @@ function LayoutToggle({
|
|
|
527
622
|
type="button"
|
|
528
623
|
onClick={() => onChange(value)}
|
|
529
624
|
className={cn(
|
|
530
|
-
"min-h-7 rounded-og-xs px-1.5 py-0.5 text-
|
|
625
|
+
"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",
|
|
531
626
|
layout === value
|
|
532
627
|
? "bg-og-accent-soft text-og-fg"
|
|
533
628
|
: "text-og-fg-subtle hover:text-og-fg",
|