@opengeni/react 0.12.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +20 -14
  2. package/dist/chunk-TOJR776I.js +2280 -0
  3. package/dist/chunk-TOJR776I.js.map +1 -0
  4. package/dist/index.d.ts +1040 -363
  5. package/dist/index.js +10938 -6281
  6. package/dist/index.js.map +1 -1
  7. package/dist/{machines-BeZ3bD3t.d.ts → machines-BpdwuQcD.d.ts} +134 -14
  8. package/dist/machines.d.ts +1 -1
  9. package/dist/machines.js +23 -1
  10. package/package.json +20 -12
  11. package/src/approvals.ts +16 -4
  12. package/src/client.ts +41 -5
  13. package/src/commands/index.ts +1 -7
  14. package/src/commands/registry.ts +46 -12
  15. package/src/components/chat-composer.tsx +417 -69
  16. package/src/components/code-editor.tsx +28 -36
  17. package/src/components/command-palette.tsx +26 -5
  18. package/src/components/desktop-viewer.tsx +29 -16
  19. package/src/components/diff-view.tsx +27 -189
  20. package/src/components/enrollment-consent.tsx +28 -10
  21. package/src/components/enrollment-device-flow.tsx +8 -5
  22. package/src/components/file-browser.tsx +190 -56
  23. package/src/components/fleet-tile.tsx +13 -4
  24. package/src/components/machine-card.tsx +93 -15
  25. package/src/components/machine-dock-bar.tsx +20 -8
  26. package/src/components/machine-health-pill.tsx +68 -0
  27. package/src/components/machine-metrics.tsx +28 -19
  28. package/src/components/machine-status-pill.tsx +26 -5
  29. package/src/components/machines/health.ts +146 -0
  30. package/src/components/machines/machine-detail.tsx +220 -0
  31. package/src/components/machines/metric-history-chart.tsx +298 -0
  32. package/src/components/machines/metric-sparkline.tsx +76 -0
  33. package/src/components/machines/series.ts +113 -0
  34. package/src/components/machines-dashboard.tsx +21 -3
  35. package/src/components/markdown.tsx +39 -9
  36. package/src/components/message-timeline.tsx +633 -109
  37. package/src/components/pierre-diff.tsx +83 -15
  38. package/src/components/pierre-file.tsx +10 -9
  39. package/src/components/queue-surface.tsx +578 -0
  40. package/src/components/sandbox-files.tsx +193 -225
  41. package/src/components/sandbox-terminal.tsx +256 -88
  42. package/src/components/sandbox-workspace.tsx +957 -0
  43. package/src/components/session-status.tsx +22 -2
  44. package/src/components/workbench-changes.tsx +585 -0
  45. package/src/components/workspace-dock.tsx +213 -59
  46. package/src/hooks/internal.ts +5 -2
  47. package/src/hooks/use-available-models.ts +7 -2
  48. package/src/hooks/use-billing-usage.ts +4 -1
  49. package/src/hooks/use-codex-accounts.ts +74 -44
  50. package/src/hooks/use-composer.ts +414 -63
  51. package/src/hooks/use-environments.ts +92 -47
  52. package/src/hooks/use-file-attachments.ts +101 -55
  53. package/src/hooks/use-goal.ts +52 -16
  54. package/src/hooks/use-machine-chip.ts +134 -0
  55. package/src/hooks/use-machines.ts +34 -16
  56. package/src/hooks/use-packs.ts +24 -19
  57. package/src/hooks/use-relay-frame-stream.ts +5 -2
  58. package/src/hooks/use-rigs.ts +335 -0
  59. package/src/hooks/use-sandbox-files.ts +213 -39
  60. package/src/hooks/use-sandbox-git.ts +188 -11
  61. package/src/hooks/use-sandbox-terminal.ts +18 -14
  62. package/src/hooks/use-scheduled-tasks.ts +10 -2
  63. package/src/hooks/use-session-capabilities.ts +77 -20
  64. package/src/hooks/use-session-control.ts +50 -23
  65. package/src/hooks/use-session-events.ts +50 -21
  66. package/src/hooks/use-session-lineage.ts +128 -0
  67. package/src/hooks/use-session.ts +53 -30
  68. package/src/hooks/use-slash-commands.ts +6 -4
  69. package/src/hooks/use-turn-queue.ts +202 -147
  70. package/src/hooks/use-windowed-sections.ts +204 -0
  71. package/src/hooks/use-workspace-capture.ts +233 -0
  72. package/src/hooks/use-workspace-edit.ts +231 -0
  73. package/src/hooks/use-workspace-sessions.ts +48 -5
  74. package/src/hooks/use-workspaces.ts +18 -15
  75. package/src/index.ts +133 -23
  76. package/src/lib/format.ts +3 -3
  77. package/src/lib/xterm-renderer.ts +65 -0
  78. package/src/lib/xterm-theme.ts +224 -18
  79. package/src/machines.ts +29 -3
  80. package/src/provider.tsx +195 -6
  81. package/src/timeline/activity-rail.tsx +213 -54
  82. package/src/timeline/disclosure-context.tsx +12 -2
  83. package/src/timeline/index.ts +21 -2
  84. package/src/timeline/parsers.ts +87 -16
  85. package/src/timeline/projection.ts +412 -87
  86. package/src/timeline/shared.tsx +111 -24
  87. package/src/timeline/tool-diff.tsx +24 -20
  88. package/src/timeline/tool-renderers.tsx +98 -21
  89. package/src/timeline/turn-summary.tsx +76 -23
  90. package/src/timeline/types.ts +96 -12
  91. package/styles/index.css +22 -0
  92. package/styles/tokens.css +2 -2
  93. package/dist/chunk-5TIXRCSI.js +0 -1291
  94. package/dist/chunk-5TIXRCSI.js.map +0 -1
