nextworks 0.2.0-alpha.2 → 0.2.0-alpha.21

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 (145) hide show
  1. package/README.md +280 -228
  2. package/dist/cli_manifests/blocks_manifest.json +193 -194
  3. package/dist/commands/blocks.d.ts +3 -0
  4. package/dist/commands/blocks.d.ts.map +1 -1
  5. package/dist/commands/blocks.js +128 -19
  6. package/dist/commands/blocks.js.map +1 -1
  7. package/dist/commands/doctor.d.ts +136 -0
  8. package/dist/commands/doctor.d.ts.map +1 -0
  9. package/dist/commands/doctor.js +696 -0
  10. package/dist/commands/doctor.js.map +1 -0
  11. package/dist/commands/remove-blocks.d.ts +4 -1
  12. package/dist/commands/remove-blocks.d.ts.map +1 -1
  13. package/dist/commands/remove-blocks.js +24 -6
  14. package/dist/commands/remove-blocks.js.map +1 -1
  15. package/dist/index.js +125 -4
  16. package/dist/index.js.map +1 -1
  17. package/dist/kits/blocks/.nextworks/docs/BLOCKS_QUICKSTART.md +104 -194
  18. package/dist/kits/blocks/.nextworks/docs/BLOCKS_README.md +115 -75
  19. package/dist/kits/blocks/.nextworks/docs/THEME_GUIDE.md +269 -251
  20. package/dist/kits/blocks/app/templates/digitalagency/PresetThemeVars.tsx +80 -80
  21. package/dist/kits/blocks/app/templates/digitalagency/README.md +42 -36
  22. package/dist/kits/blocks/app/templates/digitalagency/components/About.tsx +71 -99
  23. package/dist/kits/blocks/app/templates/digitalagency/components/CTA.tsx +40 -74
  24. package/dist/kits/blocks/app/templates/digitalagency/components/Contact.tsx +123 -227
  25. package/dist/kits/blocks/app/templates/digitalagency/components/Footer.tsx +89 -89
  26. package/dist/kits/blocks/app/templates/digitalagency/components/Hero.tsx +83 -90
  27. package/dist/kits/blocks/app/templates/digitalagency/components/Navbar.tsx +121 -168
  28. package/dist/kits/blocks/app/templates/digitalagency/components/NetworkPattern.tsx +288 -297
  29. package/dist/kits/blocks/app/templates/digitalagency/components/Portfolio.tsx +157 -157
  30. package/dist/kits/blocks/app/templates/digitalagency/components/Pricing.tsx +114 -114
  31. package/dist/kits/blocks/app/templates/digitalagency/components/Process.tsx +59 -59
  32. package/dist/kits/blocks/app/templates/digitalagency/components/Services.tsx +55 -55
  33. package/dist/kits/blocks/app/templates/digitalagency/components/Team.tsx +28 -28
  34. package/dist/kits/blocks/app/templates/digitalagency/components/Testimonials.tsx +65 -65
  35. package/dist/kits/blocks/app/templates/digitalagency/page.tsx +38 -38
  36. package/dist/kits/blocks/app/templates/gallery/PresetThemeVars.tsx +130 -85
  37. package/dist/kits/blocks/app/templates/gallery/page.tsx +357 -303
  38. package/dist/kits/blocks/app/templates/productlaunch/PresetThemeVars.tsx +75 -74
  39. package/dist/kits/blocks/app/templates/productlaunch/README.md +62 -55
  40. package/dist/kits/blocks/app/templates/productlaunch/components/About.tsx +84 -178
  41. package/dist/kits/blocks/app/templates/productlaunch/components/CTA.tsx +50 -93
  42. package/dist/kits/blocks/app/templates/productlaunch/components/Contact.tsx +231 -231
  43. package/dist/kits/blocks/app/templates/productlaunch/components/FAQ.tsx +86 -93
  44. package/dist/kits/blocks/app/templates/productlaunch/components/Features.tsx +83 -84
  45. package/dist/kits/blocks/app/templates/productlaunch/components/Footer.tsx +132 -132
  46. package/dist/kits/blocks/app/templates/productlaunch/components/Hero.tsx +88 -89
  47. package/dist/kits/blocks/app/templates/productlaunch/components/Navbar.tsx +116 -162
  48. package/dist/kits/blocks/app/templates/productlaunch/components/Pricing.tsx +106 -106
  49. package/dist/kits/blocks/app/templates/productlaunch/components/ProcessTimeline.tsx +110 -110
  50. package/dist/kits/blocks/app/templates/productlaunch/components/ServicesGrid.tsx +68 -68
  51. package/dist/kits/blocks/app/templates/productlaunch/components/Team.tsx +104 -104
  52. package/dist/kits/blocks/app/templates/productlaunch/components/Testimonials.tsx +90 -89
  53. package/dist/kits/blocks/app/templates/productlaunch/components/TrustBadges.tsx +76 -76
  54. package/dist/kits/blocks/app/templates/productlaunch/page.tsx +43 -43
  55. package/dist/kits/blocks/app/templates/saasdashboard/PresetThemeVars.tsx +80 -80
  56. package/dist/kits/blocks/app/templates/saasdashboard/README.md +44 -38
  57. package/dist/kits/blocks/app/templates/saasdashboard/components/Contact.tsx +129 -176
  58. package/dist/kits/blocks/app/templates/saasdashboard/components/Dashboard.tsx +293 -293
  59. package/dist/kits/blocks/app/templates/saasdashboard/components/FAQ.tsx +55 -55
  60. package/dist/kits/blocks/app/templates/saasdashboard/components/Features.tsx +90 -91
  61. package/dist/kits/blocks/app/templates/saasdashboard/components/Footer.tsx +77 -77
  62. package/dist/kits/blocks/app/templates/saasdashboard/components/Hero.tsx +104 -105
  63. package/dist/kits/blocks/app/templates/saasdashboard/components/Hero_mask.tsx +126 -127
  64. package/dist/kits/blocks/app/templates/saasdashboard/components/Navbar.tsx +117 -159
  65. package/dist/kits/blocks/app/templates/saasdashboard/components/Pricing.tsx +90 -90
  66. package/dist/kits/blocks/app/templates/saasdashboard/components/SmoothScroll.tsx +96 -97
  67. package/dist/kits/blocks/app/templates/saasdashboard/components/Testimonials.tsx +72 -72
  68. package/dist/kits/blocks/app/templates/saasdashboard/components/TrustBadges.tsx +53 -53
  69. package/dist/kits/blocks/app/templates/saasdashboard/page.tsx +39 -39
  70. package/dist/kits/blocks/components/enhanced-theme-provider.tsx +195 -183
  71. package/dist/kits/blocks/components/providers/BlocksAppProviders.tsx +27 -27
  72. package/dist/kits/blocks/components/sections/About.tsx +291 -291
  73. package/dist/kits/blocks/components/sections/CTA.tsx +257 -258
  74. package/dist/kits/blocks/components/sections/CommandShowcase.tsx +517 -0
  75. package/dist/kits/blocks/components/sections/Contact.tsx +267 -267
  76. package/dist/kits/blocks/components/sections/FAQ.tsx +214 -225
  77. package/dist/kits/blocks/components/sections/FeaturedProjectShowcase.tsx +687 -0
  78. package/dist/kits/blocks/components/sections/Features.tsx +268 -270
  79. package/dist/kits/blocks/components/sections/Footer.tsx +302 -302
  80. package/dist/kits/blocks/components/sections/HeroMotion.tsx +308 -308
  81. package/dist/kits/blocks/components/sections/HeroOverlay.tsx +358 -358
  82. package/dist/kits/blocks/components/sections/HeroProductDemo.tsx +246 -0
  83. package/dist/kits/blocks/components/sections/HeroSplit.tsx +352 -352
  84. package/dist/kits/blocks/components/sections/HeroWithVideo.tsx +495 -0
  85. package/dist/kits/blocks/components/sections/Navbar.tsx +352 -353
  86. package/dist/kits/blocks/components/sections/PortfolioSimple.tsx +549 -550
  87. package/dist/kits/blocks/components/sections/Pricing.tsx +264 -264
  88. package/dist/kits/blocks/components/sections/ProcessTimeline.tsx +325 -325
  89. package/dist/kits/blocks/components/sections/ProjectDeepDive.tsx +805 -0
  90. package/dist/kits/blocks/components/sections/SelectedWorkRail.tsx +485 -0
  91. package/dist/kits/blocks/components/sections/ServicesGrid.tsx +210 -210
  92. package/dist/kits/blocks/components/sections/Team.tsx +309 -309
  93. package/dist/kits/blocks/components/sections/Testimonials.tsx +158 -158
  94. package/dist/kits/blocks/components/sections/TrustBadges.tsx +162 -162
  95. package/dist/kits/blocks/components/sections/product-demo/ApprovalInboxPanel.tsx +128 -0
  96. package/dist/kits/blocks/components/sections/product-demo/DemoStage.tsx +519 -0
  97. package/dist/kits/blocks/components/sections/product-demo/DemoWindow.tsx +140 -0
  98. package/dist/kits/blocks/components/sections/product-demo/KnowledgePanel.tsx +130 -0
  99. package/dist/kits/blocks/components/sections/product-demo/RunConsolePanel.tsx +430 -0
  100. package/dist/kits/blocks/components/sections/product-demo/TaskListPanel.tsx +302 -0
  101. package/dist/kits/blocks/components/sections/product-demo/WorkflowStudioPanel.tsx +744 -0
  102. package/dist/kits/blocks/components/sections/product-demo/types.ts +262 -0
  103. package/dist/kits/blocks/components/theme-provider.tsx +1 -34
  104. package/dist/kits/blocks/components/ui/alert-dialog.tsx +134 -134
  105. package/dist/kits/blocks/components/ui/brand-node.tsx +121 -121
  106. package/dist/kits/blocks/components/ui/button.tsx +122 -122
  107. package/dist/kits/blocks/components/ui/card.tsx +95 -95
  108. package/dist/kits/blocks/components/ui/checkbox.tsx +30 -30
  109. package/dist/kits/blocks/components/ui/cta-button.tsx +125 -125
  110. package/dist/kits/blocks/components/ui/dropdown-menu.tsx +201 -201
  111. package/dist/kits/blocks/components/ui/feature-card.tsx +91 -91
  112. package/dist/kits/blocks/components/ui/input.tsx +27 -27
  113. package/dist/kits/blocks/components/ui/label.tsx +29 -29
  114. package/dist/kits/blocks/components/ui/pricing-card.tsx +120 -120
  115. package/dist/kits/blocks/components/ui/select.tsx +25 -25
  116. package/dist/kits/blocks/components/ui/skeleton.tsx +13 -13
  117. package/dist/kits/blocks/components/ui/table.tsx +98 -98
  118. package/dist/kits/blocks/components/ui/testimonial-card.tsx +108 -108
  119. package/dist/kits/blocks/components/ui/textarea.tsx +26 -26
  120. package/dist/kits/blocks/components/ui/theme-selector.tsx +243 -247
  121. package/dist/kits/blocks/components/ui/theme-toggle.tsx +74 -74
  122. package/dist/kits/blocks/components/ui/toaster.tsx +7 -7
  123. package/dist/kits/blocks/lib/themes.ts +400 -400
  124. package/dist/kits/blocks/lib/utils.ts +6 -9
  125. package/dist/kits/blocks/package-deps.json +40 -28
  126. package/dist/kits/blocks/tsconfig.json +11 -0
  127. package/dist/kits/blocks/tsconfig.tsbuildinfo +1 -0
  128. package/dist/utils/file-operations.d.ts +3 -1
  129. package/dist/utils/file-operations.d.ts.map +1 -1
  130. package/dist/utils/file-operations.js +84 -12
  131. package/dist/utils/file-operations.js.map +1 -1
  132. package/dist/utils/installation-tracker.d.ts +2 -2
  133. package/dist/utils/installation-tracker.d.ts.map +1 -1
  134. package/dist/utils/installation-tracker.js +11 -11
  135. package/dist/utils/installation-tracker.js.map +1 -1
  136. package/dist/utils/package-manager.d.ts +6 -0
  137. package/dist/utils/package-manager.d.ts.map +1 -0
  138. package/dist/utils/package-manager.js +58 -0
  139. package/dist/utils/package-manager.js.map +1 -0
  140. package/dist/utils/yarn-pnp.d.ts +6 -0
  141. package/dist/utils/yarn-pnp.d.ts.map +1 -0
  142. package/dist/utils/yarn-pnp.js +39 -0
  143. package/dist/utils/yarn-pnp.js.map +1 -0
  144. package/package.json +5 -2
  145. package/dist/kits/blocks/notes/THEME_GUIDE.md +0 -29
