@tangle-network/agent-app 0.42.4 → 0.42.6

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 (48) hide show
  1. package/dist/{DesignCanvas-AOIVN3SJ.js → DesignCanvas-GUTCY763.js} +2 -2
  2. package/dist/{DesignCanvasEditor-QQNAZRJQ.js → DesignCanvasEditor-ZO4V7H6A.js} +4 -4
  3. package/dist/{TimelineEditor-Z7C6BFP5.js → TimelineEditor-SNOAGBV6.js} +2 -2
  4. package/dist/brand/index.d.ts +35 -0
  5. package/dist/brand/index.js +89 -0
  6. package/dist/brand/index.js.map +1 -0
  7. package/dist/{chunk-YYYXGLLQ.js → chunk-6JYDS34Y.js} +184 -54
  8. package/dist/chunk-6JYDS34Y.js.map +1 -0
  9. package/dist/{chunk-FJUOBNIY.js → chunk-A5Z53E3A.js} +3 -3
  10. package/dist/{chunk-V5XZKHCL.js → chunk-AFQXKJAE.js} +132 -2
  11. package/dist/chunk-AFQXKJAE.js.map +1 -0
  12. package/dist/{chunk-S6WS2B32.js → chunk-EX3LD5G6.js} +322 -122
  13. package/dist/chunk-EX3LD5G6.js.map +1 -0
  14. package/dist/{chunk-DTS5TZRN.js → chunk-MFI4ZM7W.js} +2 -2
  15. package/dist/chunk-MFI4ZM7W.js.map +1 -0
  16. package/dist/{chunk-YEWTVDDT.js → chunk-OVUQNKLM.js} +379 -323
  17. package/dist/chunk-OVUQNKLM.js.map +1 -0
  18. package/dist/chunk-ZN5J47UX.js +1 -0
  19. package/dist/chunk-ZN5J47UX.js.map +1 -0
  20. package/dist/{command-stack-CkjelfK_.d.ts → command-stack-CcWi_2bN.d.ts} +15 -0
  21. package/dist/design-canvas-react/engine.d.ts +2 -2
  22. package/dist/design-canvas-react/engine.js +5 -6
  23. package/dist/design-canvas-react/index.d.ts +61 -8
  24. package/dist/design-canvas-react/index.js +10 -9
  25. package/dist/design-canvas-react/lazy.d.ts +2 -2
  26. package/dist/design-canvas-react/lazy.js +1 -1
  27. package/dist/{lazy-BHHqS8-j.d.ts → lazy-2XBVp1_e.d.ts} +6 -2
  28. package/dist/sequences-react/index.d.ts +94 -2
  29. package/dist/sequences-react/index.js +12 -2
  30. package/dist/sequences-react/index.js.map +1 -1
  31. package/dist/studio/index.d.ts +1 -1
  32. package/dist/studio/index.js +1 -1
  33. package/dist/studio-react/index.js +1 -1
  34. package/dist/web-react/index.d.ts +73 -6
  35. package/dist/web-react/index.js +453 -210
  36. package/dist/web-react/index.js.map +1 -1
  37. package/package.json +14 -9
  38. package/dist/chunk-DTS5TZRN.js.map +0 -1
  39. package/dist/chunk-S6WS2B32.js.map +0 -1
  40. package/dist/chunk-SQV7NUEM.js +0 -136
  41. package/dist/chunk-SQV7NUEM.js.map +0 -1
  42. package/dist/chunk-V5XZKHCL.js.map +0 -1
  43. package/dist/chunk-YEWTVDDT.js.map +0 -1
  44. package/dist/chunk-YYYXGLLQ.js.map +0 -1
  45. /package/dist/{DesignCanvas-AOIVN3SJ.js.map → DesignCanvas-GUTCY763.js.map} +0 -0
  46. /package/dist/{DesignCanvasEditor-QQNAZRJQ.js.map → DesignCanvasEditor-ZO4V7H6A.js.map} +0 -0
  47. /package/dist/{TimelineEditor-Z7C6BFP5.js.map → TimelineEditor-SNOAGBV6.js.map} +0 -0
  48. /package/dist/{chunk-FJUOBNIY.js.map → chunk-A5Z53E3A.js.map} +0 -0
@@ -1,11 +1,11 @@
1
- import { D as DesignCanvasProps, c as createSceneCommandStack, E as ExportTriggerOptions, a as DesignCanvasMode } from '../command-stack-CkjelfK_.js';
2
- export { A as ApplySceneResult, b as EditorSceneState, S as SCENE_COMMAND_HISTORY_LIMIT, d as SceneCommand, e as SceneCommandStack, f as SceneCommandStackWithReapply, g as SnapEngine, h as SnapResult, i as SnapTarget, j as SnapTargetKind, k as SnapTargets, Z as ZoomPanMath } from '../command-stack-CkjelfK_.js';
1
+ import { D as DesignCanvasProps, c as createSceneCommandStack, E as ExportTriggerOptions, a as DesignCanvasMode } from '../command-stack-CcWi_2bN.js';
2
+ export { A as ApplySceneResult, b as EditorSceneState, S as SCENE_COMMAND_HISTORY_LIMIT, d as SceneCommand, e as SceneCommandStack, f as SceneCommandStackWithReapply, g as SnapEngine, h as SnapResult, i as SnapTarget, j as SnapTargetKind, k as SnapTargets, Z as ZoomPanMath } from '../command-stack-CcWi_2bN.js';
3
3
  import { InsertPageGeometry, InsertTemplate, MultiSetAttrsEntry } from './engine.js';
4
4
  export { AddElementInput, AddPageInput, BindSlotInput, DEFAULT_INSERT_TEMPLATES, DUPLICATE_OFFSET, DeleteElementInput, DeletePageInput, DuplicatePageInput, ExportViewSnapshot, GroupElementsInput, MAX_INSERT_DIMENSION, MarqueeSelectOptions, NudgeDelta, ReorderElementInput, ReorderPageInput, ResolvedExportParams, SetAttrsInput, SetDocumentTitleInput, SetPageGuidesInput, SetPagePropsInput, UngroupElementInput, ZoomPanConfig, addElementCommand, addPageCommand, bindSlotCommand, buildInsertImageOp, centeredPosition, collectGridTargets, createSnapEngine, createZoomPanMath, deleteElementCommand, deletePageCommand, documentCropToStageCoords, duplicatePageCommand, fittedSize, groupElementsCommand, hitTestPoint, identifyTaintedSrc, isCrossOriginSrc, isExportHiddenNodeName, marqueeSelect, mintElementId, multiSetAttrsCommand, nudgeDelta, reorderElementCommand, reorderPageCommand, resolveExportParams, setAttrsCommand, setDocumentTitleCommand, setPageGuidesCommand, setPagePropsCommand, ungroupElementCommand } from './engine.js';
5
5
  import { c as SceneDocument, g as ScenePage, d as SceneElement, P as PageBleed, a as PageGuides } from '../model-BBwyOuWG.js';
6
6
  import { f as ExportPreset } from '../export-presets-DAsU5Wal.js';
7
7
  export { d as ExportCropRect, h as bleedAwareExportBounds, s as scaleForPreset } from '../export-presets-DAsU5Wal.js';
8
- export { D as DesignCanvas, a as DesignCanvasChromeLazy, b as DesignCanvasEditor, c as DesignCanvasFullProps, d as DesignCanvasLazy } from '../lazy-BHHqS8-j.js';
8
+ export { D as DesignCanvas, a as DesignCanvasChromeLazy, b as DesignCanvasEditor, c as DesignCanvasFullProps, d as DesignCanvasLazy } from '../lazy-2XBVp1_e.js';
9
9
  import * as react from 'react';
10
10
  import { ButtonHTMLAttributes } from 'react';
11
11
  import { g as SceneOperation, f as SceneAttrsPatch } from '../operations-DLIvvD00.js';
@@ -142,8 +142,15 @@ interface WorkspaceViewProps {
142
142
  onReady?(): void;
143
143
  /** Konva render palette. Omitted → light defaults (byte-identical history). */
144
144
  render?: CanvasRenderPalette;
145
+ /** Show the branded in-canvas empty state (three doors) while the active page
146
+ * has no elements and the user can write. Default true. Set false to keep a
147
+ * bare blank stage (e.g. when restoring a saved-but-intentionally-empty page). */
148
+ showEmptyState?: boolean;
149
+ /** Focus/open the agent from the empty state's "Ask the agent" door. When
150
+ * omitted that door is hidden — there is no agent surface to send them to. */
151
+ onAskAgent?(): void;
145
152
  }