package/src/index.ts CHANGED
@@ -1,3 +1,6 @@
1
+ // oxlint-disable-next-line typescript/triple-slash-reference -- package consumers must load the ambient type for this optional untyped peer without emitting a runtime import.
2
+ /// <reference path="./types/external.d.ts" />
3
+
1
4
  // @opengeni/react — hooks + styled components on @opengeni/sdk.
2
5
  //
3
6
  // Import the styles once in your Tailwind entry CSS:
@@ -12,31 +15,74 @@ export type { ClientOverride, OpenGeniContextValue, OpenGeniProviderProps } from
12
15
  export { useSession, isTitleEvent } from "./hooks/use-session";
13
16
  export type { UseSessionOptions, UseSessionResult } from "./hooks/use-session";
14
17
  export { useSessionEvents } from "./hooks/use-session-events";
15
- export type { SessionEventsConnectionState, UseSessionEventsOptions, UseSessionEventsResult } from "./hooks/use-session-events";
16
- export { useComposer, composeSendInput, shouldSubmitOnKey, FILE_ONLY_MESSAGE_TEXT } from "./hooks/use-composer";
17
- export type { ComposerMode, ComposerSendExtras, ComposerState, UseComposerOptions } from "./hooks/use-composer";
18
- export { useFileAttachments } from "./hooks/use-file-attachments";
19
- export type { FileAttachment, UseFileAttachmentsOptions, UseFileAttachmentsResult } from "./hooks/use-file-attachments";
18
+ export type {
19
+ SessionEventsConnectionState,
20
+ UseSessionEventsOptions,
21
+ UseSessionEventsResult,
22
+ } from "./hooks/use-session-events";
20
23
  export {
21
- useTurnQueue,
22
- isTurnQueueEvent,
23
- queueFromTurns,
24
- activeTurnFromTurns,
25
- applyTurnEdit,
26
- applyTurnReorder,
27
- applyTurnRemoval,
24
+ useComposer,
25
+ composeSendInput,
26
+ shouldSteerOnKey,
27
+ shouldSubmitOnKey,
28
+ FILE_ONLY_MESSAGE_TEXT,
29
+ } from "./hooks/use-composer";
30
+ export type { ComposerSendExtras, ComposerState, UseComposerOptions } from "./hooks/use-composer";
31
+ export { useFileAttachments } from "./hooks/use-file-attachments";
32
+ export type {
33
+ FileAttachment,
34
+ UseFileAttachmentsOptions,
35
+ UseFileAttachmentsResult,
36
+ } from "./hooks/use-file-attachments";
37
+ export { useTurnQueue, isTurnQueueEvent } from "./hooks/use-turn-queue";
38
+ export type {
39
+ QueueMutationKind,
40
+ UseTurnQueueOptions,
41
+ UseTurnQueueResult,
28
42
  } from "./hooks/use-turn-queue";
29
- export type { UseTurnQueueOptions, UseTurnQueueResult } from "./hooks/use-turn-queue";
43
+ export { QueueSurface } from "./components/queue-surface";
44
+ export type { QueueSurfaceProps } from "./components/queue-surface";
45
+ export { OPEN_WORKSTREAM_CONTROL_EVENT } from "./components/chat-composer";
30
46
  export { useGoal, isGoalEvent } from "./hooks/use-goal";
31
47
  export type { UseGoalOptions, UseGoalResult } from "./hooks/use-goal";
32
48
  export { useSessionControl } from "./hooks/use-session-control";
33
- export type { UseSessionControlOptions, UseSessionControlResult } from "./hooks/use-session-control";
49
+ export type {
50
+ UseSessionControlOptions,
51
+ UseSessionControlResult,
52
+ } from "./hooks/use-session-control";
34
53
  export { useScheduledTasks } from "./hooks/use-scheduled-tasks";
35
- export type { UseScheduledTasksOptions, UseScheduledTasksResult } from "./hooks/use-scheduled-tasks";
54
+ export type {
55
+ UseScheduledTasksOptions,
56
+ UseScheduledTasksResult,
57
+ } from "./hooks/use-scheduled-tasks";
36
58
  export { useWorkspaceSessions } from "./hooks/use-workspace-sessions";
