@opengeni/react 0.29.0 → 0.33.1

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 (207) hide show
  1. package/dist/approvals.d.ts +14 -0
  2. package/dist/{chunk-M7X4JZOD.js → chunk-4QOQ2DSR.js} +8 -1
  3. package/dist/chunk-4QOQ2DSR.js.map +1 -0
  4. package/dist/{chunk-HQFPO7VX.js → chunk-7CJOAW3V.js} +24 -3
  5. package/dist/{chunk-HQFPO7VX.js.map → chunk-7CJOAW3V.js.map} +1 -1
  6. package/dist/{chunk-CSJBF3QJ.js → chunk-A2WITZTI.js} +377 -113
  7. package/dist/chunk-A2WITZTI.js.map +1 -0
  8. package/dist/{chunk-BC6RKIHS.js → chunk-GZ4KR67U.js} +11 -11
  9. package/dist/{chunk-AVLMIWYO.js → chunk-KZRUYCL4.js} +12 -6
  10. package/dist/chunk-KZRUYCL4.js.map +1 -0
  11. package/dist/{chunk-IPVOVOW6.js → chunk-MS3FBZ6A.js} +2539 -2174
  12. package/dist/chunk-MS3FBZ6A.js.map +1 -0
  13. package/dist/{chunk-OKXCECLG.js → chunk-TCCE5ZU5.js} +171 -22
  14. package/dist/chunk-TCCE5ZU5.js.map +1 -0
  15. package/dist/chunk-U255M5EZ.js +178 -0
  16. package/dist/chunk-U255M5EZ.js.map +1 -0
  17. package/dist/chunk-ZNNE2VES.js +6413 -0
  18. package/dist/chunk-ZNNE2VES.js.map +1 -0
  19. package/dist/client.d.ts +33 -0
  20. package/dist/commands/index.d.ts +3 -0
  21. package/dist/commands/registry.d.ts +41 -0
  22. package/dist/commands/types.d.ts +85 -0
  23. package/dist/components/approval-surface.d.ts +28 -0
  24. package/dist/components/chat-composer.d.ts +51 -0
  25. package/dist/components/code-editor.d.ts +43 -0
  26. package/dist/components/command-palette.d.ts +25 -0
  27. package/dist/components/composer-transcription-control.d.ts +36 -0
  28. package/dist/components/composer.d.ts +277 -0
  29. package/dist/components/copy-button.d.ts +26 -0
  30. package/dist/components/desktop-viewer.d.ts +85 -0
  31. package/dist/components/diff-view.d.ts +38 -0
  32. package/dist/components/enrollment-consent.d.ts +33 -0
  33. package/dist/components/enrollment-device-flow.d.ts +27 -0
  34. package/dist/components/file-browser.d.ts +47 -0
  35. package/dist/components/fleet-tile.d.ts +18 -0
  36. package/dist/components/human-input-form.d.ts +47 -0
  37. package/dist/components/machine-card.d.ts +21 -0
  38. package/dist/components/machine-dock-bar.d.ts +31 -0
  39. package/dist/components/machine-health-pill.d.ts +9 -0
  40. package/dist/components/machine-metrics.d.ts +15 -0
  41. package/dist/components/machine-status-pill.d.ts +54 -0
  42. package/dist/components/machines/health.d.ts +25 -0
  43. package/dist/components/machines/machine-detail.d.ts +14 -0
  44. package/dist/components/machines/metric-history-chart.d.ts +24 -0
  45. package/dist/components/machines/metric-sparkline.d.ts +9 -0
  46. package/dist/components/machines/series.d.ts +24 -0
  47. package/dist/components/machines-dashboard.d.ts +30 -0
  48. package/dist/components/markdown.d.ts +31 -0
  49. package/dist/components/message-timeline.d.ts +98 -0
  50. package/dist/components/model-picker.d.ts +19 -0
  51. package/dist/components/pierre-diff.d.ts +34 -0
  52. package/dist/components/pierre-file.d.ts +27 -0
  53. package/dist/components/queue-draft-policy.d.ts +7 -0
  54. package/dist/components/queue-surface-implementation.d.ts +20 -0
  55. package/dist/components/queue-surface-state.d.ts +12 -0
  56. package/dist/components/queue-surface.d.ts +12 -0
  57. package/dist/components/sandbox-files.d.ts +49 -0
  58. package/dist/components/sandbox-terminal.d.ts +123 -0
  59. package/dist/components/sandbox-workspace.d.ts +128 -0
  60. package/dist/components/session-chrome.d.ts +70 -0
  61. package/dist/components/session-status.d.ts +25 -0
  62. package/dist/components/soften-streaming-markdown.d.ts +13 -0
  63. package/dist/components/stream-reveal.d.ts +37 -0
  64. package/dist/components/tip-follow.d.ts +139 -0
  65. package/dist/components/tooltip.d.ts +16 -0
  66. package/dist/components/workbench-changes.d.ts +34 -0
  67. package/dist/components/workspace-dock.d.ts +35 -0
  68. package/dist/composer.d.ts +11 -7
  69. package/dist/composer.js +9 -6
  70. package/dist/hooks/internal.d.ts +46 -0
  71. package/dist/hooks/use-available-models.d.ts +37 -0
  72. package/dist/hooks/use-billing-usage.d.ts +26 -0
  73. package/dist/hooks/use-codex-accounts.d.ts +69 -0
  74. package/dist/{use-composer-ClHy1rkL.d.ts → hooks/use-composer.d.ts} +16 -24
  75. package/dist/hooks/use-desktop-stream.d.ts +43 -0
  76. package/dist/hooks/use-environments.d.ts +35 -0
  77. package/dist/{use-file-attachments-BmT8WtqO.d.ts → hooks/use-file-attachments.d.ts} +6 -9
  78. package/dist/hooks/use-goal.d.ts +46 -0
  79. package/dist/hooks/use-human-input.d.ts +26 -0
  80. package/dist/hooks/use-last-started-turn-policy.d.ts +27 -0
  81. package/dist/hooks/use-machine-chip.d.ts +54 -0
  82. package/dist/hooks/use-machines.d.ts +81 -0
  83. package/dist/hooks/use-packs.d.ts +27 -0
  84. package/dist/hooks/use-relay-frame-stream.d.ts +53 -0
  85. package/dist/hooks/use-rigs.d.ts +87 -0
  86. package/dist/hooks/use-sandbox-files.d.ts +108 -0
  87. package/dist/hooks/use-sandbox-git.d.ts +58 -0
  88. package/dist/hooks/use-sandbox-terminal.d.ts +62 -0
  89. package/dist/hooks/use-scheduled-tasks.d.ts +15 -0
  90. package/dist/hooks/use-session-capabilities.d.ts +83 -0
  91. package/dist/hooks/use-session-control.d.ts +22 -0
  92. package/dist/hooks/use-session-events.d.ts +92 -0
  93. package/dist/hooks/use-session-lineage.d.ts +17 -0
  94. package/dist/hooks/use-session-mcp-approval-policy.d.ts +23 -0
  95. package/dist/hooks/use-session.d.ts +23 -0
  96. package/dist/hooks/use-slash-commands.d.ts +66 -0
  97. package/dist/hooks/use-terminal-stream.d.ts +46 -0
  98. package/dist/hooks/use-transcription.d.ts +73 -0
  99. package/dist/hooks/use-turn-queue.d.ts +44 -0
  100. package/dist/hooks/use-voice-input.d.ts +25 -0
  101. package/dist/hooks/use-windowed-sections.d.ts +42 -0
  102. package/dist/hooks/use-workspace-capture.d.ts +49 -0
  103. package/dist/hooks/use-workspace-edit.d.ts +73 -0
  104. package/dist/hooks/use-workspace-sessions.d.ts +30 -0
  105. package/dist/hooks/use-workspaces.d.ts +22 -0
  106. package/dist/human-input.d.ts +18 -0
  107. package/dist/index.d.ts +144 -2119
  108. package/dist/index.js +421 -324
  109. package/dist/index.js.map +1 -1
  110. package/dist/lib/clipboard.d.ts +7 -0
  111. package/dist/lib/cn.d.ts +3 -0
  112. package/dist/lib/format.d.ts +48 -0
  113. package/dist/lib/git-patch.d.ts +8 -0
  114. package/dist/lib/motion-inspect.d.ts +5 -0
  115. package/dist/lib/motion.d.ts +14 -0
  116. package/dist/lib/noto-sans-arabic-loader.d.ts +1 -0
  117. package/dist/lib/noto-sans-jp-loader.generated.d.ts +1 -0
  118. package/dist/lib/relay-wire.d.ts +30 -0
  119. package/dist/lib/use-portal-token-style.d.ts +9 -0
  120. package/dist/lib/use-theme-type.d.ts +5 -0
  121. package/dist/lib/use-unicode-fonts.d.ts +12 -0
  122. package/dist/lib/xterm-renderer.d.ts +19 -0
  123. package/dist/lib/xterm-theme.d.ts +75 -0
  124. package/dist/machines.d.ts +30 -413
  125. package/dist/machines.js +3 -3
  126. package/dist/model-policy.d.ts +32 -0
  127. package/dist/model-policy.js +33 -0
  128. package/dist/model-policy.js.map +1 -0
  129. package/dist/provider.d.ts +16 -0
  130. package/dist/{queue-surface-implementation-UQ535OKQ.js → queue-surface-implementation-THPAKGMD.js} +2 -2
  131. package/dist/{queue-surface-implementation-UQ535OKQ.js.map → queue-surface-implementation-THPAKGMD.js.map} +1 -1
  132. package/dist/session-context.d.ts +82 -0
  133. package/dist/session-ui.d.ts +10 -7
  134. package/dist/session-ui.js +9 -5
  135. package/dist/session.d.ts +28 -6
  136. package/dist/session.js +11 -11
  137. package/dist/timeline/activity-rail.d.ts +19 -0
  138. package/dist/timeline/disclosure-context.d.ts +14 -0
  139. package/dist/timeline/entrance.d.ts +14 -0
  140. package/dist/timeline/fleet-decision-projection.d.ts +9 -0
  141. package/dist/timeline/fleet-decision-row.d.ts +11 -0
  142. package/dist/timeline/fold-memory.d.ts +15 -0
  143. package/dist/timeline/index.d.ts +18 -0
  144. package/dist/timeline/parsers.d.ts +96 -0
  145. package/dist/timeline/projection.d.ts +38 -0
  146. package/dist/timeline/registry.d.ts +40 -0
  147. package/dist/timeline/screenshot-lightbox.d.ts +26 -0
  148. package/dist/timeline/seen-activity-ids.d.ts +2 -0
  149. package/dist/timeline/shared.d.ts +125 -0
  150. package/dist/timeline/tool-diff.d.ts +8 -0
  151. package/dist/timeline/tool-display-name.d.ts +10 -0
  152. package/dist/timeline/tool-renderers.d.ts +5 -0
  153. package/dist/timeline/turn-summary.d.ts +116 -0
  154. package/dist/{use-turn-queue-D_1a8W_l.d.ts → timeline/types.d.ts} +41 -69
  155. package/dist/types/machines.d.ts +11 -0
  156. package/package.json +8 -4
  157. package/src/client.ts +1 -0
  158. package/src/components/composer-transcription-control.tsx +278 -130
  159. package/src/components/composer.tsx +20 -15
  160. package/src/components/copy-button.tsx +127 -0
  161. package/src/components/markdown.tsx +231 -32
  162. package/src/components/message-timeline.tsx +1304 -378
  163. package/src/components/model-picker.tsx +83 -43
  164. package/src/components/queue-surface-implementation.tsx +1 -1
  165. package/src/components/session-chrome.tsx +942 -0
  166. package/src/components/soften-streaming-markdown.ts +205 -0
  167. package/src/components/stream-reveal.ts +195 -0
  168. package/src/components/tip-follow.ts +459 -0
  169. package/src/components/tooltip.tsx +103 -0
  170. package/src/composer.ts +6 -0
  171. package/src/hooks/internal.ts +30 -4
  172. package/src/hooks/use-available-models.ts +51 -4
  173. package/src/hooks/use-composer.ts +23 -3
  174. package/src/hooks/use-last-started-turn-policy.ts +95 -0
  175. package/src/hooks/use-session-events.ts +359 -5
  176. package/src/hooks/use-voice-input.ts +232 -0
  177. package/src/index.ts +47 -1
  178. package/src/lib/clipboard.ts +58 -0
  179. package/src/lib/format.ts +18 -0
  180. package/src/lib/motion-inspect.ts +5 -0
  181. package/src/lib/motion.ts +80 -0
  182. package/src/model-policy.ts +210 -0
  183. package/src/session-ui.ts +7 -0
  184. package/src/timeline/activity-rail.tsx +46 -7
  185. package/src/timeline/entrance.tsx +10 -0
  186. package/src/timeline/fold-memory.ts +52 -0
  187. package/src/timeline/index.ts +9 -1
  188. package/src/timeline/projection.ts +226 -41
  189. package/src/timeline/seen-activity-ids.ts +19 -0
  190. package/src/timeline/shared.tsx +1 -1
  191. package/src/timeline/tool-renderers.tsx +75 -6
  192. package/src/timeline/turn-summary.tsx +387 -97
  193. package/src/timeline/types.ts +21 -0
  194. package/styles/index.css +99 -7
  195. package/styles/tokens.css +44 -3
  196. package/dist/chunk-AVLMIWYO.js.map +0 -1
  197. package/dist/chunk-CSJBF3QJ.js.map +0 -1
  198. package/dist/chunk-IPVOVOW6.js.map +0 -1
  199. package/dist/chunk-M7X4JZOD.js.map +0 -1
  200. package/dist/chunk-OKXCECLG.js.map +0 -1
  201. package/dist/chunk-ZMNPFZWL.js +0 -3918
  202. package/dist/chunk-ZMNPFZWL.js.map +0 -1
  203. package/dist/composer-BMzimGTO.d.ts +0 -528
  204. package/dist/session-DaONt-dY.d.ts +0 -264
  205. package/dist/session-context-CAjlaeZb.d.ts +0 -84
  206. package/dist/session-ui-B5_roA95.d.ts +0 -259
  207. /package/dist/{chunk-BC6RKIHS.js.map → chunk-GZ4KR67U.js.map} +0 -0
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Display-only softener for mid-stream markdown.
3
+ *
4
+ * While tokens are still arriving, incomplete markers (`**bold`, an open
5
+ * fence, a half-finished link) would otherwise render as raw punctuation —
6
+ * the hard "breaking" flash. This closes a small set of common unfinished
7
+ * constructs so the parse looks settled; the SOURCE string is never mutated
8
+ * (callers pass the softened copy only into the markdown parser).
9
+ *
10
+ * When the real closers land, the softened and true parses converge and the
11
+ * crystallize breath covers the remount.
12
+ */
13
+
14
+ export function softenStreamingMarkdown(source: string): string {
15
+ if (source.length === 0) {
16
+ return source;
17
+ }
18
+ let text = source;
19
+
20
+ // Unclosed fenced code block: an odd count of ``` line-openers means the
21
+ // trailing fence is still in flight. Always soft-close — including an empty
22
+ // opener — so code chrome (padding/tint) appears once at ``` and lines only
23
+ // grow the body. Stripping empty openers deferred chrome until the first
24
+ // body line, which landed as one fat height spike (yank around fences).
25
+ //
26
+ // If the body already ends with `\n`, append only ``` — a second `\n``` would
27
+ // invent a blank line that vanishes when the real closer lands (height shrink
28
+ // → tip-follow compensate + mid soft-rise cancel = up/down flicker).
29
+ if (countFences(text) % 2 === 1) {
30
+ text += text.endsWith("\n") ? "```" : "\n```";
31
+ }
32
+
33
+ // Work outside fenced blocks so we don't "fix" markers that are literal
34
+ // code content.
35
+ const segments = splitByFences(text);
36
+ for (const segment of segments) {
37
+ if (segment.kind === "fence") {
38
+ continue;
39
+ }
40
+ segment.value = closeInlineMarkers(segment.value);
41
+ }
42
+ let result = segments.map((segment) => segment.value).join("");
43
+ // split("\n") drops a trailing newline into an empty final line; restore it
44
+ // so balanced finished fences round-trip byte-identical.
45
+ if (text.endsWith("\n") && !result.endsWith("\n")) {
46
+ result += "\n";
47
+ }
48
+ return result;
49
+ }
50
+
51
+ function countFences(text: string): number {
52
+ let count = 0;
53
+ for (const line of text.split("\n")) {
54
+ if (/^ {0,3}```/.test(line)) {
55
+ count += 1;
56
+ }
57
+ }
58
+ return count;
59
+ }
60
+
61
+ type Segment = { kind: "prose" | "fence"; value: string };
62
+
63
+ function splitByFences(text: string): Segment[] {
64
+ const segments: Segment[] = [];
65
+ const lines = text.split("\n");
66
+ let buf: string[] = [];
67
+ let inFence = false;
68
+ /**
69
+ * `boundary`: prose → fence. `join` alone drops a blank line before ```
70
+ * (`["a",""].join` → `"a\n"`, need `"a\n\n"`), so append an extra `\n`.
71
+ * `end`: final segment. Trailing `""` from `split("\n")` already encodes a
72
+ * terminating newline — do NOT append another (that was inventing `\n\n`
73
+ * on every streamed line and shrinking it away at crystallize).
74
+ */
75
+ const flush = (kind: Segment["kind"], mode: "boundary" | "end") => {
76
+ if (buf.length === 0) {
77
+ return;
78
+ }
79
+ let value = buf.join("\n");
80
+ if (mode === "boundary") {
81
+ value += "\n";
82
+ }
83
+ segments.push({ kind, value });
84
+ buf = [];
85
+ };
86
+ for (const line of lines) {
87
+ const isFence = /^ {0,3}```/.test(line);
88
+ if (isFence) {
89
+ if (inFence) {
90
+ buf.push(line);
91
+ flush("fence", "end");
92
+ inFence = false;
93
+ } else {
94
+ flush("prose", "boundary");
95
+ buf.push(line);
96
+ inFence = true;
97
+ }
98
+ continue;
99
+ }
100
+ buf.push(line);
101
+ }
102
+ flush(inFence ? "fence" : "prose", "end");
103
+ return segments;
104
+ }
105
+
106
+ /**
107
+ * Close unmatched **, *, ~~, and ` in prose. Order matters: handle the
108
+ * longer bold/strike markers before single `*` so we don't steal from `**`.
109
+ */
110
+ function closeInlineMarkers(prose: string): string {
111
+ let text = prose;
112
+ text = closeDelimiter(text, "~~");
113
+ text = closeDelimiter(text, "**");
114
+ text = closeDelimiter(text, "__");
115
+ text = closeSingleBackticks(text);
116
+ text = closeSingleAsterisks(text);
117
+ text = closeUnfinishedLink(text);
118
+ return text;
119
+ }
120
+
121
+ function closeDelimiter(text: string, delimiter: string): string {
122
+ // Count occurrences that aren't escaped. Odd → append a closer.
123
+ let count = 0;
124
+ let i = 0;
125
+ while (i < text.length) {
126
+ if (text[i] === "\\" && i + 1 < text.length) {
127
+ i += 2;
128
+ continue;
129
+ }
130
+ if (text.startsWith(delimiter, i)) {
131
+ count += 1;
132
+ i += delimiter.length;
133
+ continue;
134
+ }
135
+ i += 1;
136
+ }
137
+ return count % 2 === 1 ? insertBeforeTrailingNewlines(text, delimiter) : text;
138
+ }
139
+
140
+ /** Soft closers belong before trailing `\n`s so restore/round-trip stay stable. */
141
+ function insertBeforeTrailingNewlines(text: string, suffix: string): string {
142
+ let end = text.length;
143
+ while (end > 0 && text[end - 1] === "\n") {
144
+ end -= 1;
145
+ }
146
+ return text.slice(0, end) + suffix + text.slice(end);
147
+ }
148
+
149
+ function closeSingleBackticks(text: string): string {
150
+ // Ignore ``` (fences already stripped) and even pairs. A trailing unmatched
151
+ // ` is the common "inline code still typing" case.
152
+ let count = 0;
153
+ for (let i = 0; i < text.length; i += 1) {
154
+ if (text[i] === "\\" && i + 1 < text.length) {
155
+ i += 1;
156
+ continue;
157
+ }
158
+ if (text[i] === "`") {
159
+ // Skip triple backticks if any leaked into prose.
160
+ if (text.startsWith("```", i)) {
161
+ i += 2;
162
+ continue;
163
+ }
164
+ count += 1;
165
+ }
166
+ }
167
+ return count % 2 === 1 ? insertBeforeTrailingNewlines(text, "`") : text;
168
+ }
169
+
170
+ function closeSingleAsterisks(text: string): string {
171
+ // Count `*` that are NOT part of `**` (already handled). Odd → close italic.
172
+ let count = 0;
173
+ for (let i = 0; i < text.length; i += 1) {
174
+ if (text[i] === "\\" && i + 1 < text.length) {
175
+ i += 1;
176
+ continue;
177
+ }
178
+ if (text[i] === "*" && text[i + 1] === "*") {
179
+ i += 1;
180
+ continue;
181
+ }
182
+ if (text[i] === "*") {
183
+ count += 1;
184
+ }
185
+ }
186
+ return count % 2 === 1 ? insertBeforeTrailingNewlines(text, "*") : text;
187
+ }
188
+
189
+ function closeUnfinishedLink(text: string): string {
190
+ // `[label](url` without the trailing `)` — close it. Bare `[label` without
191
+ // `](` is left alone (too ambiguous with footnotes / prose brackets).
192
+ const open = text.lastIndexOf("](");
193
+ if (open === -1) {
194
+ return text;
195
+ }
196
+ const after = text.slice(open + 2);
197
+ if (after.includes(")")) {
198
+ return text;
199
+ }
200
+ // Don't close if a later `[` started something else after.
201
+ if (after.includes("[")) {
202
+ return text;
203
+ }
204
+ return insertBeforeTrailingNewlines(text, ")");
205
+ }
@@ -0,0 +1,195 @@
1
+ /* ----------------------------------------------------------------------------
2
+ Streaming tip ink (age-window fade)
3
+
4
+ Append-only markdown grows at the tip. Rather than staggering per-word
5
+ onsets (glitter under fast models), each append batch is born at wall time
6
+ and fades in over a fixed age window:
7
+
8
+ opacity progress = clamp((now − birth) / INK_FADE_MS, 0, 1)
9
+
10
+ Fast arrival → many recent characters still young → a large soft band.
11
+ Slow arrival → only the tip is young → a tight tip wash.
12
+ Same rule both ways; time does the work. No per-word stagger, no artificial
13
+ content lag — layout always holds the truthful text; paint only.
14
+
15
+ Identity is the character offset into the source. CSS animation-delay =
16
+ birth − now (often negative) so mid-fade re-renders resume, never restart.
17
+ Runs settle back to plain text once age ≥ INK_FADE_MS (zero span cost).
18
+ -------------------------------------------------------------------------- */
19
+
20
+ import { MOTION_INSPECT_SCALE } from "../lib/motion-inspect";
21
+
22
+ /** Keep in sync with `--og-duration-stream` / `.og-stream-ink` in styles. */
23
+ export const INK_FADE_MS = 420 * MOTION_INSPECT_SCALE;
24
+ /** @deprecated Use {@link INK_FADE_MS}. */
25
+ export const WORD_ANIMATION_MS = INK_FADE_MS;
26
+
27
+ /**
28
+ * A first observation larger than this is pre-existing history (timeline
29
+ * opened mid-stream), not live tip — it renders instantly.
30
+ */
31
+ const INITIAL_ANIMATED_LIMIT = 400;
32
+
33
+ type InkBatch = {
34
+ start: number;
35
+ end: number;
36
+ birth: number;
37
+ };
38
+
39
+ export type StreamReveal = {
40
+ /** Register the full current source text; new suffix becomes an ink batch. */
41
+ observe(text: string, now: number): void;
42
+ /**
43
+ * CSS animation-delay (ms) for the run covering `offset`, or null when that
44
+ * ink has settled and should render as plain text. Negative = resume mid-fade.
45
+ */
46
+ delayFor(offset: number, now: number): number | null;
47
+ /** True while any batch is still inside its fade window. */
48
+ hasActive(now: number): boolean;
49
+ };
50
+
51
+ export function createStreamReveal(): StreamReveal {
52
+ let batches: InkBatch[] = [];
53
+ let revealedChars = -1;
54
+
55
+ return {
56
+ observe(text, now) {
57
+ if (revealedChars === -1) {
58
+ revealedChars = text.length > INITIAL_ANIMATED_LIMIT ? text.length : 0;
59
+ }
60
+ if (text.length <= revealedChars) {
61
+ // Idempotent re-observation (StrictMode) or defensive no-op on shrink.
62
+ revealedChars = Math.max(revealedChars, text.length);
63
+ return;
64
+ }
65
+ batches.push({ start: revealedChars, end: text.length, birth: now });
66
+ revealedChars = text.length;
67
+ batches = batches.filter((batch) => batch.birth + INK_FADE_MS > now);
68
+ },
69
+
70
+ delayFor(offset, now) {
71
+ const batch = findBatch(batches, offset);
72
+ if (!batch) {
73
+ return null;
74
+ }
75
+ const delay = batch.birth - now;
76
+ return delay + INK_FADE_MS <= 0 ? null : delay;
77
+ },
78
+
79
+ hasActive(now) {
80
+ const last = batches[batches.length - 1];
81
+ return last !== undefined && last.birth + INK_FADE_MS > now;
82
+ },
83
+ };
84
+ }
85
+
86
+ function findBatch(batches: readonly InkBatch[], offset: number): InkBatch | null {
87
+ let low = 0;
88
+ let high = batches.length - 1;
89
+ while (low <= high) {
90
+ const mid = (low + high) >> 1;
91
+ const batch = batches[mid]!;
92
+ if (offset < batch.start) {
93
+ high = mid - 1;
94
+ } else if (offset >= batch.end) {
95
+ low = mid + 1;
96
+ } else {
97
+ return batch;
98
+ }
99
+ }
100
+ return null;
101
+ }
102
+
103
+ /* ----------------------------------------------------------------------------
104
+ rehype: wrap still-fading source runs in `.og-stream-ink` spans.
105
+
106
+ Contiguous offsets that share one birth (one append batch) become ONE span —
107
+ a continuous ink surface, not per-word glitter. Code/pre stay plain. Nodes
108
+ without source positions (syntax rewritten around them) render plain too.
109
+ -------------------------------------------------------------------------- */
110
+
111
+ type HastText = {
112
+ type: "text";
113
+ value: string;
114
+ position?: { start?: { offset?: number | undefined } | undefined } | undefined;
115
+ };
116
+
117
+ type HastParent = {
118
+ type: string;
119
+ tagName?: string;
120
+ value?: string;
121
+ children?: HastChild[];
122
+ };
123
+
124
+ type HastChild = (HastText | HastParent) & Record<string, unknown>;
125
+
126
+ export function rehypeStreamReveal(options: { reveal: StreamReveal; now: number }) {
127
+ const { reveal, now } = options;
128
+ return (tree: HastParent) => {
129
+ walk(tree, false);
130
+ };
131
+
132
+ function walk(node: HastParent, insideVerbatim: boolean): void {
133
+ if (!node.children) {
134
+ return;
135
+ }
136
+ const verbatim = insideVerbatim || node.tagName === "code" || node.tagName === "pre";
137
+ const next: HastChild[] = [];
138
+ for (const child of node.children) {
139
+ if (child.type === "text" && !verbatim) {
140
+ next.push(...splitTextNode(child as HastText));
141
+ continue;
142
+ }
143
+ walk(child as HastParent, verbatim);
144
+ next.push(child);
145
+ }
146
+ node.children = next;
147
+ }
148
+
149
+ function splitTextNode(node: HastText): HastChild[] {
150
+ const sourceOffset = node.position?.start?.offset;
151
+ if (sourceOffset === undefined) {
152
+ return [node as HastChild];
153
+ }
154
+ const value = node.value;
155
+ const out: HastChild[] = [];
156
+ let plain = "";
157
+ const flushPlain = () => {
158
+ if (plain.length > 0) {
159
+ out.push({ type: "text", value: plain } as HastChild);
160
+ plain = "";
161
+ }
162
+ };
163
+
164
+ let i = 0;
165
+ while (i < value.length) {
166
+ const delay = reveal.delayFor(sourceOffset + i, now);
167
+ let j = i + 1;
168
+ while (j < value.length) {
169
+ const nextDelay = reveal.delayFor(sourceOffset + j, now);
170
+ if (nextDelay !== delay) {
171
+ break;
172
+ }
173
+ j += 1;
174
+ }
175
+ const slice = value.slice(i, j);
176
+ if (delay === null) {
177
+ plain += slice;
178
+ } else {
179
+ flushPlain();
180
+ out.push({
181
+ type: "element",
182
+ tagName: "span",
183
+ properties: {
184
+ className: ["og-stream-ink"],
185
+ style: `animation-delay:${Math.round(delay)}ms`,
186
+ },
187
+ children: [{ type: "text", value: slice }],
188
+ } as unknown as HastChild);
189
+ }
190
+ i = j;
191
+ }
192
+ flushPlain();
193
+ return out.length > 0 ? out : [node as HastChild];
194
+ }
195
+ }