146
- declare function WorkspaceView({ canWrite, onApplyOperations, onSelectionChange, className, stack, activePage, onFitRef, onExport, onExportRef, fitOnMount, onReady, render, }: WorkspaceViewProps): react.JSX.Element;
153
+ declare function WorkspaceView({ canWrite, onApplyOperations, onSelectionChange, className, stack, activePage, onFitRef, onExport, onExportRef, fitOnMount, onReady, render, showEmptyState, onAskAgent, }: WorkspaceViewProps): react.JSX.Element;
147
154
  /**
148
155
  * Self-contained Konva workspace that creates its own command stack. Mount
149
156
  * this when you want the canvas without the toolbar/rulers/pages-strip chrome.
@@ -153,6 +160,39 @@ declare function WorkspaceView({ canWrite, onApplyOperations, onSelectionChange,
153
160
  */
154
161
  declare function Workspace(props: DesignCanvasProps): react.JSX.Element | null;
155
162
 
163
+ /**
164
+ * In-canvas empty state — the first move on a blank design. Shown by
165
+ * WorkspaceView as a centered overlay while the active page has no user
166
+ * content, so a fresh canvas reads as "ready to start" rather than "broken or
167
+ * empty". Three doors cover the two ways people begin and the agent path:
168
+ * - Start with a template — drop a starter element via the same command stack.
169
+ * - Add an element — insert a text block by hand.
170
+ * - Ask the agent — surfaced only when the host wires `onAskAgent` (an agent
171
+ * panel exists); otherwise the door is omitted rather than rendered dead.
172
+ *
173
+ * Token-styled only (CSS-var tokens + Tailwind semantic names) and stamped with
174
+ * the Tangle mark so the blank state is on-brand. Pointer-events stay scoped to
175
+ * the card: clicking the surrounding canvas still starts a marquee.
176
+ */
177
+ interface CanvasEmptyStateProps {
178
+ /** Drop a starter template element through the command stack. */
179
+ onStartTemplate(): void;
180
+ /** Insert a single editable element by hand. */
181
+ onAddElement(): void;
182
+ /** Focus the agent panel / open the agent. Omitted → the door is hidden. */
183
+ onAskAgent?(): void;
184
+ /** Heading copy. Overridable; defaults to the cold-open prompt. */
185
+ title?: string;
186
+ /** Supporting line under the heading. Overridable. */
187
+ subtitle?: string;
188
+ className?: string;
189
+ }
190
+ /**
191
+ * The three-door empty state. Each door is a real action wired to the editor's
192
+ * command stack (or the agent), never a placeholder.
193
+ */
194
+ declare function CanvasEmptyState({ onStartTemplate, onAddElement, onAskAgent, title, subtitle, className, }: CanvasEmptyStateProps): react.JSX.Element;
195
+
156
196
  interface SelectionLayerProps {
157
197
  /** Konva stage reference to look up selected nodes by name. */
158
198
  stageRef: React.RefObject<Konva.Stage | null>;
@@ -189,8 +229,12 @@ interface PagesStripProps {
189
229
  /** Show page-management affordances (add / duplicate / delete). Default true.
190
230
  * The review surface passes false: pages are navigated, not authored. */
191
231
  canManagePages?: boolean;
232
+ /** Heading shown above the strip so it reads as page management. Overridable;
233
+ * defaults to "Pages". Set to '' to hide the visible label (the container
234
+ * keeps its accessible name regardless). */
235
+ label?: string;
192
236
  }
193
- declare function PagesStrip({ pages, activePageId, canWrite, renderThumbnail, onSelectPage, onAddPage, onDuplicatePage, onDeletePage, onReorderPage, canManagePages, }: PagesStripProps): react.JSX.Element;
237
+ declare function PagesStrip({ pages, activePageId, canWrite, renderThumbnail, onSelectPage, onAddPage, onDuplicatePage, onDeletePage, onReorderPage, canManagePages, label, }: PagesStripProps): react.JSX.Element;
194
238
 
195
239
  interface LayersPanelProps {
196
240
  page: ScenePage;
@@ -244,8 +288,14 @@ interface ToolbarProps {
244
288
  onUngroup(groupId: string): void;
245
289
  onDelete(elementIds: string[]): void;
246
290
  onBindSlot(elementId: string, slot: string | null): void;
291
+ /** Field label for the page-size control. Defaults to the clearer "Page size";
292
+ * overridable for consumers that want the older "Preset" wording. */
293
+ pageSizeLabel?: string;
294
+ /** Title for the "turn on print bleed" action. Defaults to "Show print bleed"
295
+ * (the outcome) rather than the print-shop term "bleed". */
296
+ enableBleedLabel?: string;
247
297
  }
248
- declare function Toolbar({ page, selectedElements, canWrite, mode, canUndo, canRedo, gridEnabled, snapEnabled, showRulers, showBleed, onUndo, onRedo, onToggleGrid, onToggleSnap, onToggleRulers, onToggleBleed, onSetAttrs, onSetPageProps, onSetPageGuides, onReorder, onGroup, onUngroup, onDelete, onBindSlot, }: ToolbarProps): react.JSX.Element;
298
+ declare function Toolbar({ page, selectedElements, canWrite, mode, canUndo, canRedo, gridEnabled, snapEnabled, showRulers, showBleed, onUndo, onRedo, onToggleGrid, onToggleSnap, onToggleRulers, onToggleBleed, onSetAttrs, onSetPageProps, onSetPageGuides, onReorder, onGroup, onUngroup, onDelete, onBindSlot, pageSizeLabel, enableBleedLabel, }: ToolbarProps): react.JSX.Element;
249
299
 
250
300
  /**
251
301
  * Canvas zoom controls: fit-to-page, 100%, zoom-out/in buttons, and a percent
@@ -256,8 +306,11 @@ interface ZoomControlsProps {
256
306
  zoom: number;
257
307
  onZoom(zoom: number): void;
258
308
  onFit(): void;
309
+ /** Label/aria for the fit action. Defaults to "Fit to screen" — shorter and
310
+ * matching every other tool's wording for the same action. */
311
+ fitLabel?: string;
259
312
  }
260
- declare function ZoomControls({ zoom, onZoom, onFit }: ZoomControlsProps): react.JSX.Element;
313
+ declare function ZoomControls({ zoom, onZoom, onFit, fitLabel }: ZoomControlsProps): react.JSX.Element;
261
314
 
262
315
  /** 28px square (h-7) — used by the main toolbar. */
263
316
  declare const BTN = "flex h-7 w-7 items-center justify-center rounded border border-[var(--border-default)] text-[var(--text-secondary)] transition hover:text-[var(--text-primary)] focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--ring))] focus-visible:ring-offset-1 focus-visible:ring-offset-[var(--bg-input)] disabled:cursor-default disabled:opacity-40";
@@ -514,4 +567,4 @@ declare function buildRulerTicks(input: {
514
567
  * decimals are rounded to 1 place. Values ≥ 1000 are compacted to "1k" etc. */
515
568
  declare function formatRulerLabel(value: number): string;
516
569
 
517
- export { BTN, BTN_ACTIVE, BTN_SM, BTN_SM_ACTIVE, type BakedNodeAttrs, CanvasInsertPanel, type CanvasInsertPanelProps, DesignCanvasMode, DesignCanvasProps, ElementNode, type ElementNodeProps, ExportControl, type ExportControlProps, type ExportPageDataUrlOptions, ExportPreset, ExportTriggerOptions, GridLayer, type GridLayerProps, IconButton, type IconButtonProps, type InsertGeneration, InsertPageGeometry, InsertTemplate, LAYERS_PANEL_ROW_LIMIT, type LayerRow, LayersPanel, type LayersPanelProps, MultiSetAttrsEntry, PagesStrip, type PagesStripProps, type RulerTick, Rulers, type RulersProps, SelectionLayer, type SelectionLayerProps, type TickStep, Toolbar, type ToolbarProps, type TransformerNode, Workspace, WorkspaceView, type WorkspaceViewProps, ZoomControls, type ZoomControlsProps, bakeLineTransform, bakeRectTransform, bakeTextTransform, buildRulerTicks, createSceneCommandStack, downloadDataUrl, exportDocumentJson, exportPageDataUrl, flattenLayerTree, formatRulerLabel, selectTickStep };
570
+ export { BTN, BTN_ACTIVE, BTN_SM, BTN_SM_ACTIVE, type BakedNodeAttrs, CanvasEmptyState, type CanvasEmptyStateProps, CanvasInsertPanel, type CanvasInsertPanelProps, DesignCanvasMode, DesignCanvasProps, ElementNode, type ElementNodeProps, ExportControl, type ExportControlProps, type ExportPageDataUrlOptions, ExportPreset, ExportTriggerOptions, GridLayer, type GridLayerProps, IconButton, type IconButtonProps, type InsertGeneration, InsertPageGeometry, InsertTemplate, LAYERS_PANEL_ROW_LIMIT, type LayerRow, LayersPanel, type LayersPanelProps, MultiSetAttrsEntry, PagesStrip, type PagesStripProps, type RulerTick, Rulers, type RulersProps, SelectionLayer, type SelectionLayerProps, type TickStep, Toolbar, type ToolbarProps, type TransformerNode, Workspace, WorkspaceView, type WorkspaceViewProps, ZoomControls, type ZoomControlsProps, bakeLineTransform, bakeRectTransform, bakeTextTransform, buildRulerTicks, createSceneCommandStack, downloadDataUrl, exportDocumentJson, exportPageDataUrl, flattenLayerTree, formatRulerLabel, selectTickStep };
@@ -1,4 +1,5 @@
1
1
  import {
2
+ CanvasEmptyState,
2
3
  DesignCanvasEditor,
3
4
  ElementNode,
4
5
  GridLayer,
@@ -11,7 +12,7 @@ import {
11
12
  downloadDataUrl,
12
13
  exportDocumentJson,
13
14
  exportPageDataUrl
14
- } from "../chunk-YYYXGLLQ.js";
15
+ } from "../chunk-6JYDS34Y.js";
15
16
  import {
16
17
  BTN,
17
18
  BTN_ACTIVE,
@@ -40,29 +41,28 @@ import {
40
41
  buildRulerTicks,
41
42
  formatRulerLabel,
42
43
  selectTickStep
43
- } from "../chunk-YEWTVDDT.js";
44
+ } from "../chunk-OVUQNKLM.js";
45
+ import "../chunk-ZN5J47UX.js";
44
46
  import {
45
47
  DEFAULT_INSERT_TEMPLATES,
48
+ DUPLICATE_OFFSET,
46
49
  MAX_INSERT_DIMENSION,
47
50
  buildInsertImageOp,
48
51
  centeredPosition,
49
- fittedSize,
50
- mintElementId
51
- } from "../chunk-SQV7NUEM.js";
52
- import {
53
- DUPLICATE_OFFSET,
54
52
  collectGridTargets,
55
53
  createSnapEngine,
56
54
  createZoomPanMath,
57
55
  documentCropToStageCoords,
56
+ fittedSize,
58
57
  hitTestPoint,
59
58
  identifyTaintedSrc,
60
59
  isCrossOriginSrc,
61
60
  isExportHiddenNodeName,
62
61
  marqueeSelect,
62
+ mintElementId,
63
63
  nudgeDelta,
64
64
  resolveExportParams
65
- } from "../chunk-V5XZKHCL.js";
65
+ } from "../chunk-AFQXKJAE.js";
66
66
  import {
67
67
  SCENE_COMMAND_HISTORY_LIMIT,
68
68
  addElementCommand,
@@ -85,7 +85,7 @@ import {
85
85
  import {
86
86
  DesignCanvasChromeLazy,
87
87
  DesignCanvasLazy
88
- } from "../chunk-FJUOBNIY.js";
88
+ } from "../chunk-A5Z53E3A.js";
89
89
  import "../chunk-LVD37K5V.js";
90
90
  import {
91
91
  assertSceneMediaSrc,
@@ -529,6 +529,7 @@ export {
529
529
  BTN_ACTIVE,
530
530
  BTN_SM,
531
531
  BTN_SM_ACTIVE,
532
+ CanvasEmptyState,
532
533
  CanvasInsertPanel,
533
534
  DEFAULT_INSERT_TEMPLATES,
534
535
  DUPLICATE_OFFSET,
@@ -1,6 +1,6 @@
1
1
  import 'react';
2
- export { a as DesignCanvasChromeLazy, c as DesignCanvasFullProps, d as DesignCanvasLazy } from '../lazy-BHHqS8-j.js';
3
- export { D as DesignCanvasProps } from '../command-stack-CkjelfK_.js';
2
+ export { a as DesignCanvasChromeLazy, c as DesignCanvasFullProps, d as DesignCanvasLazy } from '../lazy-2XBVp1_e.js';
3
+ export { D as DesignCanvasProps } from '../command-stack-CcWi_2bN.js';
4
4
  import '../model-BBwyOuWG.js';
5
5
  import '../operations-DLIvvD00.js';
6
6
  import '../theme/index.js';
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DesignCanvasChromeLazy,
3
3
  DesignCanvasLazy
4
- } from "../chunk-FJUOBNIY.js";
4
+ } from "../chunk-A5Z53E3A.js";
5
5
  export {
6
6
  DesignCanvasChromeLazy,
7
7
  DesignCanvasLazy
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { c as SceneDocument } from './model-BBwyOuWG.js';
3
- import { D as DesignCanvasProps, c as createSceneCommandStack, E as ExportTriggerOptions } from './command-stack-CkjelfK_.js';
3
+ import { D as DesignCanvasProps, c as createSceneCommandStack, E as ExportTriggerOptions } from './command-stack-CcWi_2bN.js';
4
4
 
5
5
  /** Callers inject a workspace renderer so this chrome stays Konva-free. The
6
6
  * workspace occupies the scrollable area between the rulers and the bottom bar. */
@@ -45,6 +45,10 @@ interface DesignCanvasFullProps extends DesignCanvasProps {
45
45
  /** Forwarded from DesignCanvasProps. The Konva render palette; the
46
46
  * Konva-free chrome passes it straight through to the workspace. */
47
47
  render?: DesignCanvasProps['render'];
48
+ /** Forwarded from DesignCanvasProps. Toggles the branded in-canvas empty
49
+ * state and its agent door. */
50
+ showEmptyState?: DesignCanvasProps['showEmptyState'];
51
+ onAskAgent?: DesignCanvasProps['onAskAgent'];
48
52
  onZoomChange(zoom: number): void;
49
53
  onPanChange(panX: number, panY: number): void;
50
54
  onSelectElements(ids: string[], additive: boolean): void;
@@ -55,7 +59,7 @@ interface DesignCanvasFullProps extends DesignCanvasProps {
55
59
  */
56
60
  renderThumbnail(page: SceneDocument['pages'][number]): Promise<string | null>;
57
61
  }
58
- declare function DesignCanvas({ document: initialDocument, rev: initialRev, canWrite, mode, onApplyOperations, onSelectionChange, renderAgentPanel, renderSidePanel, onExport, exportDefaults, className, fitOnMount, onReady, render, renderWorkspace, renderThumbnail, }: DesignCanvasFullProps): react.JSX.Element;
62
+ declare function DesignCanvas({ document: initialDocument, rev: initialRev, canWrite, mode, onApplyOperations, onSelectionChange, renderAgentPanel, renderSidePanel, onExport, exportDefaults, className, fitOnMount, onReady, render, showEmptyState, onAskAgent, pageSizeLabel, enableBleedLabel, fitLabel, renderWorkspace, renderThumbnail, }: DesignCanvasFullProps): react.JSX.Element;
59
63
 
60
64
  /**
61
65
  * Mount this component to get the full editor: toolbar, rulers, layers panel,
@@ -114,6 +114,34 @@ interface TranscriptionProvider {
114
114
  onProgress?: (fraction: number) => void;
115
115
  }): Promise<TranscriptionSegment[]>;
116
116
  }
117
+ /** Overridable copy for the editor's product-facing labels. Every field is
118
+ * optional; the editor ships clear defaults (see `DEFAULT_TIMELINE_LABELS`).
119
+ * Accessible names (the `*AriaLabel` fields) stay descriptive for screen
120
+ * readers; the short visible labels carry the on-screen verb. Products rename
121
+ * per their own vocabulary without forking the component. */
122
+ interface TimelineEditorLabels {
123
+ /** Visible verb on the split-at-playhead tool. */
124
+ splitClip?: string;
125
+ splitClipAriaLabel?: string;
126
+ /** Visible verb on the add-caption-at-playhead tool. */
127
+ addCaption?: string;
128
+ addCaptionAriaLabel?: string;
129
+ /** Visible verb on the export action (rendered only when `onCreateExport`). */
130
+ createExport?: string;
131
+ /** Empty-state heading + the three doors. */
132
+ emptyTitle?: string;
133
+ emptyBody?: string;
134
+ emptyTemplateDoor?: string;
135
+ emptyClipDoor?: string;
136
+ emptyAgentDoor?: string;
137
+ /** Phone gate shown instead of the gesture-heavy editor below `sm`. */
138
+ smallScreenTitle?: string;
139
+ smallScreenBody?: string;
140
+ /** Ghost-lane names shown at rest so time reads as a timeline before any
141
+ * track exists. */
142
+ ghostVideoLane?: string;
143
+ ghostCaptionLane?: string;
144
+ }
117
145
  interface TimelineEditorProps {
118
146
  timeline: SequenceTimeline;
119
147
  canWrite: boolean;
@@ -136,8 +164,24 @@ interface TimelineEditorProps {
136
164
  renderAssetShelf?(): React.ReactNode;
137
165
  /** Frame provider override; omitted → the baseline HTMLVideoElement provider. */
138
166
  frameProvider?: VideoFrameProvider;
167
+ /** Overridable product-facing copy; defaults are clear on their own. */
168
+ labels?: TimelineEditorLabels;
169
+ /** Empty-state doors. Supplying a handler renders that door; omitting it
170
+ * hides it. With none supplied the empty state shows guidance only. */
171
+ onStartFromTemplate?(): void;
172
+ onAddClip?(): void;
173
+ onAskAgent?(): void;
174
+ /** Render the export action (a toolbar "Export…" button). Omitted → no export
175
+ * affordance. The handler owns the dialog/flow; this surface only invokes it. */
176
+ onCreateExport?(): void;
177
+ /** Off by default. When true AND the consuming product supplies a branded
178
+ * export pipeline, the export door/button is marked as Tangle-branded. This
179
+ * surface has no render pipeline of its own to brand, so the flag is a
180
+ * forward-compatible hint to `onCreateExport`, never a baked watermark. */
181
+ brandedExport?: boolean;
139
182
  className?: string;
140
183
  }
184
+ declare const DEFAULT_TIMELINE_LABELS: Required<TimelineEditorLabels>;
141
185
 
142
186
  /**
143
187
  * Undo/redo command stack over immutable `EditorTimelineState`.
@@ -635,6 +679,52 @@ interface TimelinePlayheadProps {
635
679
  }
636
680
  declare function TimelinePlayhead({ frame, zoom }: TimelinePlayheadProps): react.JSX.Element;
637
681
 
682
+ /**
683
+ * Lazy boundary around the Tangle knot from `../../brand`.
684
+ *
685
+ * `/brand` re-exports the mark from `@tangle-network/sandbox-ui`, an OPT-IN peer
686
+ * (see brand/index.tsx). Static-importing it would pull the peer into every
687
+ * module that renders the timeline — breaking substrate-free environments (and
688
+ * products that haven't installed the peer) at module-eval time. Code-splitting
689
+ * it (the repo's `lazy.tsx` convention) keeps the peer out of the editor's
690
+ * static graph: the knot streams in only when an empty/phone state actually
691
+ * renders, and its absence degrades to empty space rather than a crash.
692
+ */
693
+ interface BrandMarkProps {
694
+ size: number;
695
+ className?: string;
696
+ }
697
+ declare function BrandMark({ size, className }: BrandMarkProps): react.JSX.Element;
698
+
699
+ interface TimelineEmptyStateProps {
700
+ labels?: TimelineEditorLabels;
701
+ brandedExport?: boolean;
702
+ onStartFromTemplate?(): void;
703
+ onAddClip?(): void;
704
+ onAskAgent?(): void;
705
+ }
706
+ declare function TimelineEmptyState(props: TimelineEmptyStateProps): react.JSX.Element;
707
+
708
+ /**
709
+ * Labeled placeholder lanes drawn behind the empty state so the surface reads
710
+ * as a timeline before any track exists: a "Video" lane and a "Captions" lane
711
+ * with the same sticky header column the real `TimelineTrackRow` uses, sized to
712
+ * the timeline width so the ruler's timecodes line up over real lanes. Purely
713
+ * decorative — no clips, no gestures.
714
+ */
715
+ interface TimelineGhostLanesProps {
716
+ /** Timeline pixel width (durationFrames * zoom), so ghost lanes match the ruler. */
717
+ laneWidth: number;
718
+ videoLabel: string;
719
+ captionLabel: string;
720
+ }
721
+ declare function TimelineGhostLanes({ laneWidth, videoLabel, captionLabel }: TimelineGhostLanesProps): react.JSX.Element;
722
+
723
+ interface TimelineSmallScreenGateProps {
724
+ labels?: TimelineEditorLabels;
725
+ }
726
+ declare function TimelineSmallScreenGate({ labels }: TimelineSmallScreenGateProps): react.JSX.Element;
727
+
638
728
  interface SnapIndicatorLineProps {
639
729
  point: SnapPoint | null;
640
730
  zoom: number;
@@ -645,8 +735,10 @@ interface ZoomControlProps {
645
735
  zoomMath: ZoomMath;
646
736
  zoom: number;
647
737
  onZoomChange(zoom: number): void;
738
+ /** Fit-to-sequence pixels-per-frame; clicking the readout snaps back to it. */
739
+ fitZoom?: number;
648
740
  }
649
- declare function ZoomControl({ zoomMath, zoom, onZoomChange }: ZoomControlProps): react.JSX.Element;
741
+ declare function ZoomControl({ zoomMath, zoom, onZoomChange, fitZoom }: ZoomControlProps): react.JSX.Element;
650
742
 
651
743
  /**
652
744
  * Fuses several `TimelineCommand`s into one undo step so a single gesture
@@ -763,4 +855,4 @@ declare function chooseMoveSnap(input: {
763
855
 
764
856
  declare const SequenceTimelineEditorLazy: react__default.LazyExoticComponent<typeof TimelineEditor>;
765
857
 
766
- export { type AddCaptionInput, type ApplySnapOptions, type AudioBufferLike, COMMAND_HISTORY_LIMIT, type ClipIdResolver, type ClipMoveCommit, type ClipTrimCommit, type CommandStack, DEFAULT_MAX_MEDIA_ELEMENTS, DEFAULT_WHISPER_MODEL, type DeleteClipInput, type EditorTimelineState, type FrameRect, type LetterboxRect, type MediaElementPool, type MoveClipInput, type MoveDragInput, type PlaceClipInput, type PlaybackClock, type PlaybackClockConfig, type PooledElementLease, PreviewCanvas, type PreviewCanvasProps, SEEK_TIMEOUT_MS, SEEK_TOLERANCE_SECONDS, SEQUENCE_MEDIA_DRAG_TYPE, SequenceTimelineEditorLazy, type SetClipTextInput, SnapIndicatorLine, type SnapIndicatorLineProps, type SnapPoint, type SnapResult, type SplitClipInput, TimelineClipChip, type TimelineClipChipProps, type TimelineCommand, TimelineEditor, type TimelineEditorProps, TimelinePlayhead, type TimelinePlayheadProps, TimelineRuler, type TimelineRulerProps, type TimelineSnapPoint, TimelineTrackRow, type TimelineTrackRowProps, type ToggleClipDisabledInput, type TranscriptionProvider, type TranscriptionSegment, type TrimClipInput, type TrimEndDragInput, type TrimStartDragInput, type TrimStartDragResult, type VideoFrameProvider, type ViewportTransform, type WaveformData, type WhisperOutput, ZoomControl, type ZoomControlProps, type ZoomMath, type ZoomMathConfig, addCaptionCommand, applySnap, captionFontPx, chooseMoveSnap, classifyMediaUrl, clipChipGeometry, collectSnapPoints, compositeCommand, computeWaveform, containFitRect, createCommandStack, createImageFrameProvider, createMediaElementPool, createPlaybackClock, createVideoElementFrameProvider, createWhisperTranscriptionProvider, createZoomMath, deleteClipCommand, drawWaveform, frameToPixel, framesFromPixelDelta, letterboxRect, loadWaveform, mapWhisperOutput, mixdownToMono, moveClipCommand, moveDragStartFrame, needsSeek, pixelToFrame, placeClipCommand, selectTickStepSeconds, setClipTextCommand, snapPixel, splitClipCommand, toggleClipDisabledCommand, trimClipCommand, trimEndDrag, trimStartDrag };
858
+ export { type AddCaptionInput, type ApplySnapOptions, type AudioBufferLike, BrandMark, type BrandMarkProps, COMMAND_HISTORY_LIMIT, type ClipIdResolver, type ClipMoveCommit, type ClipTrimCommit, type CommandStack, DEFAULT_MAX_MEDIA_ELEMENTS, DEFAULT_TIMELINE_LABELS, DEFAULT_WHISPER_MODEL, type DeleteClipInput, type EditorTimelineState, type FrameRect, type LetterboxRect, type MediaElementPool, type MoveClipInput, type MoveDragInput, type PlaceClipInput, type PlaybackClock, type PlaybackClockConfig, type PooledElementLease, PreviewCanvas, type PreviewCanvasProps, SEEK_TIMEOUT_MS, SEEK_TOLERANCE_SECONDS, SEQUENCE_MEDIA_DRAG_TYPE, SequenceTimelineEditorLazy, type SetClipTextInput, SnapIndicatorLine, type SnapIndicatorLineProps, type SnapPoint, type SnapResult, type SplitClipInput, TimelineClipChip, type TimelineClipChipProps, type TimelineCommand, TimelineEditor, type TimelineEditorLabels, type TimelineEditorProps, TimelineEmptyState, type TimelineEmptyStateProps, TimelineGhostLanes, type TimelineGhostLanesProps, TimelinePlayhead, type TimelinePlayheadProps, TimelineRuler, type TimelineRulerProps, TimelineSmallScreenGate, type TimelineSmallScreenGateProps, type TimelineSnapPoint, TimelineTrackRow, type TimelineTrackRowProps, type ToggleClipDisabledInput, type TranscriptionProvider, type TranscriptionSegment, type TrimClipInput, type TrimEndDragInput, type TrimStartDragInput, type TrimStartDragResult, type VideoFrameProvider, type ViewportTransform, type WaveformData, type WhisperOutput, ZoomControl, type ZoomControlProps, type ZoomMath, type ZoomMathConfig, addCaptionCommand, applySnap, captionFontPx, chooseMoveSnap, classifyMediaUrl, clipChipGeometry, collectSnapPoints, compositeCommand, computeWaveform, containFitRect, createCommandStack, createImageFrameProvider, createMediaElementPool, createPlaybackClock, createVideoElementFrameProvider, createWhisperTranscriptionProvider, createZoomMath, deleteClipCommand, drawWaveform, frameToPixel, framesFromPixelDelta, letterboxRect, loadWaveform, mapWhisperOutput, mixdownToMono, moveClipCommand, moveDragStartFrame, needsSeek, pixelToFrame, placeClipCommand, selectTickStepSeconds, setClipTextCommand, snapPixel, splitClipCommand, toggleClipDisabledCommand, trimClipCommand, trimEndDrag, trimStartDrag };
@@ -1,6 +1,8 @@
1
1
  import {
2
+ BrandMark,
2
3
  COMMAND_HISTORY_LIMIT,
3
4
  DEFAULT_MAX_MEDIA_ELEMENTS,
5
+ DEFAULT_TIMELINE_LABELS,
4
6
  PreviewCanvas,
5
7
  SEEK_TIMEOUT_MS,
6
8
  SEEK_TOLERANCE_SECONDS,
@@ -8,8 +10,11 @@ import {
8
10
  SnapIndicatorLine,
9
11
  TimelineClipChip,
10
12
  TimelineEditor,
13
+ TimelineEmptyState,
14
+ TimelineGhostLanes,
11
15
  TimelinePlayhead,
12
16
  TimelineRuler,
17
+ TimelineSmallScreenGate,
13
18
  TimelineTrackRow,
14
19
  ZoomControl,
15
20
  addCaptionCommand,
@@ -47,7 +52,7 @@ import {
47
52
  trimClipCommand,
48
53
  trimEndDrag,
49
54
  trimStartDrag
50
- } from "../chunk-S6WS2B32.js";
55
+ } from "../chunk-EX3LD5G6.js";
51
56
  import "../chunk-ZYBWGSAZ.js";
52
57
 
53
58
  // src/sequences-react/media/transcription.ts
@@ -182,10 +187,12 @@ function createWhisperTranscriptionProvider(opts) {
182
187
 
183
188
  // src/sequences-react/lazy.tsx
184
189
  import React from "react";
185
- var SequenceTimelineEditorLazy = React.lazy(() => import("../TimelineEditor-Z7C6BFP5.js"));
190
+ var SequenceTimelineEditorLazy = React.lazy(() => import("../TimelineEditor-SNOAGBV6.js"));
186
191
  export {
192
+ BrandMark,
187
193
  COMMAND_HISTORY_LIMIT,
188
194
  DEFAULT_MAX_MEDIA_ELEMENTS,
195
+ DEFAULT_TIMELINE_LABELS,
189
196
  DEFAULT_WHISPER_MODEL,
190
197
  PreviewCanvas,
191
198
  SEEK_TIMEOUT_MS,
@@ -195,8 +202,11 @@ export {
195
202
  SnapIndicatorLine,
196
203
  TimelineClipChip,
197
204
  TimelineEditor,
205
+ TimelineEmptyState,
206
+ TimelineGhostLanes,
198
207
  TimelinePlayhead,
199
208
  TimelineRuler,
209
+ TimelineSmallScreenGate,
200
210
  TimelineTrackRow,
201
211
  ZoomControl,
202
212
  addCaptionCommand,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/sequences-react/media/transcription.ts","../../src/sequences-react/lazy.tsx"],"sourcesContent":["/**\n * Whisper transcription behind the `TranscriptionProvider` seam, powered by\n * the OPTIONAL peer `@huggingface/transformers`. The peer is loaded with a\n * dynamic variable-specifier import inside `transcribe()` so bundlers leave\n * it external and apps that never transcribe never pay for it. `available`\n * is the sync UI affordance signal; `transcribe()` re-verifies with the real\n * import and is the source of truth.\n */\n\nimport type { TranscriptionProvider, TranscriptionSegment } from '../contracts'\nimport type { AudioBufferLike } from './waveform'\n\nexport const DEFAULT_WHISPER_MODEL = 'onnx-community/whisper-large-v3-turbo'\n\n/** Whisper models are trained on 16 kHz mono — decoding through a 16 kHz\n * OfflineAudioContext resamples any source rate in one step. */\nconst WHISPER_SAMPLE_RATE = 16_000\n\nconst TRANSCRIPTION_PEER = '@huggingface/transformers'\nconst PEER_MISSING_MESSAGE = 'transcription requires optional peer @huggingface/transformers'\n\ninterface WhisperChunk {\n text: string\n timestamp: [number, number | null]\n}\n\nexport interface WhisperOutput {\n text: string\n chunks?: WhisperChunk[]\n}\n\ntype WhisperPipeline = (\n audio: Float32Array,\n options: Record<string, unknown>,\n) => Promise<WhisperOutput | WhisperOutput[]>\n\ninterface TransformersProgressEvent {\n status: string\n progress?: number\n}\n\ninterface TransformersModule {\n pipeline(\n task: 'automatic-speech-recognition',\n model: string,\n options?: Record<string, unknown>,\n ): Promise<WhisperPipeline>\n}\n\n/** Mean-mixdown to mono. Single-channel buffers return the live channel data\n * without copying — callers must treat the result as read-only. */\nexport function mixdownToMono(buffer: AudioBufferLike): Float32Array {\n if (buffer.numberOfChannels < 1) throw new Error('audio buffer has no channels — cannot mix down')\n if (buffer.numberOfChannels === 1) return buffer.getChannelData(0)\n const mono = new Float32Array(buffer.length)\n for (let channel = 0; channel < buffer.numberOfChannels; channel++) {\n const data = buffer.getChannelData(channel)\n if (data.length !== buffer.length) {\n throw new Error(`channel ${channel} has ${data.length} samples, expected ${buffer.length}`)\n }\n for (let i = 0; i < data.length; i++) {\n // in range by the length assertion above; casts silence noUncheckedIndexedAccess\n mono[i] = (mono[i] as number) + (data[i] as number)\n }\n }\n const scale = 1 / buffer.numberOfChannels\n for (let i = 0; i < mono.length; i++) {\n mono[i] = (mono[i] as number) * scale\n }\n return mono\n}\n\n/** Map whisper chunk output to contract segments. A `null` end timestamp is\n * whisper's \"ran past the end of the audio\" sentinel on the final chunk and\n * resolves to the audio duration. */\nexport function mapWhisperOutput(\n output: WhisperOutput | WhisperOutput[],\n durationSeconds: number,\n mediaUrl: string,\n): TranscriptionSegment[] {\n const first = Array.isArray(output) ? output[0] : output\n if (first === undefined) throw new Error(`whisper produced no output for ${mediaUrl}`)\n const chunks = first.chunks\n if (chunks === undefined || chunks.length === 0) {\n const text = first.text.trim()\n return text.length === 0 ? [] : [{ text, startSeconds: 0, endSeconds: durationSeconds }]\n }\n const segments: TranscriptionSegment[] = []\n for (const chunk of chunks) {\n const text = chunk.text.trim()\n if (text.length === 0) continue\n const [start, end] = chunk.timestamp\n if (!Number.isFinite(start)) {\n throw new Error(`whisper returned a non-numeric start timestamp for \"${text}\" in ${mediaUrl}`)\n }\n segments.push({ text, startSeconds: start, endSeconds: end === null ? durationSeconds : end })\n }\n return segments\n}\n\nasync function loadTransformers(): Promise<TransformersModule> {\n try {\n // Variable specifier + ignore pragmas keep bundlers from statically\n // resolving an optional peer that may not be installed.\n return (await import(/* @vite-ignore */ /* webpackIgnore: true */ TRANSCRIPTION_PEER)) as TransformersModule\n } catch (error) {\n throw new Error(PEER_MISSING_MESSAGE, { cause: error })\n }\n}\n\nasync function fetchMonoAudio(mediaUrl: string): Promise<{ samples: Float32Array; durationSeconds: number }> {\n const response = await fetch(mediaUrl)\n if (!response.ok) {\n throw new Error(`failed to fetch media for transcription: ${response.status} ${response.statusText} from ${mediaUrl}`)\n }\n const bytes = await response.arrayBuffer()\n if (typeof OfflineAudioContext === 'undefined') {\n throw new Error('transcription requires Web Audio (OfflineAudioContext) — call transcribe() from a browser')\n }\n // decodeAudioData resamples to the context rate per the Web Audio spec, so\n // a 16 kHz context yields whisper-ready samples from any source rate.\n const context = new OfflineAudioContext(1, 1, WHISPER_SAMPLE_RATE)\n const decoded = await context.decodeAudioData(bytes)\n return { samples: mixdownToMono(decoded), durationSeconds: decoded.duration }\n}\n\nexport function createWhisperTranscriptionProvider(opts?: { model?: string }): TranscriptionProvider {\n const model = opts?.model ?? DEFAULT_WHISPER_MODEL\n let availability: boolean | null = null\n let pipelinePromise: Promise<WhisperPipeline> | null = null\n\n const probeAvailability = (): boolean => {\n if (availability !== null) return availability\n const resolve = (import.meta as ImportMeta & { resolve?: (specifier: string) => string }).resolve\n if (typeof resolve !== 'function') {\n // No sync resolver in this runtime — report unavailable rather than\n // guess; transcribe() still attempts the real import either way.\n availability = false\n return availability\n }\n try {\n resolve.call(import.meta, TRANSCRIPTION_PEER)\n availability = true\n } catch {\n availability = false\n }\n return availability\n }\n\n const getPipeline = (\n transformers: TransformersModule,\n onProgress?: (fraction: number) => void,\n ): Promise<WhisperPipeline> => {\n if (pipelinePromise === null) {\n const device = typeof navigator !== 'undefined' && 'gpu' in navigator ? 'webgpu' : 'wasm'\n pipelinePromise = transformers.pipeline('automatic-speech-recognition', model, {\n dtype: 'q4',\n device,\n // Model download progress only — it dwarfs inference time on first\n // use, and only the first transcribe ever sees a download.\n progress_callback: (event: TransformersProgressEvent) => {\n if (event.status === 'progress' && typeof event.progress === 'number' && onProgress !== undefined) {\n onProgress(Math.min(1, Math.max(0, event.progress / 100)))\n }\n },\n })\n // A failed load must not poison the cache — the next transcribe retries.\n pipelinePromise.catch(() => {\n pipelinePromise = null\n })\n }\n return pipelinePromise\n }\n\n return {\n get available() {\n return probeAvailability()\n },\n async transcribe(mediaUrl, transcribeOpts) {\n const transformers = await loadTransformers()\n // A successful import is ground truth and overrides a false probe.\n availability = true\n const audio = await fetchMonoAudio(mediaUrl)\n const transcriber = await getPipeline(transformers, transcribeOpts?.onProgress)\n const options: Record<string, unknown> = {\n return_timestamps: true,\n chunk_length_s: 30,\n stride_length_s: 5,\n }\n if (transcribeOpts?.language !== undefined) options.language = transcribeOpts.language\n const output = await transcriber(audio.samples, options)\n const segments = mapWhisperOutput(output, audio.durationSeconds, mediaUrl)\n transcribeOpts?.onProgress?.(1)\n return segments\n },\n }\n}\n","/**\n * Code-split entry for the timeline editor. The editor pulls in canvas\n * painting, waveform decode, and gesture machinery products only need on the\n * sequence route — `React.lazy` keeps it out of their main bundle. Mount\n * inside a `<Suspense>` boundary.\n */\n\nimport React from 'react'\n\nexport const SequenceTimelineEditorLazy = React.lazy(() => import('./components/TimelineEditor'))\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/sequences-react/media/transcription.ts","../../src/sequences-react/lazy.tsx"],"sourcesContent":["/**\n * Whisper transcription behind the `TranscriptionProvider` seam, powered by\n * the OPTIONAL peer `@huggingface/transformers`. The peer is loaded with a\n * dynamic variable-specifier import inside `transcribe()` so bundlers leave\n * it external and apps that never transcribe never pay for it. `available`\n * is the sync UI affordance signal; `transcribe()` re-verifies with the real\n * import and is the source of truth.\n */\n\nimport type { TranscriptionProvider, TranscriptionSegment } from '../contracts'\nimport type { AudioBufferLike } from './waveform'\n\nexport const DEFAULT_WHISPER_MODEL = 'onnx-community/whisper-large-v3-turbo'\n\n/** Whisper models are trained on 16 kHz mono — decoding through a 16 kHz\n * OfflineAudioContext resamples any source rate in one step. */\nconst WHISPER_SAMPLE_RATE = 16_000\n\nconst TRANSCRIPTION_PEER = '@huggingface/transformers'\nconst PEER_MISSING_MESSAGE = 'transcription requires optional peer @huggingface/transformers'\n\ninterface WhisperChunk {\n text: string\n timestamp: [number, number | null]\n}\n\nexport interface WhisperOutput {\n text: string\n chunks?: WhisperChunk[]\n}\n\ntype WhisperPipeline = (\n audio: Float32Array,\n options: Record<string, unknown>,\n) => Promise<WhisperOutput | WhisperOutput[]>\n\ninterface TransformersProgressEvent {\n status: string\n progress?: number\n}\n\ninterface TransformersModule {\n pipeline(\n task: 'automatic-speech-recognition',\n model: string,\n options?: Record<string, unknown>,\n ): Promise<WhisperPipeline>\n}\n\n/** Mean-mixdown to mono. Single-channel buffers return the live channel data\n * without copying — callers must treat the result as read-only. */\nexport function mixdownToMono(buffer: AudioBufferLike): Float32Array {\n if (buffer.numberOfChannels < 1) throw new Error('audio buffer has no channels — cannot mix down')\n if (buffer.numberOfChannels === 1) return buffer.getChannelData(0)\n const mono = new Float32Array(buffer.length)\n for (let channel = 0; channel < buffer.numberOfChannels; channel++) {\n const data = buffer.getChannelData(channel)\n if (data.length !== buffer.length) {\n throw new Error(`channel ${channel} has ${data.length} samples, expected ${buffer.length}`)\n }\n for (let i = 0; i < data.length; i++) {\n // in range by the length assertion above; casts silence noUncheckedIndexedAccess\n mono[i] = (mono[i] as number) + (data[i] as number)\n }\n }\n const scale = 1 / buffer.numberOfChannels\n for (let i = 0; i < mono.length; i++) {\n mono[i] = (mono[i] as number) * scale\n }\n return mono\n}\n\n/** Map whisper chunk output to contract segments. A `null` end timestamp is\n * whisper's \"ran past the end of the audio\" sentinel on the final chunk and\n * resolves to the audio duration. */\nexport function mapWhisperOutput(\n output: WhisperOutput | WhisperOutput[],\n durationSeconds: number,\n mediaUrl: string,\n): TranscriptionSegment[] {\n const first = Array.isArray(output) ? output[0] : output\n if (first === undefined) throw new Error(`whisper produced no output for ${mediaUrl}`)\n const chunks = first.chunks\n if (chunks === undefined || chunks.length === 0) {\n const text = first.text.trim()\n return text.length === 0 ? [] : [{ text, startSeconds: 0, endSeconds: durationSeconds }]\n }\n const segments: TranscriptionSegment[] = []\n for (const chunk of chunks) {\n const text = chunk.text.trim()\n if (text.length === 0) continue\n const [start, end] = chunk.timestamp\n if (!Number.isFinite(start)) {\n throw new Error(`whisper returned a non-numeric start timestamp for \"${text}\" in ${mediaUrl}`)\n }\n segments.push({ text, startSeconds: start, endSeconds: end === null ? durationSeconds : end })\n }\n return segments\n}\n\nasync function loadTransformers(): Promise<TransformersModule> {\n try {\n // Variable specifier + ignore pragmas keep bundlers from statically\n // resolving an optional peer that may not be installed.\n return (await import(/* @vite-ignore */ /* webpackIgnore: true */ TRANSCRIPTION_PEER)) as TransformersModule\n } catch (error) {\n throw new Error(PEER_MISSING_MESSAGE, { cause: error })\n }\n}\n\nasync function fetchMonoAudio(mediaUrl: string): Promise<{ samples: Float32Array; durationSeconds: number }> {\n const response = await fetch(mediaUrl)\n if (!response.ok) {\n throw new Error(`failed to fetch media for transcription: ${response.status} ${response.statusText} from ${mediaUrl}`)\n }\n const bytes = await response.arrayBuffer()\n if (typeof OfflineAudioContext === 'undefined') {\n throw new Error('transcription requires Web Audio (OfflineAudioContext) — call transcribe() from a browser')\n }\n // decodeAudioData resamples to the context rate per the Web Audio spec, so\n // a 16 kHz context yields whisper-ready samples from any source rate.\n const context = new OfflineAudioContext(1, 1, WHISPER_SAMPLE_RATE)\n const decoded = await context.decodeAudioData(bytes)\n return { samples: mixdownToMono(decoded), durationSeconds: decoded.duration }\n}\n\nexport function createWhisperTranscriptionProvider(opts?: { model?: string }): TranscriptionProvider {\n const model = opts?.model ?? DEFAULT_WHISPER_MODEL\n let availability: boolean | null = null\n let pipelinePromise: Promise<WhisperPipeline> | null = null\n\n const probeAvailability = (): boolean => {\n if (availability !== null) return availability\n const resolve = (import.meta as ImportMeta & { resolve?: (specifier: string) => string }).resolve\n if (typeof resolve !== 'function') {\n // No sync resolver in this runtime — report unavailable rather than\n // guess; transcribe() still attempts the real import either way.\n availability = false\n return availability\n }\n try {\n resolve.call(import.meta, TRANSCRIPTION_PEER)\n availability = true\n } catch {\n availability = false\n }\n return availability\n }\n\n const getPipeline = (\n transformers: TransformersModule,\n onProgress?: (fraction: number) => void,\n ): Promise<WhisperPipeline> => {\n if (pipelinePromise === null) {\n const device = typeof navigator !== 'undefined' && 'gpu' in navigator ? 'webgpu' : 'wasm'\n pipelinePromise = transformers.pipeline('automatic-speech-recognition', model, {\n dtype: 'q4',\n device,\n // Model download progress only — it dwarfs inference time on first\n // use, and only the first transcribe ever sees a download.\n progress_callback: (event: TransformersProgressEvent) => {\n if (event.status === 'progress' && typeof event.progress === 'number' && onProgress !== undefined) {\n onProgress(Math.min(1, Math.max(0, event.progress / 100)))\n }\n },\n })\n // A failed load must not poison the cache — the next transcribe retries.\n pipelinePromise.catch(() => {\n pipelinePromise = null\n })\n }\n return pipelinePromise\n }\n\n return {\n get available() {\n return probeAvailability()\n },\n async transcribe(mediaUrl, transcribeOpts) {\n const transformers = await loadTransformers()\n // A successful import is ground truth and overrides a false probe.\n availability = true\n const audio = await fetchMonoAudio(mediaUrl)\n const transcriber = await getPipeline(transformers, transcribeOpts?.onProgress)\n const options: Record<string, unknown> = {\n return_timestamps: true,\n chunk_length_s: 30,\n stride_length_s: 5,\n }\n if (transcribeOpts?.language !== undefined) options.language = transcribeOpts.language\n const output = await transcriber(audio.samples, options)\n const segments = mapWhisperOutput(output, audio.durationSeconds, mediaUrl)\n transcribeOpts?.onProgress?.(1)\n return segments\n },\n }\n}\n","/**\n * Code-split entry for the timeline editor. The editor pulls in canvas\n * painting, waveform decode, and gesture machinery products only need on the\n * sequence route — `React.lazy` keeps it out of their main bundle. Mount\n * inside a `<Suspense>` boundary.\n */\n\nimport React from 'react'\n\nexport const SequenceTimelineEditorLazy = React.lazy(() => import('./components/TimelineEditor'))\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  interface StudioIntegrationConnection {
2
2
  status: string;
3
3
  providerId: string;
4
- connectorId: string;
4
+ connectorId?: string;
5
5
  }
6
6
  type GenerationType = 'image' | 'video' | 'speech' | 'avatar' | 'transcription';
7
7
  type GenerationStatus = 'pending' | 'running' | 'succeeded' | 'failed';
@@ -26,7 +26,7 @@ import {
26
26
  relativeTime,
27
27
  selectedModelsWithDefaults,
28
28
  userSafeGenerationMessage
29
- } from "../chunk-DTS5TZRN.js";
29
+ } from "../chunk-MFI4ZM7W.js";
30
30
  export {
31
31
  CADENCES,
32
32
  DESTINATIONS,
@@ -24,7 +24,7 @@ import {
24
24
  relativeTime,
25
25
  selectedModelsWithDefaults,
26
26
  userSafeGenerationMessage
27
- } from "../chunk-DTS5TZRN.js";
27
+ } from "../chunk-MFI4ZM7W.js";
28
28
 
29
29
  // src/studio-react/studio-workspace.tsx
30
30
  import { useRef as useRef3, useState as useState3 } from "react";
@@ -375,13 +375,32 @@ interface ModelPickerProps {
375
375
  * sorts providers by tier).
376
376
  */
377
377
  declare function ModelPicker({ value, onChange, models, loading, renderProviderBadge, recommendedLabel }: ModelPickerProps): react.JSX.Element;
378
+ /** One reasoning-budget level: the engine `id` is unchanged (the value the
379
+ * product sends to the loop); only the user-facing `label` is renamed to the
380
+ * plainer "how hard should it think" vocabulary from docs/product-surfaces.md.
381
+ * `low`→Quick, `medium`→Standard, `high`→Extended. The mapping is overridable
382
+ * via `EffortPickerProps.levels`, so a product can relabel without losing the
383
+ * ids the runtime expects. */
384
+ interface EffortLevel {
385
+ id: string;
386
+ label: string;
387
+ }
388
+ declare const DEFAULT_EFFORT_LEVELS: readonly EffortLevel[];
378
389
  interface EffortPickerProps {
379
390
  value: string;
380
391
  onChange: (id: string) => void;
392
+ /** Selectable levels (engine id + user-facing label). Defaults to the plain
393
+ * "Thinking" vocabulary; override to relabel without changing the ids the
394
+ * runtime receives. */
395
+ levels?: readonly EffortLevel[];
396
+ /** Prefix shown before the active level on the pill — the "what is this"
397
+ * context the bare value lacked. Default "Thinking". Pass '' to hide it. */
398
+ label?: string;
381
399
  }
382
- /** Reasoning-effort selector pill, styled to match {@link ModelPicker}. Show
383
- * it only when the selected model `supportsReasoning`. */
384
- declare function EffortPicker({ value, onChange }: EffortPickerProps): react.JSX.Element;
400
+ /** Thinking-budget selector pill, styled to match {@link ModelPicker}. Show
401
+ * it only when the selected model `supportsReasoning`. "Thinking" is the
402
+ * plain-English name for what was internally called "effort". */
403
+ declare function EffortPicker({ value, onChange, levels, label }: EffortPickerProps): react.JSX.Element;
385
404
 
386
405
  interface AgentSessionControlsProps {
387
406
  /** Catalog models — canonical provider-prefixed ids. */
@@ -464,12 +483,28 @@ interface ChatToolCallInfo {
464
483
  declare function pendingApprovalOf(call: ChatToolCallInfo): {
465
484
  proposalId: string;
466
485
  } | null;
486
+ /** One ordered piece of an assistant turn: a run of answer text, or a tool
487
+ * call, in the sequence the agent emitted them. A message carrying `segments`
488
+ * is rendered in order — interleaving text and tool chips — so the agent's
489
+ * pre- and post-tool reasoning reads chronologically instead of as one text
490
+ * blob with the tool chips collected after it. */
491
+ type ChatMessageSegment = {
492
+ kind: 'text';
493
+ content: string;
494
+ } | {
495
+ kind: 'tool';
496
+ call: ChatToolCallInfo;
497
+ };
467
498
  interface ChatUiMessage extends ChatMessageMetrics {
468
499
  id: string;
469
500
  role: 'user' | 'assistant' | 'system';
470
501
  content: string;
471
502
  reasoning?: string;
472
503
  toolCalls?: ChatToolCallInfo[];
504
+ /** Ordered text/tool sequence for true chronological interleaving. When
505
+ * present and non-empty it is rendered in place of `content` + `toolCalls`;
506
+ * both remain the fallback for producers that don't segment a turn. */
507
+ segments?: ChatMessageSegment[];
473
508
  }
474
509
  interface ChatMessagesProps {
475
510
  messages: ChatUiMessage[];
@@ -498,9 +533,41 @@ interface ChatMessagesProps {
498
533
  * retry button (e.g. when the product retries automatically). */
499
534
  onRetry?: () => void;
500
535
  /** Zero-state renderer, shown when there are no messages and the turn is
501
- * neither loading nor errored. Omit to render nothing (current behavior). */
536
+ * neither loading nor errored. When omitted, a branded first-run state is
537
+ * shown ({@link ChatEmptyState}); pass `() => null` to render nothing. */
502
538
  renderEmpty?: () => ReactNode;
539
+ /** First-run state config used when `renderEmpty` is not supplied. Lets a
540
+ * product set the headline and the "doors" (e.g. start from a template, ask
541
+ * the agent) without replacing the whole zero-state. */
542
+ emptyState?: ChatEmptyStateProps;
543
+ /** Optional branded header slot rendered above the thread. Off by default to
544
+ * preserve the current layout; pass `{ title }` (or your own node via
545
+ * `header`) to show the Tangle mark + product title in the chat shell. */
546
+ header?: ReactNode;
503
547
  }
548
+ /** One starting "door" in the chat first-run state — a concrete, labeled action
549
+ * (start from a template, do it by hand, ask the agent), not a placeholder. */
550
+ interface ChatEmptyDoor {
551
+ label: string;
552
+ description?: string;
553
+ onSelect: () => void;
554
+ }
555
+ interface ChatEmptyStateProps {
556
+ /** Product name shown next to the Tangle mark. Default "Agent". */
557
+ productName?: string;
558
+ /** Headline. Default frames delegation, not messaging. */
559
+ headline?: string;
560
+ /** Subline under the headline. */
561
+ subline?: string;
562
+ /** Up to three concrete starting doors. Omit for a mark-and-prompt-only state. */
563
+ doors?: ChatEmptyDoor[];
564
+ }
565
+ /**
566
+ * Branded chat first-run state: the Tangle mark, a delegation-framed prompt, and
567
+ * up to three concrete doors. Replaces the blank thread that read as "empty or
568
+ * broken". Concrete + actionable — never a "coming soon" placeholder.
569
+ */
570
+ declare function ChatEmptyState({ productName, headline, subline, doors, }: ChatEmptyStateProps): react.JSX.Element;
504
571
  interface ProposalApprovalHandlers {
505
572
  onApprove: (proposalId: string, toolCallId: string) => void | Promise<void>;
506
573
  onReject: (proposalId: string, toolCallId: string) => void | Promise<void>;
@@ -514,6 +581,6 @@ type ToolDetailRenderers = Record<string, (call: ChatToolCallInfo, message: Chat
514
581
  * model id, tokens/sec, and cost, plus a collapsible thinking section and
515
582
  * tool-call chips.
516
583
  */
517
- declare function ChatMessages({ messages, models, renderMarkdown, renderExtras, userLabel, agentLabel, loading, approval, onToolCallClick, toolRenderers, error, onRetry, renderEmpty, }: ChatMessagesProps): react.JSX.Element;
584
+ declare function ChatMessages({ messages, models, renderMarkdown, renderExtras, userLabel, agentLabel, loading, approval, onToolCallClick, toolRenderers, error, onRetry, renderEmpty, emptyState, header, }: ChatMessagesProps): react.JSX.Element;
518
585
 
519
- export { type ActivityTone, type AgentActivityPage, AgentActivityPanel, type AgentActivityPanelProps, type AgentActivityRecord, AgentSessionControls, type AgentSessionControlsProps, type ChatMessageMetrics, ChatMessages, type ChatMessagesProps, type ChatStreamCallbacks, type ChatStreamToolCall, type ChatStreamToolResult, type ChatToolCallInfo, type ChatUiMessage, type ConsumeChatStreamResult, EffortPicker, type EffortPickerProps, FlowWaterfall, type FlowWaterfallProps, MissionActivityLane, type MissionActivityLaneProps, ModelPicker, type ModelPickerProps, type ProposalApprovalHandlers, ProviderLogo, type ProviderLogoProps, RunDrillIn, type RunDrillInProps, type SandboxTerminalConnection, type SandboxTerminalConnectionResponse, SeatPaywall, type SeatPaywallProps, type SmoothRevealOptions, type StreamChatOptions, type TerminalStatusDisplay, type TerminalStatusTone, type ToolDetailRenderers, type ToolRunRecord, type ToolRunStep, type UseSandboxTerminalConnectionOptions, type UseSandboxTerminalConnectionResult, type WaterfallRow, WorkspaceTerminalPanel, type WorkspaceTerminalPanelProps, activityTone, consumeChatStream, dispatchChatStreamLine, formatActivityCost, formatActivityDuration, formatModelCost, formatTokensPerSecond, mergeActivityPages, nextRevealCount, pendingApprovalOf, streamChatTurn, tabTerminalConnectionId, usePending, usePopover, useSandboxTerminalConnection, useSmoothText, waterfallLayout };
586
+ export { type ActivityTone, type AgentActivityPage, AgentActivityPanel, type AgentActivityPanelProps, type AgentActivityRecord, AgentSessionControls, type AgentSessionControlsProps, type ChatEmptyDoor, ChatEmptyState, type ChatEmptyStateProps, type ChatMessageMetrics, type ChatMessageSegment, ChatMessages, type ChatMessagesProps, type ChatStreamCallbacks, type ChatStreamToolCall, type ChatStreamToolResult, type ChatToolCallInfo, type ChatUiMessage, type ConsumeChatStreamResult, DEFAULT_EFFORT_LEVELS, type EffortLevel, EffortPicker, type EffortPickerProps, FlowWaterfall, type FlowWaterfallProps, MissionActivityLane, type MissionActivityLaneProps, ModelPicker, type ModelPickerProps, type ProposalApprovalHandlers, ProviderLogo, type ProviderLogoProps, RunDrillIn, type RunDrillInProps, type SandboxTerminalConnection, type SandboxTerminalConnectionResponse, SeatPaywall, type SeatPaywallProps, type SmoothRevealOptions, type StreamChatOptions, type TerminalStatusDisplay, type TerminalStatusTone, type ToolDetailRenderers, type ToolRunRecord, type ToolRunStep, type UseSandboxTerminalConnectionOptions, type UseSandboxTerminalConnectionResult, type WaterfallRow, WorkspaceTerminalPanel, type WorkspaceTerminalPanelProps, activityTone, consumeChatStream, dispatchChatStreamLine, formatActivityCost, formatActivityDuration, formatModelCost, formatTokensPerSecond, mergeActivityPages, nextRevealCount, pendingApprovalOf, streamChatTurn, tabTerminalConnectionId, usePending, usePopover, useSandboxTerminalConnection, useSmoothText, waterfallLayout };