37
- export type { UseWorkspaceSessionsOptions, UseWorkspaceSessionsResult } from "./hooks/use-workspace-sessions";
38
- export { useEnvironments } from "./hooks/use-environments";
39
- export type { UseEnvironmentsOptions, UseEnvironmentsResult } from "./hooks/use-environments";
59
+ export type {
60
+ UseWorkspaceSessionsOptions,
61
+ UseWorkspaceSessionsResult,
62
+ } from "./hooks/use-workspace-sessions";
63
+ export { useSessionLineage, isLineageRefreshEvent } from "./hooks/use-session-lineage";
64
+ export type {
65
+ UseSessionLineageOptions,
66
+ UseSessionLineageResult,
67
+ } from "./hooks/use-session-lineage";
68
+ export { useVariableSets, useEnvironments } from "./hooks/use-environments";
69
+ export type {
70
+ UseVariableSetsOptions,
71
+ UseVariableSetsResult,
72
+ UseEnvironmentsOptions,
73
+ UseEnvironmentsResult,
74
+ } from "./hooks/use-environments";
75
+ export { useRigs, useRig, useRigVersions, useRigChanges } from "./hooks/use-rigs";
76
+ export type {
77
+ UseRigsOptions,
78
+ UseRigsResult,
79
+ UseRigOptions,
80
+ UseRigResult,
81
+ UseRigVersionsOptions,
82
+ UseRigVersionsResult,
83
+ UseRigChangesOptions,
84
+ UseRigChangesResult,
85
+ } from "./hooks/use-rigs";
40
86
  export { usePacks } from "./hooks/use-packs";
41
87
  export type { UsePacksOptions, UsePacksResult } from "./hooks/use-packs";
42
88
  export { useWorkspaces } from "./hooks/use-workspaces";
@@ -44,7 +90,10 @@ export type { UseWorkspacesOptions, UseWorkspacesResult } from "./hooks/use-work
44
90
  export { useBillingUsage } from "./hooks/use-billing-usage";
45
91
  export type { UseBillingUsageOptions, UseBillingUsageResult } from "./hooks/use-billing-usage";
46
92
  export { useAvailableModels } from "./hooks/use-available-models";
47
- export type { UseAvailableModelsOptions, UseAvailableModelsResult } from "./hooks/use-available-models";
93
+ export type {
94
+ UseAvailableModelsOptions,
95
+ UseAvailableModelsResult,
96
+ } from "./hooks/use-available-models";
48
97
 
49
98
  // Sandbox surfacing (Phase 5): capability negotiation + terminal/files/diff/desktop
50
99
  export { useSessionCapabilities } from "./hooks/use-session-capabilities";
