@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
@@ -36,11 +36,26 @@ export type TurnSummaryProps = {
36
36
  durationMs?: number | undefined;
37
37
  /** Start expanded. */
38
38
  defaultOpen?: boolean | undefined;
39
+ /**
40
+ * A nested fold — a cluster or sub-turn INSIDE an already-expanded turn. It
41
+ * drops the bordered/filled chip and renders as a plain disclosure node on the
42
+ * parent's rail (chevron + glyph + facets), so expanding a turn reveals a thread
43
+ * of nodes, never a stack of boxes-in-boxes. The top-level fold stays a chip.
44
+ */
45
+ bare?: boolean | undefined;
39
46
  /** The rendered activity rail revealed on expand. */
40
47
  children: React.ReactNode;
41
48
  };
42
49
 
43
- export function TurnSummary({ items, outcome, failureText, durationMs, defaultOpen, children }: TurnSummaryProps) {
50
+ export function TurnSummary({
51
+ items,
52
+ outcome,
53
+ failureText,
54
+ durationMs,
55
+ defaultOpen,
56
+ bare,
57
+ children,
58
+ }: TurnSummaryProps) {
44
59
  // An explicit `defaultOpen` always wins; otherwise an ancestor may seed it
45
60
  // (screenshot instrumentation); otherwise the turn starts folded.
46
61
  const forcedDefaultOpen = useForcedDefaultOpen();
@@ -49,34 +64,46 @@ export function TurnSummary({ items, outcome, failureText, durationMs, defaultOp
49
64
  const facets = summarizeTurn(items, durationMs);
50
65
 
51
66
  return (
52
- <Collapsible.Root open={open} onOpenChange={setOpen} className={enter ? "animate-og-enter" : undefined}>
67
+ <Collapsible.Root
68
+ open={open}
69
+ onOpenChange={setOpen}
70
+ className={enter && !bare ? "animate-og-enter" : undefined}
71
+ >
53
72
  <Collapsible.Trigger
54
73
  className={cn(
55
- "group flex w-full items-center gap-2.5 rounded-og-md border px-3 py-2 text-left text-og-base transition-colors",
56
- // A folded turn is a touch target on coarse pointers: grow the row so
57
- // it clears the 40px minimum without disturbing the calm desktop rhythm.
74
+ // Both the top-level turn fold and a nested cluster fold render as a
75
+ // FLAT rail row chevron + glyph + facets on the page background, no
76
+ // border, no fill. Only a hover tint hints the row is expandable, so a
77
+ // collapsed turn never reads as a boxed card. The top-level row is a
78
+ // touch larger (base text, size-5 glyph, wider gap) so it still reads
79
+ // as a turn landmark above the nested cluster rows it groups.
80
+ "group flex w-full items-center rounded-og-sm text-left transition-colors",
81
+ // A folded turn is a touch target on coarse pointers: grow the row so it
82
+ // clears the 40px minimum without disturbing the calm desktop rhythm.
58
83
  "pointer-coarse:py-2.5",
59
- // Only a failed turn earns the one filled/tinted card in the timeline;
60
- // complete and cancelled stay flat and calm.
84
+ bare
85
+ ? "gap-2 px-1.5 py-1.5 text-og-sm text-og-fg-muted"
86
+ : "-mx-2 gap-2.5 px-2 py-1.5 text-og-base text-og-fg-muted",
87
+ // A failed fold keeps its red accent (glyph + inline reason below) and a
88
+ // faint red hover wash so attention still lands there; every other
89
+ // outcome gets the neutral surface hover.
61
90
  outcome === "failed"
62
- ? "border-og-status-failed/30 bg-og-status-failed/[0.06] hover:border-og-status-failed/50"
63
- : "border-og-border bg-og-surface-1/50 hover:border-og-border-strong",
91
+ ? "hover:bg-og-status-failed/[0.06] hover:text-og-fg"
92
+ : "hover:bg-og-surface-1 hover:text-og-fg",
64
93
  )}
65
94
  >
66
95
  {/* Disclosure grammar matches the rows: chevron leads (far left), then the
67
96
  outcome glyph, then the facets — one expand affordance side everywhere. */}
68
97
  <ChevronRightIcon className="size-3.5 shrink-0 text-og-fg-subtle transition-transform duration-150 group-data-[state=open]:rotate-90" />
69
- {/* Only the exceptional outcomes earn a filled tinted circle. A clean
70
- (complete) run draws a bare muted check zero colored fills, so the
71
- eye is pulled only to a turn that needs attention. */}
98
+ {/* The outcome glyph carries the state by hue alone — no filled circle, no
99
+ card. A clean (complete) run draws a bare muted check; a failed one a
100
+ red triangle; a cancelled one a muted slash; a still-running cluster a
101
+ quiet pulse dot in the glyph's place so alignment holds. */}
72
102
  <span
73
103
  className={cn(
74
- "inline-flex size-5 shrink-0 items-center justify-center rounded-full",
75
- outcome === "failed"
76
- ? "bg-og-status-failed/15 text-og-status-failed"
77
- : outcome === "cancelled"
78
- ? "bg-og-fg-subtle/15 text-og-fg-subtle"
79
- : "text-og-fg-subtle",
104
+ "inline-flex shrink-0 items-center justify-center",
105
+ bare ? "size-3.5" : "size-5",
106
+ outcome === "failed" ? "text-og-status-failed" : "text-og-fg-subtle",
80
107
  )}
81
108
  >
82
109
  {outcome === "failed" ? (
@@ -84,17 +111,19 @@ export function TurnSummary({ items, outcome, failureText, durationMs, defaultOp
84
111
  ) : outcome === "cancelled" ? (
85
112
  <CircleSlashIcon className="size-3" />
86
113
  ) : outcome === "complete" ? (
87
- <CheckIcon className="size-3.5" />
114
+ <CheckIcon className={bare ? "size-3" : "size-3.5"} />
88
115
  ) : (
89
116
  <span className="size-1.5 animate-og-pulse rounded-full bg-og-fg-subtle" />
90
117
  )}
91
118
  </span>
92
- <span className="min-w-0 flex-1 truncate text-og-fg-muted">
119
+ <span className={cn("min-w-0 flex-1 truncate", bare ? "text-og-sm" : "text-og-fg-muted")}>
93
120
  {facets}
94
121
  {outcome === "failed" && failureText ? (
95
122
  <span className="text-og-status-failed"> · {failureText}</span>
96
123
  ) : null}
97
- {outcome === "cancelled" ? <span className="text-og-fg-subtle"> · interrupted</span> : null}
124
+ {outcome === "cancelled" ? (
125
+ <span className="text-og-fg-subtle"> · interrupted</span>
126
+ ) : null}
98
127
  </span>
99
128
  {/* The disclosure hint. Calm at rest on fine pointers (revealed on hover
100
129
  and keyboard focus), but always present on coarse pointers where there
@@ -113,7 +142,9 @@ export function TurnSummary({ items, outcome, failureText, durationMs, defaultOp
113
142
  </span>
114
143
  </Collapsible.Trigger>
115
144
  <Collapsible.Content className="overflow-hidden data-[state=closed]:animate-og-collapse data-[state=open]:animate-og-expand">
116
- <div className="pt-2">{children}</div>
145
+ {/* A nested node indents its revealed rows under the glyph (thread nesting
146
+ off the parent rail); the top-level turn body owns its own rail. */}
147
+ <div className={bare ? "pt-1 pl-5" : "pt-2"}>{children}</div>
117
148
  </Collapsible.Content>
118
149
  </Collapsible.Root>
119
150
  );
@@ -124,7 +155,19 @@ function summarizeTurn(items: ActivityItem[], durationMs?: number): string {
124
155
  let files = 0;
125
156
  let commands = 0;
126
157
  let screenshots = 0;
158
+ let memoriesSaved = 0;
159
+ let memoriesUpdated = 0;
127
160
  for (const item of items) {
161
+ if (item.kind === "memory") {
162
+ // A memory write is a first-class facet — the "wrote 1 memory" signal the
163
+ // fold should make prominent — counted saved vs updated separately.
164
+ if (item.variant === "corrected") {
165
+ memoriesUpdated += 1;
166
+ } else {
167
+ memoriesSaved += 1;
168
+ }
169
+ continue;
170
+ }
128
171
  if (item.kind !== "tool-call") {
129
172
  continue;
130
173
  }
@@ -133,7 +176,11 @@ function summarizeTurn(items: ActivityItem[], durationMs?: number): string {
133
176
  files += applyPatchOps(item.raw).length;
134
177
  } else if (item.name === "exec_command") {
135
178
  commands += 1;
136
- } else if (rawTypeOf(item) === "computer_call" || item.name === "computer_call" || item.name === "computer_screenshot") {
179
+ } else if (
180
+ rawTypeOf(item) === "computer_call" ||
181
+ item.name === "computer_call" ||
182
+ item.name === "computer_screenshot"
183
+ ) {
137
184
  if (screenshotDataUrl(item.output) !== null) {
138
185
  screenshots += 1;
139
186
  }
@@ -149,6 +196,12 @@ function summarizeTurn(items: ActivityItem[], durationMs?: number): string {
149
196
  if (screenshots) {
150
197
  parts.push(`${screenshots} ${screenshots === 1 ? "screenshot" : "screenshots"}`);
151
198
  }
199
+ if (memoriesSaved) {
200
+ parts.push(`${memoriesSaved} ${memoriesSaved === 1 ? "memory" : "memories"} saved`);
201
+ }
202
+ if (memoriesUpdated) {
203
+ parts.push(`${memoriesUpdated} ${memoriesUpdated === 1 ? "memory" : "memories"} updated`);
204
+ }
152
205
  const duration = formatDurationFacet(durationMs);
153
206
  if (duration) {
154
207
  parts.push(duration);
@@ -13,8 +13,6 @@ export type UserMessageItem = {
13
13
  kind: "user-message";
14
14
  id: string;
15
15
  text: string;
16
- /** Queued for a future turn whose start has not appeared in the event log. */
17
- pending?: boolean;
18
16
  /** Resources attached to this message (file uploads, repositories). */
19
17
  resources: ResourceRef[];
20
18
  /** Tools requested for the turn this message starts. */
@@ -71,20 +69,29 @@ export type WorkerItem = {
71
69
  id: string;
72
70
  turnId: string | null;
73
71
  callId: string | null;
74
- action: "spawn" | "message" | "interrupt";
72
+ action: "spawn" | "message";
75
73
  /** The worker's initial message / the message sent to it, when parseable. */
76
74
  prompt: string | null;
77
75
  /** The target/spawned worker session id, when parseable from args/output. */
78
76
  workerSessionId: string | null;
79
- /**
80
- * For an `interrupt` action, whether it stops the target (default) or steers
81
- * it (cancel the current turn, keep the goal). Absent on spawn/message.
82
- */
83
- mode?: "stop" | "steer";
84
77
  status: "running" | "complete" | "failed" | "cancelled";
85
78
  occurredAt: string;
86
79
  };
87
80
 
81
+ export type WorkerCompletionItem = {
82
+ kind: "worker-completion";
83
+ id: string;
84
+ turnId: string | null;
85
+ occurredAt: string;
86
+ childSessionId: string;
87
+ childStatus: string;
88
+ goalStatus: string | null;
89
+ goalText: string | null;
90
+ evidence: string | null;
91
+ pausedReason: string | null;
92
+ text: string;
93
+ };
94
+
88
95
  export type SandboxItem = {
89
96
  kind: "sandbox";
90
97
  id: string;
@@ -92,10 +99,47 @@ export type SandboxItem = {
92
99
  name: string;
93
100
  command: string | null;
94
101
  output: string;
102
+ origin?: "created" | "restored" | "resumed" | null;
95
103
  status: "running" | "complete" | "failed" | "cancelled";
96
104
  occurredAt: string;
97
105
  };
98
106
 
107
+ /**
108
+ * A first-party workspace-memory write the agent made mid-turn — a `memory.saved`
109
+ * (it committed a new preference / fact / procedure / decision / history) or a
110
+ * `memory.corrected` (it updated or archived an existing one). A settled save is
111
+ * ordinary progress, not an exceptional state, so it renders as a calm NEUTRAL
112
+ * step on the rail (never accent/color). When the host app supplies an
113
+ * `onMemoryClick` handler the row also deep-links to the record in its memory
114
+ * pane; without one it is non-interactive rich content.
115
+ */
116
+ export type MemoryItem = {
117
+ kind: "memory";
118
+ id: string;
119
+ turnId: string | null;
120
+ variant: "saved" | "corrected";
121
+ /** The memory's kind enum (`"preference" | "semantic" | …`); mapped to a human label at render. */
122
+ memoryKind: string;
123
+ /** The memory text, ellipsized to ≤120 chars server-side. For a supersede this is the OLD text. */
124
+ preview: string;
125
+ /** The save collapsed into an existing memory (no new row was written). Saved variant only. */
126
+ deduped?: boolean;
127
+ /** The NEW text when a correction superseded the memory with a replacement; absent = updated-in-place or archived. */
128
+ replacementPreview?: string;
129
+ /**
130
+ * What a `memory.corrected` did: `"superseded"` (replaced by a new record, see
131
+ * `replacementPreview`), `"updated"` (edited in place — the record lives on), or
132
+ * `"archived"` (retired). Distinguishes updated-in-place from archived when there
133
+ * is no replacement. Corrected variant only; read defensively (may be absent).
134
+ */
135
+ action?: string;
136
+ /** The saved / corrected memory's id — the deep-link target for a save. */
137
+ memoryId: string;
138
+ /** The replacement memory's id when a correction produced one — the LIVE record the deep-link targets. */
139
+ replacementMemoryId?: string;
140
+ occurredAt: string;
141
+ };
142
+
99
143
  export type SessionStatusItem = {
100
144
  kind: "session-status";
101
145
  id: string;
@@ -106,7 +150,7 @@ export type SessionStatusItem = {
106
150
  export type GoalItem = {
107
151
  kind: "goal";
108
152
  id: string;
109
- action: "set" | "updated" | "completed" | "paused" | "resumed" | "continuation";
153
+ action: "set" | "updated" | "completed" | "paused" | "resumed" | "cleared" | "continuation";
110
154
  text: string | null;
111
155
  occurredAt: string;
112
156
  };
@@ -116,10 +160,41 @@ export type NoticeItem = {
116
160
  id: string;
117
161
  tone: "waiting" | "cancelled" | "failed";
118
162
  text: string;
163
+ /** Optional evidence kept inspectable without overwhelming the main rail. */
164
+ details?: { label: string; value: unknown };
119
165
  action?: { label: string; url: string };
120
166
  occurredAt: string;
121
167
  };
122
168
 
169
+ /**
170
+ * A tool call hit a connection whose credential lapsed — the broker asked the
171
+ * user to reconnect the provider before the turn can continue. Carries the
172
+ * structured `tool.auth_needed` payload so the renderer can draw a clean inline
173
+ * reconnect affordance (provider logo + one human line + a Reconnect button)
174
+ * and the app can start the right recovery flow (OAuth reconnect for the
175
+ * surviving connection, or credential re-entry for an api-key one). The `reason`
176
+ * shapes the human copy but is never shown raw.
177
+ */
178
+ export type AuthNeededItem = {
179
+ kind: "auth-needed";
180
+ id: string;
181
+ turnId: string | null;
182
+ /** The connection's registrable domain, e.g. "linear.app". */
183
+ providerDomain: string;
184
+ /** The lapsed connection to reconnect, when the row survived. */
185
+ connectionId: string | null;
186
+ reason: "missing_connection" | "expired" | "insufficient_scope" | "refresh_failed" | null;
187
+ /** Scopes the provider now needs; may inform the copy, never shown as a raw label. */
188
+ scopes: string[];
189
+ /** The OAuth `resource` (RFC 8707) the reconnect should target, when supplied. */
190
+ resource: string | null;
191
+ /** The tool whose call triggered the reauth, for context. */
192
+ toolName: string | null;
193
+ /** A pre-minted authorization URL, when the broker already produced one. */
194
+ authorizationUrl: string | null;
195
+ occurredAt: string;
196
+ };
197
+
123
198
  export type TurnOutcome = "complete" | "failed" | "cancelled";
124
199
 
125
200
  export type TurnEndItem = {
@@ -137,18 +212,27 @@ export type TimelineItem =
137
212
  | ReasoningItem
138
213
  | ToolCallItem
139
214
  | WorkerItem
215
+ | WorkerCompletionItem
140
216
  | SandboxItem
141
217
  | SessionStatusItem
142
218
  | GoalItem
143
219
  | NoticeItem
220
+ | AuthNeededItem
221
+ | MemoryItem
144
222
  | TurnEndItem;
145
223
 
146
- /** Activity items cluster between chat messages (reasoning, tools, workers, sandbox). */
147
- export type ActivityItem = ReasoningItem | ToolCallItem | WorkerItem | SandboxItem;
224
+ /** Activity items cluster between chat messages (reasoning, tools, workers, sandbox, memory). */
225
+ export type ActivityItem = ReasoningItem | ToolCallItem | WorkerItem | SandboxItem | MemoryItem;
148
226
 
149
227
  export type TimelineGroup =
150
228
  | { kind: "item"; item: TimelineItem }
151
- | { kind: "activity"; id: string; items: ActivityItem[]; outcome?: TurnOutcome; failureText?: string }
229
+ | {
230
+ kind: "activity";
231
+ id: string;
232
+ items: ActivityItem[];
233
+ outcome?: TurnOutcome;
234
+ failureText?: string;
235
+ }
152
236
  | {
153
237
  kind: "turn";
154
238
  id: string;
package/styles/index.css CHANGED
@@ -165,6 +165,28 @@
165
165
  }
166
166
  }
167
167
 
168
+ /* Metric-history line draw-in. The chart path carries `pathLength={1}`, so the
169
+ dash math is resolution-independent: one full-length dash whose offset animates
170
+ from 1 (hidden) to 0 (drawn). Degrades to a solid line if animations are off. */
171
+ @keyframes og-mh-draw {
172
+ from {
173
+ stroke-dashoffset: 1;
174
+ }
175
+ to {
176
+ stroke-dashoffset: 0;
177
+ }
178
+ }
179
+ .og-mh-draw {
180
+ stroke-dasharray: 1;
181
+ animation: og-mh-draw 900ms var(--og-ease-out) forwards;
182
+ }
183
+ @media (prefers-reduced-motion: reduce) {
184
+ .og-mh-draw {
185
+ animation: none;
186
+ stroke-dasharray: none;
187
+ }
188
+ }
189
+
168
190
  /* Shimmer helper: gradient text the keyframe scrolls through. */
169
191
  .og-shimmer-text {
170
192
  background: linear-gradient(
package/styles/tokens.css CHANGED
@@ -45,7 +45,7 @@
45
45
  --og-color-border-strong: oklch(0.42 0.016 260);
46
46
  --og-color-fg: oklch(0.955 0.005 260);
47
47
  --og-color-fg-muted: oklch(0.73 0.012 260);
48
- --og-color-fg-subtle: oklch(0.56 0.013 260);
48
+ --og-color-fg-subtle: oklch(0.62 0.013 260);
49
49
 
50
50
  /* Accent — one brand hue, used sparingly ----------------------------------- */
51
51
  --og-color-accent: oklch(0.72 0.15 255);
@@ -92,7 +92,7 @@
92
92
  --og-color-border-strong: oklch(0.8 0.01 260);
93
93
  --og-color-fg: oklch(0.21 0.015 260);
94
94
  --og-color-fg-muted: oklch(0.46 0.014 260);
95
- --og-color-fg-subtle: oklch(0.6 0.012 260);
95
+ --og-color-fg-subtle: oklch(0.5 0.012 260);
96
96
 
97
97
  --og-color-accent: oklch(0.55 0.18 255);
98
98
  --og-color-accent-strong: oklch(0.48 0.19 255);