@@ -0,0 +1,744 @@
1
+ import React from "react";
2
+ import type {
3
+ ProductDemoWorkflowStudioState,
4
+ ProductDemoWorkflowTranscriptEntry,
5
+ } from "./types";
6
+
7
+ function useAnimatedPatchCount(target: number | undefined, visible: boolean) {
8
+ const safeTarget =
9
+ typeof target === "number" ? Math.max(target, 0) : undefined;
10
+ const [displayValue, setDisplayValue] = React.useState(() =>
11
+ typeof safeTarget === "number" ? 1 : 0,
12
+ );
13
+
14
+ React.useEffect(() => {
15
+ if (typeof safeTarget !== "number") {
16
+ return;
17
+ }
18
+
19
+ if (!visible) {
20
+ setDisplayValue(safeTarget);
21
+ return;
22
+ }
23
+
24
+ if (safeTarget <= 2) {
25
+ setDisplayValue(safeTarget);
26
+ return;
27
+ }
28
+
29
+ let frameId = 0;
30
+ const duration = Math.min(900, Math.max(520, safeTarget * 26));
31
+ const start = performance.now();
32
+
33
+ const tick = (now: number) => {
34
+ const elapsed = now - start;
35
+ const progress = Math.min(elapsed / duration, 1);
36
+ const eased = 1 - Math.pow(1 - progress, 3);
37
+ const nextValue = Math.max(1, Math.round(safeTarget * eased));
38
+
39
+ setDisplayValue(nextValue);
40
+
41
+ if (progress < 1) {
42
+ frameId = window.requestAnimationFrame(tick);
43
+ }
44
+ };
45
+
46
+ setDisplayValue(1);
47
+ frameId = window.requestAnimationFrame(tick);
48
+
49
+ return () => window.cancelAnimationFrame(frameId);
50
+ }, [safeTarget, visible]);
51
+
52
+ return typeof safeTarget === "number" ? displayValue : undefined;
53
+ }
54
+
55
+ function PatchCount({
56
+ prefix,
57
+ value,
58
+ visible,
59
+ className,
60
+ }: {
61
+ prefix: "+" | "-";
62
+ value?: number;
63
+ visible: boolean;
64
+ className: string;
65
+ }) {
66
+ const animatedValue = useAnimatedPatchCount(value, visible);
67
+
68
+ if (typeof value !== "number" || typeof animatedValue !== "number") {
69
+ return null;
70
+ }
71
+
72
+ return (
73
+ <span className={className}>
74
+ {prefix}
75
+ {animatedValue}
76
+ </span>
77
+ );
78
+ }
79
+
80
+ export interface WorkflowStudioPanelProps {
81
+ state: ProductDemoWorkflowStudioState;
82
+ }
83
+
84
+ type SessionEntry = ProductDemoWorkflowTranscriptEntry & {
85
+ origin?: "user" | "system";
86
+ };
87
+
88
+ function normalizeEntry(
89
+ entry: string | ProductDemoWorkflowTranscriptEntry,
90
+ index: number,
91
+ ): ProductDemoWorkflowTranscriptEntry {
92
+ if (typeof entry !== "string") {
93
+ return entry;
94
+ }
95
+
96
+ if (index === 0) {
97
+ return { id: `entry-${index}`, kind: "title", text: entry };
98
+ }
99
+
100
+ if (/^thought/i.test(entry)) {
101
+ return { id: `entry-${index}`, kind: "thought", text: entry };
102
+ }
103
+
104
+ return { id: `entry-${index}`, kind: "activity", text: entry };
105
+ }
106
+
107
+ function getEntryLabel(kind?: ProductDemoWorkflowTranscriptEntry["kind"]) {
108
+ switch (kind) {
109
+ case "prompt":
110
+ return "Task";
111
+ case "activity":
112
+ return "Action";
113
+ case "thought":
114
+ return "Reasoning";
115
+ case "message":
116
+ return "Update";
117
+ case "file":
118
+ return "Patch";
119
+ default:
120
+ return "Session";
121
+ }
122
+ }
123
+
124
+ export function WorkflowStudioPanel({ state }: WorkflowStudioPanelProps) {
125
+ const scrollViewportRef = React.useRef<HTMLDivElement | null>(null);
126
+ const scrollbarTrackRef = React.useRef<HTMLDivElement | null>(null);
127
+ const dragStateRef = React.useRef<{
128
+ pointerId: number;
129
+ startY: number;
130
+ startScrollTop: number;
131
+ scrollRatio: number;
132
+ pointerOffsetY: number;
133
+ } | null>(null);
134
+ const activeIndex = state.nodes.findIndex(
135
+ (node) => node.id === state.activeNodeId || node.active,
136
+ );
137
+
138
+ const activeNode = activeIndex >= 0 ? state.nodes[activeIndex] : undefined;
139
+ const transcript = (state.transcript ?? []).map(normalizeEntry);
140
+ const composer = state.composer;
141
+ const playbackMs = state.playbackMs ?? 1800;
142
+ const [visibleCount, setVisibleCount] = React.useState(
143
+ state.playbackStep ?? Math.max(1, Math.min(2, transcript.length)),
144
+ );
145
+ const [scrollMetrics, setScrollMetrics] = React.useState({
146
+ scrollTop: 0,
147
+ scrollHeight: 1,
148
+ clientHeight: 1,
149
+ });
150
+ const [composerValue, setComposerValue] = React.useState("");
151
+ const [localItems, setLocalItems] = React.useState<
152
+ Array<SessionEntry & { insertionIndex: number; order: number }>
153
+ >([]);
154
+ const submissionSeqRef = React.useRef(0);
155
+ const pendingTimeoutsRef = React.useRef<number[]>([]);
156
+ const previousPlaybackStepRef = React.useRef<number | undefined>(
157
+ state.playbackStep,
158
+ );
159
+
160
+ React.useEffect(() => {
161
+ setComposerValue("");
162
+ setLocalItems([]);
163
+ submissionSeqRef.current = 0;
164
+
165
+ pendingTimeoutsRef.current.forEach((timeoutId) => {
166
+ window.clearTimeout(timeoutId);
167
+ });
168
+ pendingTimeoutsRef.current = [];
169
+ }, [state.title, state.subtitle, state.activeNodeId, state.window.key]);
170
+
171
+ React.useEffect(() => {
172
+ return () => {
173
+ pendingTimeoutsRef.current.forEach((timeoutId) => {
174
+ window.clearTimeout(timeoutId);
175
+ });
176
+ pendingTimeoutsRef.current = [];
177
+ };
178
+ }, []);
179
+
180
+ const scrollToBottom = React.useCallback(() => {
181
+ const viewport = scrollViewportRef.current;
182
+
183
+ if (!viewport) {
184
+ return;
185
+ }
186
+
187
+ viewport.scrollTop = viewport.scrollHeight;
188
+ }, []);
189
+
190
+ const handleComposerSubmit = React.useCallback(
191
+ (event: React.FormEvent<HTMLFormElement>) => {
192
+ event.preventDefault();
193
+
194
+ const trimmedValue = composerValue.trim();
195
+
196
+ if (!trimmedValue) {
197
+ return;
198
+ }
199
+
200
+ const submissionIndex = submissionSeqRef.current + 1;
201
+ submissionSeqRef.current = submissionIndex;
202
+ const insertionIndex = Math.max(visibleCount, 1);
203
+
204
+ const promptEntry: SessionEntry & {
205
+ insertionIndex: number;
206
+ order: number;
207
+ } = {
208
+ id: `local-prompt-${submissionIndex}`,
209
+ kind: "prompt",
210
+ text: trimmedValue,
211
+ origin: "user",
212
+ insertionIndex,
213
+ order: 0,
214
+ };
215
+ const responseEntry: SessionEntry & {
216
+ insertionIndex: number;
217
+ order: number;
218
+ } = {
219
+ id: `local-response-${submissionIndex}`,
220
+ kind: "message",
221
+ text: "Added to session.",
222
+ origin: "system",
223
+ insertionIndex,
224
+ order: 1,
225
+ };
226
+
227
+ setLocalItems((currentItems) => [...currentItems, promptEntry]);
228
+ setComposerValue("");
229
+ window.requestAnimationFrame(scrollToBottom);
230
+
231
+ const timeoutId = window.setTimeout(() => {
232
+ setLocalItems((currentItems) => [...currentItems, responseEntry]);
233
+ window.requestAnimationFrame(scrollToBottom);
234
+ }, 680);
235
+
236
+ pendingTimeoutsRef.current.push(timeoutId);
237
+ },
238
+ [composerValue, scrollToBottom, visibleCount],
239
+ );
240
+
241
+ React.useEffect(() => {
242
+ if (typeof state.playbackStep === "number") {
243
+ const previousPlaybackStep = previousPlaybackStepRef.current;
244
+ const isPlaybackLoopReset =
245
+ typeof previousPlaybackStep === "number" &&
246
+ previousPlaybackStep > 2 &&
247
+ state.playbackStep <= 2;
248
+
249
+ if (isPlaybackLoopReset) {
250
+ setLocalItems([]);
251
+ pendingTimeoutsRef.current.forEach((timeoutId) => {
252
+ window.clearTimeout(timeoutId);
253
+ });
254
+ pendingTimeoutsRef.current = [];
255
+ }
256
+
257
+ previousPlaybackStepRef.current = state.playbackStep;
258
+ setVisibleCount(
259
+ Math.max(1, Math.min(state.playbackStep, transcript.length)),
260
+ );
261
+ return;
262
+ }
263
+
264
+ setVisibleCount(Math.max(1, Math.min(2, transcript.length)));
265
+
266
+ if (transcript.length <= 2) {
267
+ return;
268
+ }
269
+
270
+ const interval = window.setInterval(() => {
271
+ setVisibleCount((current) => {
272
+ if (current >= transcript.length) {
273
+ return 2;
274
+ }
275
+
276
+ return current + 1;
277
+ });
278
+ }, playbackMs);
279
+
280
+ return () => window.clearInterval(interval);
281
+ }, [playbackMs, transcript.length, state.window.title, state.playbackStep]);
282
+
283
+ React.useEffect(() => {
284
+ const viewport = scrollViewportRef.current;
285
+
286
+ if (!viewport) {
287
+ return;
288
+ }
289
+
290
+ const updateScrollMetrics = () => {
291
+ setScrollMetrics({
292
+ scrollTop: viewport.scrollTop,
293
+ scrollHeight: viewport.scrollHeight,
294
+ clientHeight: viewport.clientHeight,
295
+ });
296
+ };
297
+
298
+ updateScrollMetrics();
299
+
300
+ viewport.addEventListener("scroll", updateScrollMetrics, {
301
+ passive: true,
302
+ });
303
+
304
+ const resizeObserver = new ResizeObserver(() => {
305
+ updateScrollMetrics();
306
+ });
307
+
308
+ resizeObserver.observe(viewport);
309
+
310
+ if (viewport.firstElementChild instanceof HTMLElement) {
311
+ resizeObserver.observe(viewport.firstElementChild);
312
+ }
313
+
314
+ window.addEventListener("resize", updateScrollMetrics);
315
+
316
+ return () => {
317
+ viewport.removeEventListener("scroll", updateScrollMetrics);
318
+ resizeObserver.disconnect();
319
+ window.removeEventListener("resize", updateScrollMetrics);
320
+ };
321
+ }, [visibleCount, transcript.length]);
322
+
323
+ const visibleTranscript = transcript.slice(0, visibleCount);
324
+ const injectedItemsByIndex = React.useMemo(() => {
325
+ const map = new Map<number, Array<SessionEntry & { order: number }>>();
326
+
327
+ localItems.forEach((item) => {
328
+ const itemsAtIndex = map.get(item.insertionIndex) ?? [];
329
+ itemsAtIndex.push(item);
330
+ map.set(item.insertionIndex, itemsAtIndex);
331
+ });
332
+
333
+ for (const itemsAtIndex of map.values()) {
334
+ itemsAtIndex.sort((a, b) => a.order - b.order);
335
+ }
336
+
337
+ return map;
338
+ }, [localItems]);
339
+ const isRunning = visibleCount < transcript.length;
340
+
341
+ const hasOverflow =
342
+ scrollMetrics.scrollHeight > scrollMetrics.clientHeight + 1;
343
+ const thumbHeight = hasOverflow
344
+ ? Math.max(
345
+ 36,
346
+ (scrollMetrics.clientHeight / scrollMetrics.scrollHeight) *
347
+ scrollMetrics.clientHeight,
348
+ )
349
+ : 0;
350
+ const maxThumbOffset = Math.max(scrollMetrics.clientHeight - thumbHeight, 0);
351
+ const maxScrollTop = Math.max(
352
+ scrollMetrics.scrollHeight - scrollMetrics.clientHeight,
353
+ 1,
354
+ );
355
+ const thumbOffset = hasOverflow
356
+ ? (scrollMetrics.scrollTop / maxScrollTop) * maxThumbOffset
357
+ : 0;
358
+
359
+ const updateScrollTopFromPointer = React.useCallback(
360
+ (clientY: number) => {
361
+ const viewport = scrollViewportRef.current;
362
+ const track = scrollbarTrackRef.current;
363
+
364
+ if (!viewport || !track || !hasOverflow) {
365
+ return;
366
+ }
367
+
368
+ const trackRect = track.getBoundingClientRect();
369
+ const nextThumbTop = Math.min(
370
+ Math.max(
371
+ clientY - trackRect.top - dragStateRef.current!.pointerOffsetY,
372
+ 0,
373
+ ),
374
+ maxThumbOffset,
375
+ );
376
+ const nextScrollTop =
377
+ maxThumbOffset > 0 ? (nextThumbTop / maxThumbOffset) * maxScrollTop : 0;
378
+
379
+ viewport.scrollTop = nextScrollTop;
380
+ },
381
+ [hasOverflow, maxScrollTop, maxThumbOffset],
382
+ );
383
+
384
+ const handleScrollbarPointerDown = React.useCallback(
385
+ (event: React.PointerEvent<HTMLDivElement>) => {
386
+ const viewport = scrollViewportRef.current;
387
+ const track = scrollbarTrackRef.current;
388
+
389
+ if (!viewport || !track || !hasOverflow) {
390
+ return;
391
+ }
392
+
393
+ const trackRect = track.getBoundingClientRect();
394
+ const targetElement = event.target as HTMLElement | null;
395
+ const clickedThumb = targetElement?.dataset.scrollbarThumb === "true";
396
+ const pointerOffsetY = clickedThumb
397
+ ? event.clientY - trackRect.top - thumbOffset
398
+ : thumbHeight / 2;
399
+
400
+ dragStateRef.current = {
401
+ pointerId: event.pointerId,
402
+ startY: event.clientY,
403
+ startScrollTop: viewport.scrollTop,
404
+ scrollRatio: maxThumbOffset > 0 ? maxScrollTop / maxThumbOffset : 0,
405
+ pointerOffsetY,
406
+ };
407
+
408
+ if (!clickedThumb) {
409
+ updateScrollTopFromPointer(event.clientY);
410
+ }
411
+
412
+ (event.currentTarget as HTMLDivElement).setPointerCapture(
413
+ event.pointerId,
414
+ );
415
+ event.preventDefault();
416
+ },
417
+ [
418
+ hasOverflow,
419
+ maxScrollTop,
420
+ maxThumbOffset,
421
+ thumbHeight,
422
+ thumbOffset,
423
+ updateScrollTopFromPointer,
424
+ ],
425
+ );
426
+
427
+ const handleScrollbarPointerMove = React.useCallback(
428
+ (event: React.PointerEvent<HTMLDivElement>) => {
429
+ const dragState = dragStateRef.current;
430
+
431
+ if (!dragState || dragState.pointerId !== event.pointerId) {
432
+ return;
433
+ }
434
+
435
+ const viewport = scrollViewportRef.current;
436
+ const track = scrollbarTrackRef.current;
437
+
438
+ if (!viewport || !track || !hasOverflow) {
439
+ return;
440
+ }
441
+
442
+ const deltaY = event.clientY - dragState.startY;
443
+ const nextScrollTop = Math.min(
444
+ Math.max(dragState.startScrollTop + deltaY * dragState.scrollRatio, 0),
445
+ maxScrollTop,
446
+ );
447
+
448
+ viewport.scrollTop = nextScrollTop;
449
+ event.preventDefault();
450
+ },
451
+ [hasOverflow, maxScrollTop],
452
+ );
453
+
454
+ const handleScrollbarPointerUp = React.useCallback(
455
+ (event: React.PointerEvent<HTMLDivElement>) => {
456
+ const dragState = dragStateRef.current;
457
+
458
+ if (dragState?.pointerId === event.pointerId) {
459
+ dragStateRef.current = null;
460
+ }
461
+ },
462
+ [],
463
+ );
464
+
465
+ const renderSessionEntry = (
466
+ entry: SessionEntry,
467
+ index: number,
468
+ isLocalEntry = false,
469
+ ) => {
470
+ if (entry.kind === "title") {
471
+ return (
472
+ <div key={entry.id} className="space-y-2.5">
473
+ <div className="flex items-center justify-between gap-3 text-[9px] uppercase tracking-[0.15em] text-[var(--demo-subtle-fg)]">
474
+ <div className="flex min-w-0 items-center gap-2">
475
+ <span>Session focus</span>
476
+ <span className="h-1 w-1 rounded-full bg-[var(--demo-border-strong)]" />
477
+ <span className="truncate">{entry.text}</span>
478
+ </div>
479
+ {activeNode?.type ? (
480
+ <span className="rounded-full border border-[var(--demo-border)] bg-[var(--demo-panel-bg)] px-2 py-1 text-[8px] tracking-[0.16em] text-[var(--demo-muted-fg)]">
481
+ {activeNode.type}
482
+ </span>
483
+ ) : null}
484
+ </div>
485
+ {activeNode?.description ? (
486
+ <div className="rounded-lg border border-[var(--demo-border)] bg-[var(--demo-panel-bg)] px-3 py-2.5 text-[12px] leading-relaxed text-[var(--demo-fg)] shadow-none">
487
+ {activeNode.description}
488
+ </div>
489
+ ) : null}
490
+ </div>
491
+ );
492
+ }
493
+
494
+ if (entry.kind === "prompt" && isLocalEntry) {
495
+ return (
496
+ <div
497
+ key={entry.id}
498
+ className="space-y-1.5 rounded-lg border border-[var(--demo-border-strong)] bg-[var(--demo-shell-strong-bg)] px-3 py-2.5"
499
+ >
500
+ <div className="flex items-center justify-between gap-3 text-[9px] uppercase tracking-[0.15em] text-[var(--demo-subtle-fg)]">
501
+ <span>Prompt</span>
502
+ <span className="text-[8px] tracking-[0.18em] text-[var(--demo-subtle-fg)]">
503
+ Sent
504
+ </span>
505
+ </div>
506
+ <div className="text-[12px] leading-relaxed text-[var(--demo-fg)]">
507
+ {entry.text}
508
+ </div>
509
+ </div>
510
+ );
511
+ }
512
+
513
+ if (entry.kind === "prompt") {
514
+ return (
515
+ <div
516
+ key={entry.id}
517
+ className="space-y-1.5 rounded-lg border border-[var(--demo-border)] bg-[var(--demo-panel-bg)] px-3 py-2.5"
518
+ >
519
+ <div className="text-[9px] uppercase tracking-[0.15em] text-[var(--demo-subtle-fg)]">
520
+ {getEntryLabel(entry.kind)}
521
+ </div>
522
+ <div className="text-[12px] leading-relaxed text-[var(--demo-fg)]">
523
+ {entry.text}
524
+ </div>
525
+ </div>
526
+ );
527
+ }
528
+
529
+ if (entry.kind === "message") {
530
+ return (
531
+ <div key={entry.id} className="max-w-[92%] space-y-1">
532
+ <div className="text-[9px] uppercase tracking-[0.15em] text-[var(--demo-subtle-fg)]">
533
+ {getEntryLabel(entry.kind)}
534
+ </div>
535
+ <div className="text-[12px] leading-relaxed text-[var(--demo-fg)]">
536
+ {isLocalEntry ? (
537
+ <span className="inline-flex items-center gap-2">
538
+ <span className="h-1.5 w-1.5 rounded-full bg-[var(--demo-accent)]" />
539
+
540
+ <span>{entry.text}</span>
541
+ </span>
542
+ ) : (
543
+ entry.text
544
+ )}
545
+ </div>
546
+ </div>
547
+ );
548
+ }
549
+
550
+ if (entry.kind === "file") {
551
+ const isNewestVisibleFile =
552
+ entry.id ===
553
+ [...visibleTranscript]
554
+ .reverse()
555
+ .find((transcriptEntry) => transcriptEntry.kind === "file")?.id;
556
+
557
+ return (
558
+ <div
559
+ key={entry.id}
560
+ className="space-y-1.5 rounded-md border border-[var(--demo-border)] bg-[var(--demo-panel-bg)] px-3 py-2"
561
+ >
562
+ <div className="text-[9px] uppercase tracking-[0.15em] text-[var(--demo-subtle-fg)]">
563
+ {getEntryLabel(entry.kind)}
564
+ </div>
565
+ <div className="flex items-center justify-between gap-3 text-[11px] text-[var(--demo-fg)]">
566
+ <span className="truncate font-mono text-[11px] text-[var(--demo-fg)]">
567
+ {entry.path ?? entry.text}
568
+ </span>
569
+ <div className="flex items-center gap-2 font-mono text-[11px] tabular-nums">
570
+ <PatchCount
571
+ prefix="+"
572
+ value={entry.added}
573
+ visible={isNewestVisibleFile}
574
+ className="text-[var(--demo-info)]"
575
+ />
576
+ <PatchCount
577
+ prefix="-"
578
+ value={entry.removed}
579
+ visible={isNewestVisibleFile}
580
+ className="text-[var(--demo-danger)]"
581
+ />
582
+ </div>
583
+ </div>
584
+ </div>
585
+ );
586
+ }
587
+
588
+ if (entry.kind === "thought") {
589
+ return (
590
+ <div key={entry.id} className="space-y-1">
591
+ <div className="text-[9px] uppercase tracking-[0.15em] text-[var(--demo-subtle-fg)]">
592
+ {getEntryLabel(entry.kind)}
593
+ </div>
594
+ <div className="text-[11px] leading-relaxed text-[var(--demo-muted-fg)]">
595
+ {entry.text}
596
+ </div>
597
+ </div>
598
+ );
599
+ }
600
+
601
+ const isLastActivity =
602
+ !isLocalEntry &&
603
+ (index === visibleTranscript.length - 1 ||
604
+ (index < visibleTranscript.length - 1 &&
605
+ visibleTranscript[index + 1]?.kind === "file"));
606
+
607
+ return (
608
+ <div key={entry.id} className="space-y-1.5">
609
+ {/*
610
+ <div className="flex items-center gap-2 text-[9px] uppercase tracking-[0.15em] text-slate-400/90 dark:text-slate-500/90">
611
+ <span>{getEntryLabel(entry.kind)}</span>
612
+ <span className="h-1 w-1 rounded-full bg-[var(--demo-border-strong)]" />
613
+ <span className="truncate">
614
+ {activeNode?.type ?? "Agent"}
615
+ </span>
616
+ </div>
617
+ */}
618
+ <div className="text-[11px] leading-relaxed text-[var(--demo-muted-fg)]">
619
+ {entry.text}
620
+ </div>
621
+ {isLastActivity && activeNode?.metadata ? (
622
+ <div className="pt-1 text-[11px] leading-relaxed text-[var(--demo-muted-fg)]">
623
+ {activeNode.metadata}
624
+ </div>
625
+ ) : null}
626
+ {isRunning && index === visibleTranscript.length - 1 ? (
627
+ <div className="flex items-center gap-2 pt-1 text-[11px] text-[var(--demo-subtle-fg)]">
628
+ <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-[var(--demo-accent)]" />
629
+ Running
630
+ </div>
631
+ ) : null}
632
+ </div>
633
+ );
634
+ };
635
+
636
+ return (
637
+ <div className="flex h-full min-h-0 flex-col overflow-hidden bg-[var(--demo-panel-muted-bg)] text-[var(--demo-fg)] [font-synthesis:none] antialiased">
638
+ <div className="relative min-h-0 flex-1">
639
+ <div
640
+ ref={scrollViewportRef}
641
+ className="min-h-0 h-full overflow-y-auto px-4 py-4 pr-5 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
642
+ >
643
+ <div className="flex min-h-full flex-col pr-1.5">
644
+ <div className="space-y-3">
645
+ {visibleTranscript.map((entry, index) => (
646
+ <React.Fragment key={entry.id}>
647
+ {renderSessionEntry(entry, index)}
648
+ {injectedItemsByIndex.has(index + 1)
649
+ ? injectedItemsByIndex
650
+ .get(index + 1)
651
+ ?.map((item) =>
652
+ renderSessionEntry(item, index + 0.5, true),
653
+ )
654
+ : null}
655
+ </React.Fragment>
656
+ ))}
657
+ </div>
658
+
659
+ <div className="flex-1" />
660
+ </div>
661
+ </div>
662
+
663
+ {hasOverflow ? (
664
+ <div
665
+ ref={scrollbarTrackRef}
666
+ aria-hidden="true"
667
+ onPointerDown={handleScrollbarPointerDown}
668
+ onPointerMove={handleScrollbarPointerMove}
669
+ onPointerUp={handleScrollbarPointerUp}
670
+ onPointerCancel={handleScrollbarPointerUp}
671
+ className="absolute inset-y-3 right-1.5 w-[10px] cursor-pointer overflow-hidden rounded-full bg-[var(--demo-scroll-track)]"
672
+ >
673
+ <div
674
+ data-scrollbar-thumb="true"
675
+ className="absolute inset-x-1 rounded-full bg-[var(--demo-scroll-thumb)] shadow-[inset_0_1px_0_rgba(255,255,255,0.35)] dark:shadow-[inset_0_1px_0_rgba(255,255,255,0.08)] will-change-transform"
676
+ style={{
677
+ height: `${thumbHeight}px`,
678
+ transform: `translateY(${thumbOffset}px)`,
679
+ }}
680
+ />
681
+ </div>
682
+ ) : null}
683
+ </div>
684
+
685
+ {composer ? (
686
+ <div className="border-t border-[var(--demo-border)] bg-[var(--demo-panel-subtle-bg)] px-4 py-3">
687
+ <form
688
+ className="rounded-lg border border-[var(--demo-border)] bg-[var(--demo-panel-bg)] px-3 py-3 shadow-none"
689
+ onSubmit={handleComposerSubmit}
690
+ >
691
+ <label className="block text-[11px] text-[var(--demo-subtle-fg)]">
692
+ <span className="sr-only">
693
+ {composer.placeholder ??
694
+ "Ask the agent to inspect, search, or build..."}
695
+ </span>
696
+ <input
697
+ type="text"
698
+ value={composerValue}
699
+ onChange={(event) => setComposerValue(event.target.value)}
700
+ placeholder={
701
+ composer.placeholder ??
702
+ "Ask the agent to inspect, search, or build..."
703
+ }
704
+ className="w-full bg-transparent text-[11px] leading-5 text-[var(--demo-fg)] outline-none placeholder:text-[var(--demo-subtle-fg)]"
705
+ />
706
+ </label>
707
+
708
+ <div className="mt-3 flex items-center gap-2 text-[10px] text-[var(--demo-muted-fg)]">
709
+ <span className="rounded-full border border-[var(--demo-border)] bg-[var(--demo-shell-strong-bg)] px-2.5 py-1 text-[var(--demo-muted-fg)]">
710
+ {composer.modeLabel ?? "Agent"}
711
+ </span>
712
+ <span className="rounded-full border border-[var(--demo-border)] bg-[var(--demo-shell-strong-bg)] px-2.5 py-1 text-[var(--demo-muted-fg)]">
713
+ {composer.modelLabel ?? "Model 2"}
714
+ </span>
715
+ <div className="ml-auto flex items-center gap-2">
716
+ <button
717
+ type="submit"
718
+ disabled={!composerValue.trim()}
719
+ aria-label="Submit prompt"
720
+ className="flex h-6 w-6 items-center justify-center rounded-full border border-[var(--demo-border)] bg-[var(--demo-shell-strong-bg)] text-[var(--demo-muted-fg)] transition hover:border-[var(--demo-border-strong)] hover:bg-[var(--demo-panel-bg)] hover:text-[var(--demo-fg)] disabled:cursor-not-allowed disabled:opacity-40"
721
+ >
722
+ <svg
723
+ aria-hidden="true"
724
+ viewBox="0 0 16 16"
725
+ className="h-3.5 w-3.5"
726
+ fill="none"
727
+ >
728
+ <path
729
+ d="M8 3.25v9.5M8 3.25 4.75 6.5M8 3.25 11.25 6.5"
730
+ stroke="currentColor"
731
+ strokeWidth="1.4"
732
+ strokeLinecap="round"
733
+ strokeLinejoin="round"
734
+ />
735
+ </svg>
736
+ </button>
737
+ </div>
738
+ </div>
739
+ </form>
740
+ </div>
741
+ ) : null}
742
+ </div>
743
+ );
744
+ }