@@ -84,6 +133,27 @@ export type {
84
133
  export { useSandboxGit } from "./hooks/use-sandbox-git";
85
134
  export type { UseSandboxGitOptions, UseSandboxGitResult } from "./hooks/use-sandbox-git";
86
135
 
136
+ // Workbench v2 turn-end capture (the cold-paint data layer)
137
+ export { useWorkspaceCapture } from "./hooks/use-workspace-capture";
138
+ export type {
139
+ UseWorkspaceCaptureOptions,
140
+ UseWorkspaceCaptureResult,
141
+ } from "./hooks/use-workspace-capture";
142
+ export { useWorkspaceEdit } from "./hooks/use-workspace-edit";
143
+ export type {
144
+ UseWorkspaceEditOptions,
145
+ UseWorkspaceEditResult,
146
+ WorkspaceEditConflict,
147
+ WorkspaceEditState,
148
+ } from "./hooks/use-workspace-edit";
149
+ export { deriveMachineChip, formatAsOf, useMachineChip } from "./hooks/use-machine-chip";
150
+ export type {
151
+ DeriveMachineChipInput,
152
+ MachineChip,
153
+ MachineChipState,
154
+ UseMachineChipOptions,
155
+ } from "./hooks/use-machine-chip";
156
+
87
157
  // Pending-approvals projection
88
158
  export { approvalsFromRequiresAction, projectPendingApprovals } from "./approvals";
89
159
  export type { PendingApproval } from "./approvals";
@@ -100,6 +170,7 @@ export {
100
170
  export type {
101
171
  ActivityItem,
102
172
  AgentMessageItem,
173
+ AuthNeededItem,
103
174
  GoalItem,
104
175
  NoticeItem,
105
176
  ReasoningItem,
@@ -114,7 +185,12 @@ export type {
114
185
  } from "./timeline";
115
186
 
116
187
  // Tool-renderer registry + the per-tool renderers (the timeline's extension API)
117
- export { createDefaultToolRegistry, createToolRegistry, defaultToolRegistry, rawTypeOf } from "./timeline";
188
+ export {
189
+ createDefaultToolRegistry,
190
+ createToolRegistry,
191
+ defaultToolRegistry,
192
+ rawTypeOf,
193
+ } from "./timeline";
118
194
  export type {
119
195
  CreateToolRegistryOptions,
120
196
  ToolRegistry,
@@ -206,7 +282,11 @@ export type { MessageTimelineProps } from "./components/message-timeline";
206
282
  export { Markdown } from "./components/markdown";
207
283
  export type { MarkdownProps } from "./components/markdown";
208
284
  export { SessionStatus, StatusDot, SESSION_STATUS_META } from "./components/session-status";
209
- export type { SessionStatusProps, StatusDotProps, SessionStatusMeta } from "./components/session-status";
285
+ export type {
286
+ SessionStatusProps,
287
+ StatusDotProps,
288
+ SessionStatusMeta,
289
+ } from "./components/session-status";
210
290
  export { FleetTile, sessionDisplayTitle } from "./components/fleet-tile";
211
291
  export type { FleetTileProps } from "./components/fleet-tile";
212
292
 
@@ -230,12 +310,33 @@ export type { DesktopViewerProps } from "./components/desktop-viewer";
230
310
  export { WorkspaceDock } from "./components/workspace-dock";
231
311
  export type { WorkspaceDockProps, WorkspaceTab } from "./components/workspace-dock";
232
312
 
313
+ // The embeddable dock "brain" (Workbench v2, M4): the whole session workspace as
314
+ // one component + the tab-building hook + the pre-paint default-tab helper.
315
+ export {
316
+ SandboxWorkspace,
317
+ useSandboxWorkspaceTabs,
318
+ initialWorkspaceTab,
319
+ WORKBENCH_TAB_CHANGES,
320
+ WORKBENCH_TAB_FILES,
321
+ } from "./components/sandbox-workspace";
322
+ export type {
323
+ SandboxWorkspaceProps,
324
+ UseSandboxWorkspaceTabsOptions,
325
+ UseSandboxWorkspaceTabsResult,
326
+ WorkspaceMachine,
327
+ WorkspaceNotification,
328
+ } from "./components/sandbox-workspace";
329
+
233
330
  // Connected-machine UI moved to the "@opengeni/react/machines" subpath; re-exported
234
331
  // here for back-compat (#144).
235
332
  export * from "./machines";
236
333
  // Multi-account Codex (P1): accounts list + active-switch hook.
237
334
  export { useCodexAccounts, isCodexAccountEvent } from "./hooks/use-codex-accounts";
238
- export type { CodexAccountsClientLike, UseCodexAccountsOptions, UseCodexAccountsResult } from "./hooks/use-codex-accounts";
335
+ export type {
336
+ CodexAccountsClientLike,
337
+ UseCodexAccountsOptions,
338
+ UseCodexAccountsResult,
339
+ } from "./hooks/use-codex-accounts";
239
340
 
240
341
  // Sandbox helpers
241
342
  export { gitFileDiffToPatch } from "./lib/git-patch";
@@ -243,4 +344,13 @@ export { xtermThemeFromTokens } from "./lib/xterm-theme";
243
344
 
244
345
  // Utilities
245
346
  export { cn } from "./lib/cn";
246
- export { CREDIT_EXHAUSTION_MESSAGE, formatBytes, formatRelativeTime, humanizeFailureReason, isCreditExhaustion, stringifyPayload, truncate, tryParseJson } from "./lib/format";
347
+ export {
348
+ CREDIT_EXHAUSTION_MESSAGE,
349
+ formatBytes,
350
+ formatRelativeTime,
351
+ humanizeFailureReason,
352
+ isCreditExhaustion,
353
+ stringifyPayload,
354
+ truncate,
355
+ tryParseJson,
356
+ } from "./lib/format";
package/src/lib/format.ts CHANGED
@@ -140,13 +140,13 @@ export function humanizeFailureReason(reason: string | null): string | null {
140
140
  normalized.includes("invalid api key") ||
141
141
  normalized.includes("invalid_api_key") ||
142
142
  normalized.includes("platform.openai.com/account/api-keys") ||
143
- (normalized.includes("401") && (normalized.includes("api key") || normalized.includes("unauthorized")));
143
+ (normalized.includes("401") &&
144
+ (normalized.includes("api key") || normalized.includes("unauthorized")));
144
145
  if (authFailure) {
145
146
  return "The model provider rejected this deployment's engine credentials. Sending messages won't help until the deployment's engine configuration is fixed.";
146
147
  }
147
148
  const quotaFailure =
148
- normalized.includes("insufficient_quota") ||
149
- normalized.includes("exceeded your current quota");
149
+ normalized.includes("insufficient_quota") || normalized.includes("exceeded your current quota");
150
150
  if (quotaFailure) {
151
151
  return "The model provider refused the request: this deployment's provider quota is exhausted.";
152
152
  }
@@ -0,0 +1,65 @@
1
+ /* ----------------------------------------------------------------------------
2
+ xterm renderer selection ladder: WebGL → 2D canvas → DOM.
3
+
4
+ WebGL is the fast path (GPU-composited cells — the whole point of the burst
5
+ responsiveness win). But a WebGL context can fail to init (headless / no GPU /
6
+ blocklisted driver) or be LOST at runtime (GPU reset, tab backgrounded). On
7
+ either, we dispose the addon and step down one tier so the terminal keeps
8
+ painting via the 2D canvas addon, and failing that xterm's built-in DOM
9
+ renderer (no addon).
10
+
11
+ `attachRenderer` is pure over its `loaders` (each closes over the xterm
12
+ instance and constructs+loads its addon, throwing on failure), so the ladder
13
+ + context-loss downgrade unit-test without a real WebGL context.
14
+ -------------------------------------------------------------------------- */
15
+
16
+ export type RendererTier = "webgl" | "canvas" | "dom";
17
+
18
+ export type RendererAddon = { dispose: () => void };
19
+
20
+ /** A loader constructs + `loadAddon`s its renderer, wiring `onLoss` to the
21
+ * addon's context-loss event, and throws if the renderer can't initialize. */
22
+ export type RendererLoaders = {
23
+ webgl?: ((onLoss: () => void) => Promise<RendererAddon>) | undefined;
24
+ canvas?: (() => Promise<RendererAddon>) | undefined;
25
+ };
26
+
27
+ const ORDER: RendererTier[] = ["webgl", "canvas", "dom"];
28
+
29
+ /** The next lower renderer tier, or null at the bottom (DOM). Pure. */
30
+ export function nextRendererTier(tier: RendererTier): RendererTier | null {
31
+ const i = ORDER.indexOf(tier);
32
+ return i >= 0 && i < ORDER.length - 1 ? ORDER[i + 1]! : null;
33
+ }
34
+
35
+ /**
36
+ * Attach the highest renderer tier at or below `prefer` that initializes,
37
+ * reporting the settled tier via `onTier`. On a WebGL/canvas context loss the
38
+ * loader's `onLoss` re-enters at the next tier down, so `onTier` is called again
39
+ * with the downgraded tier. Returns the tier chosen on this pass.
40
+ */
41
+ export async function attachRenderer(
42
+ prefer: RendererTier,
43
+ loaders: RendererLoaders,
44
+ onTier: (tier: RendererTier) => void,
45
+ ): Promise<RendererTier> {
46
+ const start = Math.max(0, ORDER.indexOf(prefer));
47
+ for (let i = start; i < ORDER.length; i++) {
48
+ const tier = ORDER[i]!;
49
+ if (tier === "dom") break;
50
+ const loader = tier === "webgl" ? loaders.webgl : loaders.canvas;
51
+ if (!loader) continue;
52
+ try {
53
+ const downgrade = nextRendererTier(tier) ?? "dom";
54
+ await loader(() => {
55
+ void attachRenderer(downgrade, loaders, onTier);
56
+ });
57
+ onTier(tier);
58
+ return tier;
59
+ } catch {
60
+ // Initialization failed — fall through to the next tier down.
61
+ }
62
+ }
63
+ onTier("dom");
64
+ return "dom";
65
+ }
@@ -1,31 +1,237 @@
1
1
  import type { XtermTheme } from "../components/sandbox-terminal";
2
2
 
3
+ /* ----------------------------------------------------------------------------
4
+ xterm theming + font resolution from the `--og-*` token system.
5
+
6
+ xterm paints glyphs into a canvas (WebGL / 2D) and builds its font strings by
7
+ hand, so it can consume NEITHER CSS custom properties (`var(--og-font-mono)`
8
+ never resolves in a canvas font string) NOR a partial palette (an unset ANSI
9
+ slot falls back to xterm's stock VGA color). This module resolves both to
10
+ concrete values: a full 16-color ANSI ITheme designed against the og palette
11
+ for BOTH themes, and a concrete monospace family + px size.
12
+
13
+ The color-building + font-resolution cores are pure over a token `read`
14
+ function so they unit-test without a DOM; thin `*FromTokens` / `resolve*`
15
+ wrappers do the `getComputedStyle` read on the client.
16
+ -------------------------------------------------------------------------- */
17
+
18
+ export type ThemeMode = "dark" | "light";
19
+
20
+ /** Reads a resolved token value (already `getComputedStyle`-flattened). */
21
+ export type TokenReader = (name: string) => string | undefined;
22
+
23
+ /**
24
+ * The 16 ANSI colors + selection/cursor treatment, designed against the og
25
+ * OKLCH palette. Hues are borrowed from the og status ramp so terminal color
26
+ * reads as part of the same system: red≈status-failed(22), green≈status-idle
27
+ * (155), yellow≈status-running(80), blue≈accent(255), magenta≈status-waiting
28
+ * (305), cyan≈200. Dark uses lifted lightness on the dark slate ground; light
29
+ * uses deeper, more saturated inks on the near-white ground. Bright variants
30
+ * add lightness + a touch of chroma. `black`/`white` are neutral slate ramp
31
+ * stops (never pure #000/#fff — that clashes with the calm neutral ground).
32
+ */
33
+ export type AnsiPalette = {
34
+ black: string;
35
+ red: string;
36
+ green: string;
37
+ yellow: string;
38
+ blue: string;
39
+ magenta: string;
40
+ cyan: string;
41
+ white: string;
42
+ brightBlack: string;
43
+ brightRed: string;
44
+ brightGreen: string;
45
+ brightYellow: string;
46
+ brightBlue: string;
47
+ brightMagenta: string;
48
+ brightCyan: string;
49
+ brightWhite: string;
50
+ /** Selection wash + its foreground; sits over the terminal ground. */
51
+ selectionBackground: string;
52
+ selectionForeground: string;
53
+ selectionInactiveBackground: string;
54
+ };
55
+
56
+ const DARK_ANSI: AnsiPalette = {
57
+ black: "oklch(0.3 0.014 260)",
58
+ red: "oklch(0.68 0.17 22)",
59
+ green: "oklch(0.75 0.13 155)",
60
+ yellow: "oklch(0.8 0.11 80)",
61
+ blue: "oklch(0.7 0.14 255)",
62
+ magenta: "oklch(0.72 0.13 305)",
63
+ cyan: "oklch(0.75 0.1 210)",
64
+ white: "oklch(0.8 0.01 260)",
65
+ brightBlack: "oklch(0.45 0.014 260)",
66
+ brightRed: "oklch(0.76 0.17 22)",
67
+ brightGreen: "oklch(0.82 0.14 155)",
68
+ brightYellow: "oklch(0.86 0.12 85)",
69
+ brightBlue: "oklch(0.78 0.13 255)",
70
+ brightMagenta: "oklch(0.8 0.13 305)",
71
+ brightCyan: "oklch(0.83 0.1 200)",
72
+ brightWhite: "oklch(0.97 0.005 260)",
73
+ selectionBackground: "oklch(0.72 0.15 255 / 0.34)",
74
+ selectionForeground: "oklch(0.985 0.005 260)",
75
+ selectionInactiveBackground: "oklch(0.72 0.02 260 / 0.22)",
76
+ };
77
+
78
+ const LIGHT_ANSI: AnsiPalette = {
79
+ black: "oklch(0.35 0.015 260)",
80
+ red: "oklch(0.52 0.19 22)",
81
+ green: "oklch(0.5 0.13 155)",
82
+ yellow: "oklch(0.56 0.13 80)",
83
+ blue: "oklch(0.52 0.18 255)",
84
+ magenta: "oklch(0.52 0.16 305)",
85
+ cyan: "oklch(0.52 0.1 210)",
86
+ white: "oklch(0.7 0.01 260)",
87
+ brightBlack: "oklch(0.5 0.014 260)",
88
+ brightRed: "oklch(0.55 0.2 22)",
89
+ brightGreen: "oklch(0.54 0.14 155)",
90
+ brightYellow: "oklch(0.6 0.14 80)",
91
+ brightBlue: "oklch(0.48 0.19 255)",
92
+ brightMagenta: "oklch(0.54 0.17 305)",
93
+ brightCyan: "oklch(0.54 0.11 210)",
94
+ brightWhite: "oklch(0.3 0.015 260)",
95
+ selectionBackground: "oklch(0.55 0.18 255 / 0.24)",
96
+ selectionForeground: "oklch(0.21 0.015 260)",
97
+ selectionInactiveBackground: "oklch(0.6 0.02 260 / 0.16)",
98
+ };
99
+
100
+ /** The designed ANSI-16 palette for a theme mode. Pure. */
101
+ export function ogAnsiPalette(mode: ThemeMode): AnsiPalette {
102
+ return mode === "light" ? LIGHT_ANSI : DARK_ANSI;
103
+ }
104
+
105
+ /**
106
+ * Build a COMPLETE xterm ITheme from token values. Background/foreground/cursor
107
+ * come from the live `--og-*` tokens (so an embedder's rebrand flows through);
108
+ * the ANSI-16 + selection come from the designed palette for `mode`. Every ANSI
109
+ * slot is always set — xterm never falls back to its stock VGA colors. Pure.
110
+ */
111
+ export function buildXtermTheme(read: TokenReader, mode: ThemeMode): XtermTheme {
112
+ const ansi = ogAnsiPalette(mode);
113
+ const bgFallback = mode === "light" ? "oklch(0.978 0.003 260)" : "oklch(0.155 0.012 260)";
114
+ const fgFallback = mode === "light" ? "oklch(0.21 0.015 260)" : "oklch(0.955 0.005 260)";
115
+ const bg = read("--og-color-bg") ?? bgFallback;
116
+ const fg = read("--og-color-fg") ?? fgFallback;
117
+ const accent = read("--og-color-accent") ?? ansi.brightBlue;
118
+ return {
119
+ // Match the dock surface the terminal paints on (`--og-color-bg`).
120
+ background: bg,
121
+ foreground: fg,
122
+ cursor: accent,
123
+ cursorAccent: bg,
124
+ selectionBackground: ansi.selectionBackground,
125
+ selectionForeground: ansi.selectionForeground,
126
+ selectionInactiveBackground: ansi.selectionInactiveBackground,
127
+ black: ansi.black,
128
+ red: ansi.red,
129
+ green: ansi.green,
130
+ yellow: ansi.yellow,
131
+ blue: ansi.blue,
132
+ magenta: ansi.magenta,
133
+ cyan: ansi.cyan,
134
+ white: ansi.white,
135
+ brightBlack: ansi.brightBlack,
136
+ brightRed: ansi.brightRed,
137
+ brightGreen: ansi.brightGreen,
138
+ brightYellow: ansi.brightYellow,
139
+ brightBlue: ansi.brightBlue,
140
+ brightMagenta: ansi.brightMagenta,
141
+ brightCyan: ansi.brightCyan,
142
+ brightWhite: ansi.brightWhite,
143
+ };
144
+ }
145
+
146
+ /** Which og theme is active for `el`, matching the `use-theme-type` convention
147
+ * (nearest `data-og-theme` / `.og-light` ancestor; else the computed
148
+ * `color-scheme`; else dark, the first-class default). */
149
+ function detectMode(el: Element, style: CSSStyleDeclaration): ThemeMode {
150
+ const closest = el.closest?.bind(el);
151
+ if (closest) {
152
+ if (closest('[data-og-theme="light"]') || closest(".og-light")) return "light";
153
+ const explicit = closest("[data-og-theme]");
154
+ if (explicit) return explicit.getAttribute("data-og-theme") === "light" ? "light" : "dark";
155
+ }
156
+ const scheme = (
157
+ style.getPropertyValue("color-scheme").trim() ||
158
+ style.colorScheme ||
159
+ ""
160
+ ).toLowerCase();
161
+ if (scheme.includes("light") && !scheme.includes("dark")) return "light";
162
+ return "dark";
163
+ }
164
+
3
165
  /**
4
- * Derive an xterm `ITheme` subset from the live OKLCH `--og-*` token system.
5
- * Reads the COMPUTED values so xterm which
6
- * paints into a canvas and can't consume CSS vars gets concrete colors. Call
7
- * on mount and re-derive on a `data-og-theme` flip.
166
+ * Derive a COMPLETE xterm `ITheme` from the live `--og-*` token system on the
167
+ * DOM. Reads the COMPUTED values so xterm gets concrete colors, detects the
168
+ * active theme mode, and fills every ANSI slot. Call on mount + re-derive on a
169
+ * `data-og-theme` flip. SSR-safe: returns undefined off the DOM.
8
170
  *
9
- * SSR-safe: returns undefined off the DOM (the caller keeps xterm's defaults).
171
+ * `root` should be an element INSIDE the themed subtree (e.g. the terminal
172
+ * container) so nested `[data-og-theme]` themes resolve correctly; when omitted
173
+ * it falls back to the nearest `[data-og-theme]` element, then documentElement.
10
174
  */
11
175
  export function xtermThemeFromTokens(root?: HTMLElement | null): XtermTheme | undefined {
12
176
  if (typeof window === "undefined" || typeof getComputedStyle === "undefined") return undefined;
13
- const el = root ?? document.documentElement;
177
+ const el =
178
+ root ??
179
+ (typeof document !== "undefined"
180
+ ? ((document.querySelector("[data-og-theme]") as HTMLElement | null) ??
181
+ document.documentElement)
182
+ : null);
183
+ if (!el) return undefined;
14
184
  const style = getComputedStyle(el);
15
- const read = (name: string): string | undefined => {
185
+ const read: TokenReader = (name) => {
16
186
  const value = style.getPropertyValue(name).trim();
17
187
  return value || undefined;
18
188
  };
19
- const bg = read("--og-color-bg");
20
- const fg = read("--og-color-fg");
21
- const accent = read("--og-color-accent");
22
- const theme: XtermTheme = {};
23
- if (bg) theme.background = bg;
24
- if (fg) theme.foreground = fg;
25
- if (accent) {
26
- theme.cursor = accent;
27
- theme.selectionBackground = accent;
189
+ return buildXtermTheme(read, detectMode(el, style));
190
+ }
191
+
192
+ // ── Font resolution ──────────────────────────────────────────────────────────
193
+
194
+ export type ResolvedTerminalFont = { fontFamily: string; fontSize: number; lineHeight: number };
195
+
196
+ /** Concrete fallback if the token is unset or (defensively) still a `var()`. */
197
+ const FALLBACK_MONO = "ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace";
198
+
199
+ export type FontOverrides = { fontFamily?: string | undefined; fontSize?: number | undefined };
200
+
201
+ /**
202
+ * Resolve xterm's font from token values. The critical bug this fixes:
203
+ * `fontFamily: "var(--og-font-mono)"` is measured by xterm via a canvas font
204
+ * string where `var()` does NOT resolve, so xterm silently mis-measures every
205
+ * glyph. Here the family is a CONCRETE stack; a `var(` in the input is treated
206
+ * as unresolved and replaced with the fallback. Pure over a token `read`.
207
+ */
208
+ export function resolveTerminalFontFromReader(
209
+ read: TokenReader,
210
+ overrides?: FontOverrides,
211
+ ): ResolvedTerminalFont {
212
+ const rawFamily = overrides?.fontFamily ?? read("--og-font-mono");
213
+ const fontFamily =
214
+ rawFamily && rawFamily.trim() && !rawFamily.includes("var(") ? rawFamily.trim() : FALLBACK_MONO;
215
+
216
+ let fontSize = overrides?.fontSize;
217
+ if (fontSize == null) {
218
+ const raw = read("--og-code-font-size");
219
+ const parsed = raw ? Number.parseFloat(raw) : Number.NaN;
220
+ fontSize = Number.isFinite(parsed) && parsed > 0 ? parsed : 13;
28
221
  }
29
- if (bg) theme.cursorAccent = bg;
30
- return Object.keys(theme).length > 0 ? theme : undefined;
222
+
223
+ return { fontFamily, fontSize, lineHeight: 1.3 };
224
+ }
225
+
226
+ /** DOM wrapper: resolve the terminal font from an element's computed tokens. */
227
+ export function resolveTerminalFont(
228
+ el: HTMLElement,
229
+ overrides?: FontOverrides,
230
+ ): ResolvedTerminalFont {
231
+ const style = getComputedStyle(el);
232
+ const read: TokenReader = (name) => {
233
+ const value = style.getPropertyValue(name).trim();
234
+ return value || undefined;
235
+ };
236
+ return resolveTerminalFontFromReader(read, overrides);
31
237
  }
package/src/machines.ts CHANGED
@@ -34,12 +34,34 @@ export { MachineMetrics } from "./components/machine-metrics";
34
34
  export type { MachineMetricsProps } from "./components/machine-metrics";
35
35
  export { MachineCard } from "./components/machine-card";
36
36
  export type { MachineCardProps } from "./components/machine-card";
37
+ export { MachineHealthPill } from "./components/machine-health-pill";
38
+ export type { MachineHealthPillProps } from "./components/machine-health-pill";
39
+ // Telemetry: fused health signal + history charts + the per-machine detail view.
40
+ export { deriveHealth, HEALTH_TOKEN, healthPulses } from "./components/machines/health";
41
+ export type { HealthLevel, HealthVerdict } from "./components/machines/health";
42
+ export { MetricSparkline } from "./components/machines/metric-sparkline";
43
+ export type { MetricSparklineProps } from "./components/machines/metric-sparkline";
44
+ export { MetricHistoryChart } from "./components/machines/metric-history-chart";
45
+ export type {
46
+ MetricHistoryChartProps,
47
+ SeriesPoint,
48
+ } from "./components/machines/metric-history-chart";
49
+ export { MachineDetail } from "./components/machines/machine-detail";
50
+ export type { MachineDetailProps } from "./components/machines/machine-detail";
51
+ export { METRICS, METRIC_WINDOWS, WINDOW_LABEL, pointsFor } from "./components/machines/series";
52
+ export type { MetricDef, MetricKey, MetricWindow } from "./components/machines/series";
37
53
  export { MachinesDashboard } from "./components/machines-dashboard";
38
54
  export type { MachinesDashboardProps } from "./components/machines-dashboard";
39
55
  export { MachineDockBar, SharedMachineDisclosure } from "./components/machine-dock-bar";
40
- export type { MachineDockBarProps, SharedMachineDisclosureProps } from "./components/machine-dock-bar";
56
+ export type {
57
+ MachineDockBarProps,
58
+ SharedMachineDisclosureProps,
59
+ } from "./components/machine-dock-bar";
41
60
  export { EnrollmentDeviceFlow } from "./components/enrollment-device-flow";
42
- export type { DeviceFlowPhase, EnrollmentDeviceFlowProps } from "./components/enrollment-device-flow";
61
+ export type {
62
+ DeviceFlowPhase,
63
+ EnrollmentDeviceFlowProps,
64
+ } from "./components/enrollment-device-flow";
43
65
  export { EnrollmentConsent } from "./components/enrollment-consent";
44
66
  export type {
45
67
  EnrollmentConsentMachine,
@@ -47,4 +69,8 @@ export type {
47
69
  EnrollmentConsentProps,
48
70
  } from "./components/enrollment-consent";
49
71
  export { useMachines } from "./hooks/use-machines";
50
- export type { MachinesClientLike, UseMachinesOptions, UseMachinesResult } from "./hooks/use-machines";
72
+ export type {
73
+ MachinesClientLike,
74
+ UseMachinesOptions,
75
+ UseMachinesResult,
76
+ } from "./hooks/use-machines";