@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.
Files changed (42) hide show
  1. package/dist/{chunk-DEW2ZNF2.js → chunk-5C7RGAWA.js} +70 -37
  2. package/dist/chunk-5C7RGAWA.js.map +1 -0
  3. package/dist/index.d.ts +29 -5
  4. package/dist/index.js +1061 -618
  5. package/dist/index.js.map +1 -1
  6. package/dist/{machines-BD6h9P_s.d.ts → machines-Bv9tG7qZ.d.ts} +1 -1
  7. package/dist/machines.d.ts +1 -1
  8. package/dist/machines.js +1 -1
  9. package/package.json +2 -2
  10. package/src/components/chat-composer.tsx +127 -60
  11. package/src/components/code-editor.tsx +15 -15
  12. package/src/components/desktop-viewer.tsx +40 -36
  13. package/src/components/diff-view.tsx +22 -22
  14. package/src/components/enrollment-consent.tsx +13 -13
  15. package/src/components/file-browser.tsx +74 -67
  16. package/src/components/fleet-tile.tsx +3 -3
  17. package/src/components/machine-card.tsx +6 -6
  18. package/src/components/machine-status-pill.tsx +3 -3
  19. package/src/components/machines-dashboard.tsx +28 -14
  20. package/src/components/markdown.tsx +6 -6
  21. package/src/components/message-timeline.tsx +75 -4
  22. package/src/components/pierre-diff.tsx +9 -10
  23. package/src/components/pierre-file.tsx +8 -8
  24. package/src/components/sandbox-files.tsx +37 -40
  25. package/src/components/sandbox-terminal.tsx +9 -11
  26. package/src/components/session-status.tsx +2 -2
  27. package/src/components/workspace-dock.tsx +153 -49
  28. package/src/hooks/use-file-attachments.ts +45 -15
  29. package/src/index.ts +2 -2
  30. package/src/lib/format.ts +32 -0
  31. package/src/lib/xterm-theme.ts +8 -11
  32. package/src/timeline/activity-rail.tsx +1 -1
  33. package/src/timeline/parsers.ts +104 -0
  34. package/src/timeline/projection.ts +135 -3
  35. package/src/timeline/screenshot-lightbox.tsx +1 -1
  36. package/src/timeline/shared.tsx +4 -1
  37. package/src/timeline/tool-diff.tsx +1 -1
  38. package/src/timeline/tool-renderers.tsx +46 -7
  39. package/src/timeline/turn-summary.tsx +21 -3
  40. package/src/timeline/types.ts +2 -0
  41. package/styles/tokens.css +8 -0
  42. 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 Modal sandbox, each with its connection-status pill, state badges,
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`.
@@ -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-BD6h9P_s.js';
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
@@ -13,7 +13,7 @@ import {
13
13
  SharedMachineDisclosure,
14
14
  connectionStatusForState,
15
15
  useMachines
16
- } from "./chunk-DEW2ZNF2.js";
16
+ } from "./chunk-5C7RGAWA.js";
17
17
  export {
18
18
  CONNECTION_STATUS_META,
19
19
  ConnectionDot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/react",
3
- "version": "0.6.1",
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.0",
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
- <div className={cn("og-root", className)}>
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 attachments={attachments.attachments} onRemove={attachments.remove} />
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
- "block w-full resize-none bg-transparent px-4 pt-3.5 pb-1 text-[15px] leading-6",
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-center justify-between gap-2 px-2.5 pb-2.5 pt-1">
405
+ <div className="flex items-end gap-2 px-2.5 pb-2.5 pt-1">
397
406
  {attachments || models || controlsStart ? (
398
- <span className="flex min-w-0 items-center gap-1.5">
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-[11px] text-og-fg-subtle max-sm:hidden">
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
- /** The danger confirm bar — reuses og-status-failed tokens (like the stop control). */
525
- function ConfirmBar({ command, onCancel, onConfirm }: { command: SlashCommand; onCancel: () => void; onConfirm: () => void }) {
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
- className="flex items-center justify-between gap-2 px-2.5 pb-2.5 pt-1"
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-[12px] text-og-status-failed">
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-[12px] text-og-fg-muted hover:bg-og-surface-3"
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-[12px] text-og-status-failed hover:bg-og-status-failed/25"
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
- Confirm
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. Styled with the
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
- <div
571
- key={attachment.id}
572
- className={cn(
573
- "flex min-w-0 max-w-[240px] items-center gap-2 rounded-og-md border px-2 py-1.5",
574
- "border-og-border bg-og-surface-2 text-xs",
575
- )}
576
- >
577
- {attachment.previewUrl ? (
578
- <img src={attachment.previewUrl} alt="" className="size-8 shrink-0 rounded object-cover" />
579
- ) : attachment.contentType.startsWith("image/") ? (
580
- <ImageIcon className="size-4 shrink-0 text-og-fg-muted" />
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
- {attachment.status === "uploading" ? "Uploading" : attachment.status === "failed" ? "Upload failed" : formatBytes(attachment.sizeBytes)}
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
- {attachment.status === "uploading" ? <LoaderCircleIcon className="size-3.5 shrink-0 animate-og-spin" /> : null}
595
- <button
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-[12px] font-medium text-og-fg">Commands</span>
620
- <button type="button" onClick={onClose} className="text-[11px] text-og-fg-subtle hover:text-og-fg">
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-[12px] text-og-accent">
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-[12px] text-og-fg-muted">{command.description}</span>
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-[10px] uppercase tracking-wide text-og-status-failed">
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-[color:var(--og-color-border,var(--color-border,#2a2a2a))] bg-[color:var(--og-color-surface-1,var(--color-surface,#161616))] px-2 py-1">
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-[color:var(--og-color-status-running,var(--color-warning,#d29922))]"
289
- : "bg-[color:var(--og-color-status-idle,var(--color-success,#3fb950))]",
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-[family-name:var(--og-font-mono,var(--font-mono,monospace))] text-[11px] text-[color:var(--og-color-fg-muted,var(--color-fg-muted,#aaa))]">
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-[10px] text-[color:var(--og-color-danger,var(--color-danger,#f85149))]"
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-[10px] text-[color:var(--og-color-status-idle,var(--color-success,#3fb950))]">
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-[10px] uppercase tracking-wide text-[color:var(--og-color-fg-subtle,var(--color-fg-subtle,#888))]">
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-[var(--og-radius-sm,4px)] border border-[color:var(--og-color-border,var(--color-border,#2a2a2a))] px-1.5 py-0.5 text-[10px]",
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-[color:var(--og-color-fg-subtle,var(--color-fg-subtle,#888))] opacity-60"
324
- : "text-[color:var(--og-color-fg,var(--color-fg,#e6e6e6))] hover:bg-[color:var(--og-color-accent-soft,var(--color-surface-2,#222))]",
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-[12.5px]"
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, #0d0d0d)",
369
- fontFamily: "var(--og-font-mono, var(--font-mono, monospace))",
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-[family-name:var(--og-font-mono,var(--font-mono,monospace))] text-[12px] leading-[18px] text-[color:var(--og-color-fg,var(--color-fg,#e6e6e6))] outline-none"
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-xs text-[color:var(--og-color-fg-subtle,var(--color-fg-subtle,#888))]">
394
+ <div className="p-3 text-og-sm text-og-fg-subtle">
395
395
  Loading editor…
396
396
  </div>
397
397
  );