@opengeni/react 0.6.1 → 0.6.3
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/dist/{chunk-DEW2ZNF2.js → chunk-5C7RGAWA.js} +70 -37
- package/dist/chunk-5C7RGAWA.js.map +1 -0
- package/dist/index.d.ts +29 -5
- package/dist/index.js +1061 -618
- package/dist/index.js.map +1 -1
- package/dist/{machines-BD6h9P_s.d.ts → machines-Bv9tG7qZ.d.ts} +1 -1
- package/dist/machines.d.ts +1 -1
- package/dist/machines.js +1 -1
- package/package.json +2 -2
- package/src/components/chat-composer.tsx +127 -60
- package/src/components/code-editor.tsx +15 -15
- package/src/components/desktop-viewer.tsx +40 -36
- package/src/components/diff-view.tsx +22 -22
- package/src/components/enrollment-consent.tsx +13 -13
- package/src/components/file-browser.tsx +74 -67
- package/src/components/fleet-tile.tsx +3 -3
- package/src/components/machine-card.tsx +6 -6
- package/src/components/machine-status-pill.tsx +3 -3
- package/src/components/machines-dashboard.tsx +28 -14
- package/src/components/markdown.tsx +6 -6
- package/src/components/message-timeline.tsx +75 -4
- package/src/components/pierre-diff.tsx +9 -10
- package/src/components/pierre-file.tsx +8 -8
- package/src/components/sandbox-files.tsx +37 -40
- package/src/components/sandbox-terminal.tsx +9 -11
- package/src/components/session-status.tsx +2 -2
- package/src/components/workspace-dock.tsx +153 -49
- package/src/hooks/use-file-attachments.ts +45 -15
- package/src/index.ts +2 -2
- package/src/lib/format.ts +32 -0
- package/src/lib/xterm-theme.ts +8 -11
- package/src/timeline/activity-rail.tsx +1 -1
- package/src/timeline/parsers.ts +104 -0
- package/src/timeline/projection.ts +135 -3
- package/src/timeline/screenshot-lightbox.tsx +1 -1
- package/src/timeline/shared.tsx +4 -1
- package/src/timeline/tool-diff.tsx +1 -1
- package/src/timeline/tool-renderers.tsx +46 -7
- package/src/timeline/turn-summary.tsx +21 -3
- package/src/timeline/types.ts +2 -0
- package/styles/tokens.css +8 -0
- package/dist/chunk-DEW2ZNF2.js.map +0 -1
|
@@ -147,7 +147,7 @@ type MachinesDashboardProps = {
|
|
|
147
147
|
};
|
|
148
148
|
/**
|
|
149
149
|
* The workspace Machines dashboard: the fleet of selfhosted enrollments + the
|
|
150
|
-
* session's
|
|
150
|
+
* session's managed sandbox, each with its connection-status pill, state badges,
|
|
151
151
|
* latest metrics, and an attach/swap affordance. Renders the empty state (no
|
|
152
152
|
* machines → enroll CTA), a load error, and the populated grid. The component
|
|
153
153
|
* is purely presentational — feed it `MachinesResponse` data via `useMachines`.
|
package/dist/machines.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { a as CONNECTION_STATUS_META, b as ConnectionDot, c as ConnectionDotProps, d as ConnectionStatus, e as ConnectionStatusMeta, f as ConnectionStatusPill, g as ConnectionStatusPillProps, D as DeviceFlowPhase, E as EnrollmentConsent, h as EnrollmentConsentMachine, i as EnrollmentConsentPhase, j as EnrollmentConsentProps, k as EnrollmentDeviceFlow, l as EnrollmentDeviceFlowProps, M as MACHINE_STATE_BADGE_META, m as MachineCard, n as MachineCardProps, o as MachineDockBar, p as MachineDockBarProps, q as MachineMetrics, r as MachineMetricsProps, s as MachineStateBadgeMeta, t as MachineStatusPill, u as MachineStatusPillProps, v as MachinesClientLike, w as MachinesDashboard, x as MachinesDashboardProps, A as SharedMachineDisclosure, B as SharedMachineDisclosureProps, U as UseMachinesOptions, F as UseMachinesResult, G as connectionStatusForState, H as useMachines } from './machines-
|
|
1
|
+
export { a as CONNECTION_STATUS_META, b as ConnectionDot, c as ConnectionDotProps, d as ConnectionStatus, e as ConnectionStatusMeta, f as ConnectionStatusPill, g as ConnectionStatusPillProps, D as DeviceFlowPhase, E as EnrollmentConsent, h as EnrollmentConsentMachine, i as EnrollmentConsentPhase, j as EnrollmentConsentProps, k as EnrollmentDeviceFlow, l as EnrollmentDeviceFlowProps, M as MACHINE_STATE_BADGE_META, m as MachineCard, n as MachineCardProps, o as MachineDockBar, p as MachineDockBarProps, q as MachineMetrics, r as MachineMetricsProps, s as MachineStateBadgeMeta, t as MachineStatusPill, u as MachineStatusPillProps, v as MachinesClientLike, w as MachinesDashboard, x as MachinesDashboardProps, A as SharedMachineDisclosure, B as SharedMachineDisclosureProps, U as UseMachinesOptions, F as UseMachinesResult, G as connectionStatusForState, H as useMachines } from './machines-Bv9tG7qZ.js';
|
|
2
2
|
export { MachineKind, MachineMetricsSeriesResponse, MachineState, MachineView, MachinesResponse, MetricSample } from '@opengeni/sdk';
|
|
3
3
|
import 'react/jsx-runtime';
|
|
4
4
|
import 'react';
|
package/dist/machines.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/react",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "React hooks and styled components for OpenGeni: live session streaming, chat composer, message timeline, session status, and fleet views — token-themed (CSS variables), dark-first, built on Tailwind v4 + Radix + Motion.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@bufbuild/protobuf": "^2.2.0",
|
|
48
|
-
"@opengeni/sdk": "^0.6.
|
|
48
|
+
"@opengeni/sdk": "^0.6.3",
|
|
49
49
|
"clsx": "^2.1.1",
|
|
50
50
|
"lucide-react": "^1.8.0",
|
|
51
51
|
"motion": "^12.0.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ClientModel, SessionStatus } from "@opengeni/sdk";
|
|
2
|
-
import { ArrowUpIcon, FileIcon, ImageIcon, LoaderCircleIcon, PaperclipIcon, SquareIcon, XIcon } from "lucide-react";
|
|
2
|
+
import { ArrowUpIcon, FileIcon, ImageIcon, LoaderCircleIcon, PaperclipIcon, RotateCwIcon, SquareIcon, XIcon } from "lucide-react";
|
|
3
3
|
import { AnimatePresence, motion } from "motion/react";
|
|
4
|
-
import { useCallback, useEffect, useId, useMemo, useRef, useState, type ChangeEvent, type ClipboardEvent, type DragEvent, type KeyboardEvent, type ReactNode } from "react";
|
|
4
|
+
import { useCallback, useEffect, useId, useMemo, useRef, useState, type ChangeEvent, type ClipboardEvent, type DragEvent, type KeyboardEvent, type ReactNode, type RefObject } from "react";
|
|
5
5
|
import { argHint } from "../commands/registry";
|
|
6
6
|
import type { Notice, SlashCommand } from "../commands/types";
|
|
7
7
|
import type { ComposerState } from "../hooks/use-composer";
|
|
@@ -303,7 +303,9 @@ export function ChatComposer({
|
|
|
303
303
|
const activeNotice = notice ?? (composer.error ? { tone: "error" as const, message: composer.error.message || "Sending failed — your draft is still here. Try again." } : null);
|
|
304
304
|
|
|
305
305
|
return (
|
|
306
|
-
|
|
306
|
+
// Respect the iOS home-indicator inset so the sticky composer never sits
|
|
307
|
+
// under it (0 on non-notch devices and desktop, so it's inert there).
|
|
308
|
+
<div className={cn("og-root", className)} style={{ paddingBottom: "env(safe-area-inset-bottom)" }}>
|
|
307
309
|
<div className="relative">
|
|
308
310
|
{paletteEnabled ? (
|
|
309
311
|
<CommandPalette
|
|
@@ -354,7 +356,11 @@ export function ChatComposer({
|
|
|
354
356
|
</div>
|
|
355
357
|
) : null}
|
|
356
358
|
{attachments && attachments.attachments.length > 0 ? (
|
|
357
|
-
<AttachmentChips
|
|
359
|
+
<AttachmentChips
|
|
360
|
+
attachments={attachments.attachments}
|
|
361
|
+
onRemove={attachments.remove}
|
|
362
|
+
onRetry={attachments.retry}
|
|
363
|
+
/>
|
|
358
364
|
) : null}
|
|
359
365
|
{header}
|
|
360
366
|
<textarea
|
|
@@ -373,7 +379,9 @@ export function ChatComposer({
|
|
|
373
379
|
aria-controls={paletteEnabled && palette.open ? listboxId : undefined}
|
|
374
380
|
aria-activedescendant={paletteEnabled && palette.open ? `${listboxId}-option-${palette.highlight}` : undefined}
|
|
375
381
|
className={cn(
|
|
376
|
-
|
|
382
|
+
// Font size steps up to 16px below `md` so iOS never zooms the
|
|
383
|
+
// viewport on focus; desktop keeps the 15px og-md rhythm.
|
|
384
|
+
"block w-full resize-none bg-transparent px-4 pt-3.5 pb-1 text-base leading-6 md:text-og-md",
|
|
377
385
|
// The wrapper owns the whole-composer focus affordance (focus-within
|
|
378
386
|
// border + soft glow). Suppress any self-scoped focus outline on the
|
|
379
387
|
// textarea itself: `focus:outline-none` alone only sets outline-style
|
|
@@ -391,11 +399,15 @@ export function ChatComposer({
|
|
|
391
399
|
command={pendingDangerCommand}
|
|
392
400
|
onCancel={() => confirmState.resolve(false)}
|
|
393
401
|
onConfirm={() => confirmState.resolve(true)}
|
|
402
|
+
returnFocusRef={textareaRef}
|
|
394
403
|
/>
|
|
395
404
|
) : (
|
|
396
|
-
<div className="flex items-
|
|
405
|
+
<div className="flex items-end gap-2 px-2.5 pb-2.5 pt-1">
|
|
397
406
|
{attachments || models || controlsStart ? (
|
|
398
|
-
|
|
407
|
+
// The control group wraps onto extra rows when it can't fit
|
|
408
|
+
// (narrow viewports) instead of clipping under the rounded
|
|
409
|
+
// corner; send/stop stays anchored bottom-right (shrink-0).
|
|
410
|
+
<span className="flex min-w-0 flex-1 flex-wrap items-center gap-1.5">
|
|
399
411
|
{attachments ? (
|
|
400
412
|
<>
|
|
401
413
|
<input
|
|
@@ -432,11 +444,11 @@ export function ChatComposer({
|
|
|
432
444
|
{controlsStart}
|
|
433
445
|
</span>
|
|
434
446
|
) : (
|
|
435
|
-
<span className="px-1.5 text-
|
|
447
|
+
<span className="min-w-0 flex-1 px-1.5 text-og-xs text-og-fg-subtle max-sm:hidden">
|
|
436
448
|
{hint ?? "Enter to send · Shift+Enter for a new line · / for commands"}
|
|
437
449
|
</span>
|
|
438
450
|
)}
|
|
439
|
-
<span className="flex items-center gap-1.5">
|
|
451
|
+
<span className="ml-auto flex shrink-0 items-center gap-1.5">
|
|
440
452
|
<AnimatePresence initial={false}>
|
|
441
453
|
{active ? (
|
|
442
454
|
<motion.button
|
|
@@ -451,7 +463,7 @@ export function ChatComposer({
|
|
|
451
463
|
aria-label="Stop the current turn"
|
|
452
464
|
title="Stop the current turn"
|
|
453
465
|
className={cn(
|
|
454
|
-
"inline-flex size-8 items-center justify-center rounded-og-md border border-og-border",
|
|
466
|
+
"inline-flex size-8 items-center justify-center rounded-og-md border border-og-border pointer-coarse:size-11",
|
|
455
467
|
"bg-og-surface-2 text-og-fg-muted transition-colors duration-150",
|
|
456
468
|
"hover:border-og-status-failed/50 hover:text-og-status-failed",
|
|
457
469
|
"disabled:opacity-50",
|
|
@@ -476,7 +488,7 @@ export function ChatComposer({
|
|
|
476
488
|
disabled={!canSend || disabled === true || commandDraftBlocked}
|
|
477
489
|
aria-label="Send message"
|
|
478
490
|
className={cn(
|
|
479
|
-
"inline-flex size-8 items-center justify-center rounded-og-md",
|
|
491
|
+
"inline-flex size-8 items-center justify-center rounded-og-md pointer-coarse:size-11",
|
|
480
492
|
"bg-og-accent text-og-accent-fg shadow-og-sm",
|
|
481
493
|
"transition-[background-color,transform,opacity] duration-150 ease-og-spring",
|
|
482
494
|
"hover:bg-og-accent-strong active:scale-95",
|
|
@@ -521,33 +533,66 @@ export function ChatComposer({
|
|
|
521
533
|
);
|
|
522
534
|
}
|
|
523
535
|
|
|
524
|
-
/**
|
|
525
|
-
|
|
536
|
+
/**
|
|
537
|
+
* The danger confirm bar — reuses og-status-failed tokens (like the stop
|
|
538
|
+
* control). Inline (not a modal) but keyboard-complete: Escape cancels, focus
|
|
539
|
+
* moves to the primary action when it appears and returns to the composer on
|
|
540
|
+
* dismiss, and the primary button names the action rather than a bare "Confirm".
|
|
541
|
+
*/
|
|
542
|
+
function ConfirmBar({
|
|
543
|
+
command,
|
|
544
|
+
onCancel,
|
|
545
|
+
onConfirm,
|
|
546
|
+
returnFocusRef,
|
|
547
|
+
}: {
|
|
548
|
+
command: SlashCommand;
|
|
549
|
+
onCancel: () => void;
|
|
550
|
+
onConfirm: () => void;
|
|
551
|
+
/** Focus returns here when the bar dismisses (the composer textarea). */
|
|
552
|
+
returnFocusRef?: RefObject<HTMLTextAreaElement | null> | undefined;
|
|
553
|
+
}) {
|
|
554
|
+
const confirmRef = useRef<HTMLButtonElement | null>(null);
|
|
555
|
+
const descriptionId = useId();
|
|
556
|
+
useEffect(() => {
|
|
557
|
+
const returnTo = returnFocusRef?.current ?? null;
|
|
558
|
+
confirmRef.current?.focus();
|
|
559
|
+
return () => {
|
|
560
|
+
returnTo?.focus();
|
|
561
|
+
};
|
|
562
|
+
}, [returnFocusRef]);
|
|
526
563
|
return (
|
|
527
564
|
<div
|
|
528
565
|
role="alertdialog"
|
|
529
566
|
aria-label={`Confirm /${command.name}`}
|
|
567
|
+
aria-describedby={descriptionId}
|
|
530
568
|
data-testid="danger-confirm"
|
|
531
|
-
|
|
569
|
+
onKeyDown={(event) => {
|
|
570
|
+
if (event.key === "Escape") {
|
|
571
|
+
event.preventDefault();
|
|
572
|
+
event.stopPropagation();
|
|
573
|
+
onCancel();
|
|
574
|
+
}
|
|
575
|
+
}}
|
|
576
|
+
className="flex items-end justify-between gap-2 px-2.5 pb-2.5 pt-1"
|
|
532
577
|
>
|
|
533
|
-
<span className="px-1.5 text-
|
|
578
|
+
<span id={descriptionId} className="min-w-0 flex-1 px-1.5 text-og-sm text-og-status-failed">
|
|
534
579
|
Run <span className="font-mono">/{command.name}</span>? {command.description}
|
|
535
580
|
</span>
|
|
536
|
-
<span className="flex items-center gap-1.5">
|
|
581
|
+
<span className="flex shrink-0 items-center gap-1.5">
|
|
537
582
|
<button
|
|
538
583
|
type="button"
|
|
539
584
|
onClick={onCancel}
|
|
540
|
-
className="rounded-og-md border border-og-border bg-og-surface-2 px-2.5 py-1 text-
|
|
585
|
+
className="rounded-og-md border border-og-border bg-og-surface-2 px-2.5 py-1 text-og-sm text-og-fg-muted hover:bg-og-surface-3 pointer-coarse:min-h-10"
|
|
541
586
|
>
|
|
542
587
|
Cancel
|
|
543
588
|
</button>
|
|
544
589
|
<button
|
|
590
|
+
ref={confirmRef}
|
|
545
591
|
type="button"
|
|
546
|
-
autoFocus
|
|
547
592
|
onClick={onConfirm}
|
|
548
|
-
className="rounded-og-md border border-og-status-failed/50 bg-og-status-failed/15 px-2.5 py-1 text-
|
|
593
|
+
className="rounded-og-md border border-og-status-failed/50 bg-og-status-failed/15 px-2.5 py-1 text-og-sm text-og-status-failed hover:bg-og-status-failed/25 pointer-coarse:min-h-10"
|
|
549
594
|
>
|
|
550
|
-
|
|
595
|
+
Run /{command.name}
|
|
551
596
|
</button>
|
|
552
597
|
</span>
|
|
553
598
|
</div>
|
|
@@ -557,51 +602,73 @@ function ConfirmBar({ command, onCancel, onConfirm }: { command: SlashCommand; o
|
|
|
557
602
|
/**
|
|
558
603
|
* The attachment-chips strip rendered above the textarea while files are
|
|
559
604
|
* attached. Each chip shows an image preview (or a type icon), the filename,
|
|
560
|
-
* an upload/size/failed status line, and a remove control.
|
|
605
|
+
* an upload/size/failed status line, and a remove control. A failed upload
|
|
606
|
+
* surfaces its actual error (inline, full text on hover via `title`) and offers
|
|
607
|
+
* a retry alongside remove — a failed attachment never blocks send (only an
|
|
608
|
+
* in-progress upload does), and removing it clears the way. Styled with the
|
|
561
609
|
* package's og-* tokens so it themes in any consumer.
|
|
562
610
|
*/
|
|
563
|
-
function AttachmentChips({ attachments, onRemove }: {
|
|
611
|
+
function AttachmentChips({ attachments, onRemove, onRetry }: {
|
|
564
612
|
attachments: UseFileAttachmentsResult["attachments"];
|
|
565
613
|
onRemove: (id: string) => void;
|
|
614
|
+
onRetry?: ((id: string) => void) | undefined;
|
|
566
615
|
}) {
|
|
567
616
|
return (
|
|
568
617
|
<div className="flex flex-wrap gap-2 border-b border-og-border px-3 py-2">
|
|
569
|
-
{attachments.map((attachment) =>
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
<FileIcon className="size-4 shrink-0 text-og-fg-muted" />
|
|
583
|
-
)}
|
|
584
|
-
<div className="min-w-0 flex-1">
|
|
585
|
-
<div className="truncate font-medium text-og-fg">{attachment.name}</div>
|
|
586
|
-
<div className={cn(
|
|
587
|
-
"truncate text-[11px]",
|
|
588
|
-
attachment.status === "failed" ? "text-og-status-failed" : "text-og-fg-subtle",
|
|
618
|
+
{attachments.map((attachment) => {
|
|
619
|
+
const failed = attachment.status === "failed";
|
|
620
|
+
const statusText = attachment.status === "uploading"
|
|
621
|
+
? "Uploading"
|
|
622
|
+
: failed
|
|
623
|
+
? attachment.error || "Upload failed"
|
|
624
|
+
: formatBytes(attachment.sizeBytes);
|
|
625
|
+
return (
|
|
626
|
+
<div
|
|
627
|
+
key={attachment.id}
|
|
628
|
+
className={cn(
|
|
629
|
+
"flex min-w-0 max-w-[240px] items-center gap-2 rounded-og-md border px-2 py-1.5 text-og-sm",
|
|
630
|
+
failed ? "border-og-status-failed/40 bg-og-status-failed/10" : "border-og-border bg-og-surface-2",
|
|
589
631
|
)}
|
|
590
|
-
|
|
591
|
-
|
|
632
|
+
>
|
|
633
|
+
{attachment.previewUrl ? (
|
|
634
|
+
<img src={attachment.previewUrl} alt="" className="size-8 shrink-0 rounded object-cover" />
|
|
635
|
+
) : attachment.contentType.startsWith("image/") ? (
|
|
636
|
+
<ImageIcon className="size-4 shrink-0 text-og-fg-muted" />
|
|
637
|
+
) : (
|
|
638
|
+
<FileIcon className="size-4 shrink-0 text-og-fg-muted" />
|
|
639
|
+
)}
|
|
640
|
+
<div className="min-w-0 flex-1">
|
|
641
|
+
<div className="truncate font-medium text-og-fg">{attachment.name}</div>
|
|
642
|
+
<div
|
|
643
|
+
className={cn("truncate text-og-xs", failed ? "text-og-status-failed" : "text-og-fg-subtle")}
|
|
644
|
+
title={failed ? statusText : undefined}
|
|
645
|
+
>
|
|
646
|
+
{statusText}
|
|
647
|
+
</div>
|
|
592
648
|
</div>
|
|
649
|
+
{attachment.status === "uploading" ? <LoaderCircleIcon className="size-3.5 shrink-0 animate-og-spin" /> : null}
|
|
650
|
+
{failed && onRetry ? (
|
|
651
|
+
<button
|
|
652
|
+
type="button"
|
|
653
|
+
onClick={() => onRetry(attachment.id)}
|
|
654
|
+
className="shrink-0 rounded-og-xs p-1 text-og-fg-muted hover:bg-og-surface-1 hover:text-og-fg pointer-coarse:size-8"
|
|
655
|
+
aria-label={`Retry ${attachment.name}`}
|
|
656
|
+
title="Retry upload"
|
|
657
|
+
>
|
|
658
|
+
<RotateCwIcon className="size-3.5" />
|
|
659
|
+
</button>
|
|
660
|
+
) : null}
|
|
661
|
+
<button
|
|
662
|
+
type="button"
|
|
663
|
+
onClick={() => onRemove(attachment.id)}
|
|
664
|
+
className="shrink-0 rounded-og-xs p-1 text-og-fg-muted hover:bg-og-surface-1 hover:text-og-fg pointer-coarse:size-8"
|
|
665
|
+
aria-label={`Remove ${attachment.name}`}
|
|
666
|
+
>
|
|
667
|
+
<XIcon className="size-3.5" />
|
|
668
|
+
</button>
|
|
593
669
|
</div>
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
type="button"
|
|
597
|
-
onClick={() => onRemove(attachment.id)}
|
|
598
|
-
className="shrink-0 rounded-og-xs p-1 text-og-fg-muted hover:bg-og-surface-1 hover:text-og-fg"
|
|
599
|
-
aria-label={`Remove ${attachment.name}`}
|
|
600
|
-
>
|
|
601
|
-
<XIcon className="size-3.5" />
|
|
602
|
-
</button>
|
|
603
|
-
</div>
|
|
604
|
-
))}
|
|
670
|
+
);
|
|
671
|
+
})}
|
|
605
672
|
</div>
|
|
606
673
|
);
|
|
607
674
|
}
|
|
@@ -616,8 +683,8 @@ function HelpPanel({ commands, onClose }: { commands: readonly SlashCommand[]; o
|
|
|
616
683
|
className="mt-2 overflow-hidden rounded-og-lg border border-og-border bg-og-surface-2"
|
|
617
684
|
>
|
|
618
685
|
<div className="flex items-center justify-between border-b border-og-border px-3 py-1.5">
|
|
619
|
-
<span className="text-
|
|
620
|
-
<button type="button" onClick={onClose} className="text-
|
|
686
|
+
<span className="text-og-sm font-medium text-og-fg">Commands</span>
|
|
687
|
+
<button type="button" onClick={onClose} className="text-og-xs text-og-fg-subtle hover:text-og-fg">
|
|
621
688
|
Close
|
|
622
689
|
</button>
|
|
623
690
|
</div>
|
|
@@ -626,13 +693,13 @@ function HelpPanel({ commands, onClose }: { commands: readonly SlashCommand[]; o
|
|
|
626
693
|
const hint = argHint(command.args);
|
|
627
694
|
return (
|
|
628
695
|
<li key={command.name} className="flex items-baseline gap-2 px-3 py-1">
|
|
629
|
-
<span className="font-mono text-
|
|
696
|
+
<span className="font-mono text-og-sm text-og-accent">
|
|
630
697
|
/{command.name}
|
|
631
698
|
{hint ? <span className="ml-1 text-og-fg-subtle">{hint}</span> : null}
|
|
632
699
|
</span>
|
|
633
|
-
<span className="text-
|
|
700
|
+
<span className="text-og-sm text-og-fg-muted">{command.description}</span>
|
|
634
701
|
{command.danger ? (
|
|
635
|
-
<span className="ml-auto rounded-og-xs bg-og-status-failed/15 px-1 text-
|
|
702
|
+
<span className="ml-auto rounded-og-xs bg-og-status-failed/15 px-1 text-og-xs uppercase tracking-wide text-og-status-failed">
|
|
636
703
|
danger
|
|
637
704
|
</span>
|
|
638
705
|
) : null}
|
|
@@ -278,38 +278,38 @@ export function CodeEditor({
|
|
|
278
278
|
style={editorVars}
|
|
279
279
|
>
|
|
280
280
|
{/* Save bar: dirty indicator + status + the explicit Save button. */}
|
|
281
|
-
<div className="flex shrink-0 items-center gap-2 border-b border-
|
|
281
|
+
<div className="flex shrink-0 items-center gap-2 border-b border-og-border bg-og-surface-1 px-2 py-1">
|
|
282
282
|
<span
|
|
283
283
|
className={cn(
|
|
284
284
|
"size-1.5 shrink-0 rounded-full transition-colors",
|
|
285
285
|
readOnly
|
|
286
286
|
? "bg-transparent"
|
|
287
287
|
: dirty
|
|
288
|
-
? "bg-
|
|
289
|
-
: "bg-
|
|
288
|
+
? "bg-og-status-running"
|
|
289
|
+
: "bg-og-status-idle",
|
|
290
290
|
)}
|
|
291
291
|
title={readOnly ? "read-only" : dirty ? "unsaved changes" : "saved"}
|
|
292
292
|
/>
|
|
293
|
-
<span className="truncate font-
|
|
293
|
+
<span className="truncate font-og-mono text-og-xs text-og-fg-muted">
|
|
294
294
|
{fileName}
|
|
295
295
|
{dirty && !readOnly ? " •" : ""}
|
|
296
296
|
</span>
|
|
297
297
|
<div className="ml-auto flex shrink-0 items-center gap-2">
|
|
298
298
|
{saveError && (
|
|
299
299
|
<span
|
|
300
|
-
className="max-w-[220px] truncate text-
|
|
300
|
+
className="max-w-[220px] truncate text-og-xs text-og-status-failed"
|
|
301
301
|
title={saveError.message}
|
|
302
302
|
>
|
|
303
303
|
{saveError.message}
|
|
304
304
|
</span>
|
|
305
305
|
)}
|
|
306
306
|
{!saveError && savedTick && (
|
|
307
|
-
<span className="text-
|
|
307
|
+
<span className="text-og-xs text-og-status-idle">
|
|
308
308
|
Saved
|
|
309
309
|
</span>
|
|
310
310
|
)}
|
|
311
311
|
{readOnly ? (
|
|
312
|
-
<span className="text-
|
|
312
|
+
<span className="text-og-xs uppercase tracking-wide text-og-fg-subtle">
|
|
313
313
|
Read-only
|
|
314
314
|
</span>
|
|
315
315
|
) : (
|
|
@@ -318,10 +318,10 @@ export function CodeEditor({
|
|
|
318
318
|
onClick={() => void save()}
|
|
319
319
|
disabled={saving || !dirty}
|
|
320
320
|
className={cn(
|
|
321
|
-
"flex items-center gap-1 rounded-
|
|
321
|
+
"flex items-center gap-1 rounded-og-sm border border-og-border px-1.5 py-0.5 text-og-xs pointer-coarse:min-h-10",
|
|
322
322
|
saving || !dirty
|
|
323
|
-
? "cursor-default text-
|
|
324
|
-
: "text-
|
|
323
|
+
? "cursor-default text-og-fg-subtle opacity-60"
|
|
324
|
+
: "text-og-fg hover:bg-og-accent-soft",
|
|
325
325
|
)}
|
|
326
326
|
title="Save (⌘/Ctrl+S)"
|
|
327
327
|
>
|
|
@@ -347,7 +347,7 @@ export function CodeEditor({
|
|
|
347
347
|
basicSetup={true}
|
|
348
348
|
extensions={cmExtensions}
|
|
349
349
|
height="100%"
|
|
350
|
-
className="og-cm-editor min-h-full text-
|
|
350
|
+
className="og-cm-editor min-h-full text-og-sm"
|
|
351
351
|
onChange={
|
|
352
352
|
readOnly
|
|
353
353
|
? undefined
|
|
@@ -365,8 +365,8 @@ export function CodeEditor({
|
|
|
365
365
|
|
|
366
366
|
/** og-* themed CSS-var overrides handed to CodeMirror's container. */
|
|
367
367
|
const editorVars = {
|
|
368
|
-
"--og-cm-bg": "var(--og-color-bg
|
|
369
|
-
fontFamily: "var(--og-font-mono
|
|
368
|
+
"--og-cm-bg": "var(--og-color-bg)",
|
|
369
|
+
fontFamily: "var(--og-font-mono)",
|
|
370
370
|
} as CSSProperties;
|
|
371
371
|
|
|
372
372
|
function PlainTextarea({
|
|
@@ -384,14 +384,14 @@ function PlainTextarea({
|
|
|
384
384
|
readOnly={readOnly}
|
|
385
385
|
spellCheck={false}
|
|
386
386
|
onChange={(e) => onChange(e.target.value)}
|
|
387
|
-
className="h-full w-full resize-none bg-transparent p-2 font-
|
|
387
|
+
className="h-full w-full resize-none bg-transparent p-2 font-og-mono text-og-sm text-og-fg outline-none"
|
|
388
388
|
/>
|
|
389
389
|
);
|
|
390
390
|
}
|
|
391
391
|
|
|
392
392
|
function EditorSkeleton() {
|
|
393
393
|
return (
|
|
394
|
-
<div className="p-3 text-
|
|
394
|
+
<div className="p-3 text-og-sm text-og-fg-subtle">
|
|
395
395
|
Loading editor…
|
|
396
396
|
</div>
|
|
397
397
|
);
|