@hlang-tech/editor-ui 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { NodeKind, EditorNode, PortDescriptor, EditorCore, NodeId, Diagnostic, ParamValue, NodeDescriptor, ParamBag, Endpoint, IConnectionValidator, Point, IExecutionHandle } from '@hlang-tech/editor-core';
2
- import { CSSProperties, ReactNode, ReactElement, RefObject } from 'react';
1
+ import { NodeKind, EditorNode, PortDescriptor, EditorCore, NodeId, Diagnostic, ParamValue, NodeDescriptor, ParamBag, Endpoint, IConnectionValidator, Point as Point$1, IExecutionHandle, INodeDeliveryEvent, INodeErrorEvent, INodeEndedEvent, INodeFinishedEvent, INodeDroppedEvent, EdgeId, EditorEdge, EdgeBounds } from '@hlang-tech/editor-core';
2
+ import { CSSProperties, ReactNode, ReactElement, RefObject, UIEvent } from 'react';
3
3
  import { NodeProps, EdgeProps, NodeTypes, EdgeTypes, OnConnectStartParams } from '@xyflow/react';
4
4
  import { HlangRFNode, HlangRFEdge } from '@hlang-tech/editor-react';
5
5
 
@@ -159,7 +159,7 @@ declare function NodeBodySlot({ children }: NodeBodySlotProps): ReactElement | n
159
159
 
160
160
  /**
161
161
  * The signature edge: a connection rendered as a living stream. A base bezier
162
- * conduit plus a silver overlay that marches from output (O) to input (I) —
162
+ * conduit (or, from a node back into itself, a loop routed around the node) plus a silver overlay that marches from output (O) to input (I) —
163
163
  * exactly what an hlang RxJS port does; a cold spark lights it while flowing.
164
164
  *
165
165
  * When the SOURCE node carries a semantic `role` param (e.g. Character,
@@ -170,6 +170,48 @@ declare function NodeBodySlot({ children }: NodeBodySlotProps): ReactElement | n
170
170
 
171
171
  declare function FlowEdge(props: EdgeProps<HlangRFEdge>): ReactElement;
172
172
 
173
+ /**
174
+ * The route of an edge from a node back into the same node: a Transform fed
175
+ * by its own output (iteration). A curve between two handles on opposite
176
+ * sides of one node would cut across it; instead the edge leaves the output
177
+ * to the right, runs under the node and comes up on the left into the input,
178
+ * with rounded corners, as node editors route an edge that goes backwards
179
+ * (n8n's back edges). Pure geometry, so the edge component only draws it.
180
+ */
181
+ interface Point {
182
+ readonly x: number;
183
+ readonly y: number;
184
+ }
185
+ interface Rect {
186
+ readonly x: number;
187
+ readonly y: number;
188
+ readonly width: number;
189
+ readonly height: number;
190
+ }
191
+ interface SelfLoopInput {
192
+ /** The output handle (on the node's right side) and the input handle (on its left). */
193
+ readonly source: Point;
194
+ readonly target: Point;
195
+ /** The node, in the same coordinates. */
196
+ readonly node: Rect;
197
+ /** How far the route keeps from the node. Default `SELF_LOOP_CLEARANCE`. */
198
+ readonly clearance?: number;
199
+ /** The corner radius, reduced where a segment is too short. Default `SELF_LOOP_RADIUS`. */
200
+ readonly radius?: number;
201
+ }
202
+ interface SelfLoop {
203
+ /** SVG path data. */
204
+ readonly path: string;
205
+ /** Where a label sits: the middle of the run under the node. */
206
+ readonly labelX: number;
207
+ readonly labelY: number;
208
+ }
209
+ /** The design system's distance between a self-loop and its node, in flow units. */
210
+ declare const SELF_LOOP_CLEARANCE = 28;
211
+ /** The design system's corner radius for a self-loop, in flow units. */
212
+ declare const SELF_LOOP_RADIUS = 16;
213
+ declare function selfLoopPath(input: SelfLoopInput): SelfLoop;
214
+
173
215
  /**
174
216
  * One-line React Flow wiring: `createHlangFlowTypes()` returns the `nodeTypes`
175
217
  * and `edgeTypes` maps to spread onto `<ReactFlow>`, binding hlang's R/T/W node
@@ -188,11 +230,84 @@ interface HlangFlowTypes {
188
230
  /** Build the React Flow node/edge type maps for the hlang editor. */
189
231
  declare function createHlangFlowTypes(): HlangFlowTypes;
190
232
 
233
+ /**
234
+ * The size rules of the editor's resizable footer (its console): how tall it
235
+ * may be, and how the keyboard and a double click change it. Pure, so the
236
+ * pointer and keyboard handling in `useFooterResize` stays a thin layer.
237
+ */
238
+ /** How far one arrow key press moves the footer edge, in pixels; Shift moves four times as far. */
239
+ declare const FOOTER_KEY_STEP = 16;
240
+ interface FooterBounds {
241
+ readonly min: number;
242
+ readonly max: number;
243
+ }
244
+ /**
245
+ * The heights the footer may take in an editor with `available` pixels below
246
+ * its toolbar: at least `minHeight` (its header alone, collapsed), and at most
247
+ * what leaves the canvas `minCanvasHeight`.
248
+ */
249
+ declare function footerBounds(options: {
250
+ readonly available: number;
251
+ readonly minHeight: number;
252
+ readonly minCanvasHeight: number;
253
+ }): FooterBounds;
254
+ /** `height` held within `bounds`, in whole pixels. */
255
+ declare function clampFooter(height: number, bounds: FooterBounds): number;
256
+ /** The height after pressing `key` on the footer edge, or `undefined` for a key that does nothing. */
257
+ declare function resizeByKey(height: number, key: string, shift: boolean, bounds: FooterBounds): number | undefined;
258
+ /** The footer's height and the height to return to when it is opened again. */
259
+ interface FooterSize {
260
+ readonly height: number;
261
+ readonly restore: number;
262
+ }
263
+ /** Collapses an open footer (remembering its height), or opens a collapsed one to that height. */
264
+ declare function toggleCollapsed(size: FooterSize, bounds: FooterBounds): FooterSize;
265
+
266
+ /**
267
+ * `useFooterResize` — the state of a footer whose top edge the user drags, or
268
+ * moves with the keyboard, to make it taller or shorter. It takes only what it
269
+ * reads (a pointer's `clientY`, a key), so `FooterResizer` adapts the browser's
270
+ * events to it and the behavior is testable without a browser.
271
+ *
272
+ * The height the user asked for is kept apart from the height shown: when the
273
+ * editor gets shorter the footer shrinks to fit, and when it grows back the
274
+ * footer returns to the asked-for height.
275
+ */
276
+
277
+ /** How a resizable footer behaves. */
278
+ interface FooterResizeOptions {
279
+ /** Its height when the editor opens, in pixels. */
280
+ readonly defaultHeight: number;
281
+ /** Its height when collapsed: its own header, so it can always be grabbed and opened again. */
282
+ readonly minHeight: number;
283
+ /** The height the canvas always keeps. Default `DEFAULT_MIN_CANVAS_HEIGHT`. */
284
+ readonly minCanvasHeight?: number;
285
+ }
286
+ /** The canvas height a footer never takes away, unless a host sets another. */
287
+ declare const DEFAULT_MIN_CANVAS_HEIGHT = 160;
288
+ interface FooterResize {
289
+ /** The height to render. */
290
+ readonly height: number;
291
+ readonly bounds: FooterBounds;
292
+ readonly dragging: boolean;
293
+ /** Tells the hook how many pixels the toolbar leaves for the canvas and the footer. */
294
+ readonly measure: (available: number) => void;
295
+ readonly startDrag: (clientY: number) => void;
296
+ readonly drag: (clientY: number) => void;
297
+ readonly endDrag: () => void;
298
+ /** Applies a key pressed on the edge; returns whether the key resized the footer. */
299
+ readonly keyDown: (key: string, shift: boolean) => boolean;
300
+ /** Collapses the footer, or opens it again to the height it had. */
301
+ readonly toggle: () => void;
302
+ }
303
+ declare function useFooterResize(options: FooterResizeOptions): FooterResize;
304
+
191
305
  /**
192
306
  * `EditorShell` — composes the editor's regions (top toolbar, left panel,
193
- * center canvas, right inspector) as a CSS grid. It holds NO graph logic; a
194
- * host slots children into named regions. Left/right panels are collapsible via
195
- * UI-only props (collapse state is presentation, never part of the graph).
307
+ * center canvas, right inspector, footer). It holds NO graph logic; a host
308
+ * slots children into named regions. Left/right panels are collapsible via
309
+ * UI-only props, and the footer can be made resizable (presentation state,
310
+ * never part of the graph).
196
311
  */
197
312
 
198
313
  interface EditorShellProps {
@@ -206,10 +321,36 @@ interface EditorShellProps {
206
321
  readonly canvas: ReactNode;
207
322
  /** Optional footer region (e.g. an output/console strip). */
208
323
  readonly footer?: ReactNode;
324
+ /**
325
+ * Makes the footer resizable: its top edge can be dragged, moved with the
326
+ * keyboard, and double-clicked to collapse it. Without it the footer takes
327
+ * its content's height.
328
+ */
329
+ readonly footerResize?: FooterResizeOptions & {
330
+ /** The accessible name of the footer's resize handle. Default "Resize the panel". */
331
+ readonly label?: string;
332
+ };
209
333
  readonly leftCollapsed?: boolean;
210
334
  readonly rightCollapsed?: boolean;
211
335
  }
212
- declare function EditorShell({ toolbar, left, right, canvas, footer, leftCollapsed, rightCollapsed, }: EditorShellProps): ReactElement;
336
+ declare function EditorShell({ toolbar, left, right, canvas, footer, footerResize, leftCollapsed, rightCollapsed, }: EditorShellProps): ReactElement;
337
+
338
+ /**
339
+ * `FooterResizer` — the grab handle on a resizable footer's top edge. Drag it
340
+ * with a mouse, a finger or a pen; with focus, the arrow keys move it (Shift
341
+ * for larger steps), Home and End go to the smallest and largest heights, and
342
+ * Enter or a double click collapses the footer and opens it again. It is a
343
+ * focusable ARIA `separator` that reports the footer's height.
344
+ */
345
+
346
+ interface FooterResizerProps {
347
+ readonly resize: FooterResize;
348
+ /** The id of the footer the handle resizes. */
349
+ readonly controls: string;
350
+ /** Its accessible name. */
351
+ readonly label: string;
352
+ }
353
+ declare function FooterResizer({ resize, controls, label }: FooterResizerProps): ReactElement;
213
354
 
214
355
  /**
215
356
  * The top toolbar: a thin glass bar with a brand wordmark on the left and an
@@ -255,6 +396,21 @@ declare function ToolbarGhostButton({ children, onClick, title, }: {
255
396
  readonly title?: string;
256
397
  }): ReactElement;
257
398
 
399
+ /**
400
+ * `ReadOnlyBadge` — says, in the toolbar, that the graph can be looked at
401
+ * and run but not changed (the editor core is read-only; see editor-core's
402
+ * `IEditorModeService`). The host says why in `title`. In a narrow toolbar it
403
+ * keeps only its lock; the label stays its accessible name.
404
+ */
405
+
406
+ interface ReadOnlyBadgeProps {
407
+ /** The badge's text. */
408
+ readonly label?: string;
409
+ /** Why the editor is read-only, shown as its tooltip. */
410
+ readonly title?: string;
411
+ }
412
+ declare function ReadOnlyBadge({ label, title }: ReadOnlyBadgeProps): ReactElement;
413
+
258
414
  /**
259
415
  * `ToolCluster` — the centred group of canvas tools in the top bar (nover-style):
260
416
  * a row of icon buttons with optional separators, in the black/silver glass
@@ -297,6 +453,10 @@ declare function CanvasChrome({ background }: CanvasChromeProps): ReactElement;
297
453
  * glass language. It replaces React Flow's default corner Controls + MiniMap
298
454
  * with one calm, centred dock.
299
455
  *
456
+ * A read-only dock (`readOnly`) keeps only the view controls (zoom, fit):
457
+ * undo, redo, the pointer modes and add-node all change the graph or exist
458
+ * to edit it.
459
+ *
300
460
  * Presentation only: it renders buttons and the current zoom %, and calls back
301
461
  * to the host for every action. It holds no React Flow or graph state — the host
302
462
  * wires the callbacks to `useReactFlow()` (zoom/fit) and the node search (add).
@@ -320,8 +480,10 @@ interface CanvasDockProps {
320
480
  readonly onRedo: () => void;
321
481
  readonly canUndo: boolean;
322
482
  readonly canRedo: boolean;
483
+ /** Show the view controls only: the graph cannot be edited. */
484
+ readonly readOnly?: boolean;
323
485
  }
324
- declare function CanvasDock({ zoom, onZoomIn, onZoomOut, onFitView, onAdd, mode, onModeChange, onUndo, onRedo, canUndo, canRedo, }: CanvasDockProps): ReactElement;
486
+ declare function CanvasDock({ zoom, onZoomIn, onZoomOut, onFitView, onAdd, mode, onModeChange, onUndo, onRedo, canUndo, canRedo, readOnly, }: CanvasDockProps): ReactElement;
325
487
 
326
488
  /**
327
489
  * `EmptyState` — a quiet, centered hint shown over an empty canvas. Purely
@@ -339,6 +501,7 @@ declare function EmptyState({ title, hint, }: EmptyStateProps): ReactElement;
339
501
  * `InspectorPanel` — the right-hand property inspector. It follows selection:
340
502
  * with exactly one node selected it shows that node's identity plus a
341
503
  * schema-driven parameter form (edits commit undoable UpdateParamsCommands);
504
+ * with exactly one connection selected, the connection and its back-pressure;
342
505
  * otherwise it shows an empty/summary state. This is the unified control center
343
506
  * (nover.studio's model) over hlang's headless core.
344
507
  */
@@ -387,6 +550,59 @@ interface PreviewPanelProps {
387
550
  }
388
551
  declare function PreviewPanel({ imageUrl, title, status }: PreviewPanelProps): ReactElement;
389
552
 
553
+ /**
554
+ * `PanelHead` and `PanelAction` — the header row of a docked panel (the
555
+ * console, the run log): what the panel is, a short status, and its actions.
556
+ *
557
+ * The header responds to its own width, not the window's (a container query,
558
+ * as VS Code's and DevTools' panel toolbars do): when it is narrow the actions
559
+ * drop their labels and keep their icons, and then the status gives way. A
560
+ * label that is not shown is still the button's accessible name, and its
561
+ * tooltip.
562
+ */
563
+
564
+ interface PanelHeadProps {
565
+ /** What the panel is: its title, or the host's tabs. */
566
+ readonly leading: ReactNode;
567
+ /** A short status after it, e.g. a count; the first thing to go when the panel is narrow. */
568
+ readonly meta?: ReactNode;
569
+ /** The panel's actions, usually `PanelAction`s. */
570
+ readonly children?: ReactNode;
571
+ }
572
+ declare function PanelHead({ leading, meta, children }: PanelHeadProps): ReactElement;
573
+ interface PanelActionProps {
574
+ readonly icon: ReactNode;
575
+ /** The action's name: shown when there is room, and always its accessible name. */
576
+ readonly label: string;
577
+ readonly onClick: () => void;
578
+ /** For a toggle: whether it is on. Leave out for a plain action. */
579
+ readonly pressed?: boolean;
580
+ readonly disabled?: boolean;
581
+ /** The tooltip; the label by default. */
582
+ readonly title?: string;
583
+ }
584
+ declare function PanelAction({ icon, label, onClick, pressed, disabled, title }: PanelActionProps): ReactElement;
585
+
586
+ /**
587
+ * Per-type node icons. A small registry mapping a node `code` to a crisp
588
+ * line-glyph (1.5px stroke, currentColor), with a kind fallback (R/T/W). Used
589
+ * by the outline tree and the node chrome so every node reads by its ROLE, not
590
+ * a generic letter — the difference between a tool and a spreadsheet.
591
+ *
592
+ * Icons are inline SVG (not emoji) for a premium, monochrome, scalable look;
593
+ * they inherit `color` so callers tint them by kind or silver.
594
+ */
595
+
596
+ interface IconProps {
597
+ readonly size?: number;
598
+ }
599
+ /** Funnel — show only what involves the selection. */
600
+ declare function FilterIcon({ size }: IconProps): ReactElement;
601
+ /** Arrow onto a line — keep the newest entry in view. */
602
+ declare function FollowIcon({ size }: IconProps): ReactElement;
603
+ /** Circle with a stroke through it — clear the log (DevTools' glyph). */
604
+ declare function ClearIcon({ size }: IconProps): ReactElement;
605
+
390
606
  /**
391
607
  * `RailTabs` — the segmented tab strip at the top of a side rail (Outline /
392
608
  * Insert / Assets, nover-style), in the black/silver glass language. Purely
@@ -517,8 +733,10 @@ interface ParamFormProps {
517
733
  readonly fields: readonly ParamField[];
518
734
  /** Commit a single field's new value (undoable, handled by the caller). */
519
735
  readonly onCommit: (key: string, value: ParamValue) => void;
736
+ /** Disables every field, e.g. in a read-only editor. */
737
+ readonly disabled?: boolean;
520
738
  }
521
- declare function ParamForm({ fields, onCommit }: ParamFormProps): ReactElement;
739
+ declare function ParamForm({ fields, onCommit, disabled }: ParamFormProps): ReactElement;
522
740
 
523
741
  /**
524
742
  * `useNodeParams` — reactively read a node's params and commit edits through the
@@ -641,9 +859,9 @@ declare function connectableCandidates(query: string, descriptors: readonly Node
641
859
  /** Where and how the palette is currently open. */
642
860
  interface NodeSearchState {
643
861
  /** Screen-space anchor for rendering the floating panel. */
644
- readonly screen: Point;
862
+ readonly screen: Point$1;
645
863
  /** Flow-space position where the created node will be placed. */
646
- readonly flow: Point;
864
+ readonly flow: Point$1;
647
865
  /** Set when opened by releasing a dangling connection. */
648
866
  readonly pending?: PendingConnection;
649
867
  }
@@ -652,9 +870,9 @@ interface NodeSearchController {
652
870
  /** Current open state, or `undefined` when closed. */
653
871
  readonly state: NodeSearchState | undefined;
654
872
  /** Open the palette to create a free-standing node. */
655
- openBlank(screen: Point, flow: Point): void;
873
+ openBlank(screen: Point$1, flow: Point$1): void;
656
874
  /** Open the palette to complete a released connection. */
657
- openFromConnection(screen: Point, flow: Point, pending: PendingConnection): void;
875
+ openFromConnection(screen: Point$1, flow: Point$1, pending: PendingConnection): void;
658
876
  /** Close the palette without creating anything. */
659
877
  close(): void;
660
878
  /**
@@ -703,6 +921,9 @@ declare function NodeSearch({ controller }: NodeSearchProps): ReactElement | nul
703
921
  * only for the pane, never for nodes/edges/handles) rather than a raw DOM
704
922
  * double-click, so this never reaches into React Flow's internal markup.
705
923
  *
924
+ * A read-only editor (`core.mode.readOnly`) opens nothing: the palette only
925
+ * adds nodes, which a read-only graph refuses.
926
+ *
706
927
  * Screen→flow coordinate conversion uses the React Flow instance, so created
707
928
  * nodes land under the pointer regardless of pan/zoom. This hook must be used
708
929
  * within a `ReactFlowProvider` (React Flow's own requirement for `useReactFlow`).
@@ -779,14 +1000,14 @@ declare function useEditorHotkeys(core: EditorCore, options?: EditorHotkeysOptio
779
1000
  /** Where the menu is open and which node it targets. */
780
1001
  interface NodeContextMenuState {
781
1002
  /** Screen-space anchor (the click point). */
782
- readonly screen: Point;
1003
+ readonly screen: Point$1;
783
1004
  /** The node the menu acts on. */
784
1005
  readonly nodeId: NodeId;
785
1006
  }
786
1007
  interface NodeContextMenuController {
787
1008
  readonly state: NodeContextMenuState | undefined;
788
1009
  /** Open the menu for a node at a screen point. */
789
- openForNode(screen: Point, nodeId: NodeId): void;
1010
+ openForNode(screen: Point$1, nodeId: NodeId): void;
790
1011
  close(): void;
791
1012
  }
792
1013
  declare function useNodeContextMenu(): NodeContextMenuController;
@@ -824,15 +1045,19 @@ declare function NodeContextMenu({ controller, items }: NodeContextMenuProps): R
824
1045
  * `NodeInteractionContext` — optional per-node interaction wiring supplied by the
825
1046
  * host (diagnostics to badge, rename to commit). Node chrome reads it to render a
826
1047
  * diagnostic badge and to support inline rename, but degrades gracefully to a
827
- * plain, read-only node when no provider is present — so editor-ui stays usable
1048
+ * plain node that cannot be renamed when no provider is present, or when the
1049
+ * host leaves `rename` out (a read-only editor) — so editor-ui stays usable
828
1050
  * without the full editor wiring.
829
1051
  */
830
1052
 
831
1053
  interface NodeInteraction {
832
1054
  /** The most severe diagnostic on a node, or undefined. */
833
1055
  diagnosticFor(id: NodeId): Diagnostic | undefined;
834
- /** Commit a rename (host routes it through the command service). */
835
- rename(id: NodeId, name: string): void;
1056
+ /**
1057
+ * Commit a rename (host routes it through the command service). Without it,
1058
+ * a node's name cannot be edited in place.
1059
+ */
1060
+ readonly rename?: (id: NodeId, name: string) => void;
836
1061
  }
837
1062
  interface NodeInteractionProviderProps {
838
1063
  readonly value: NodeInteraction;
@@ -897,27 +1122,43 @@ declare function useToast(): ToastApi;
897
1122
  * frame however many signals arrived in it.
898
1123
  */
899
1124
  /** Live state of a node during execution. */
900
- type NodeLiveState = 'idle' | 'active' | 'error';
901
- type Listener = () => void;
902
- /** Minimal edge shape the store needs to map a source-port pulse to edges. */
1125
+ type NodeLiveState = 'idle' | 'active' | 'error' | 'finished';
1126
+ type Listener$1 = () => void;
1127
+ /**
1128
+ * Minimal edge shape the store needs to map a signal to edges. With its
1129
+ * target, a delivery lights exactly the edge that carried it.
1130
+ */
903
1131
  interface EdgeIndexEntry {
904
1132
  readonly id: string;
905
1133
  readonly sourceNodeId: string;
906
1134
  readonly sourcePortId: string;
1135
+ readonly targetNodeId?: string;
1136
+ readonly targetPortId?: string;
907
1137
  readonly isError: boolean;
908
1138
  }
1139
+ /** A message delivered on one connection: its source ran and sent it, its target ran to handle it. */
1140
+ interface DeliverySignal {
1141
+ readonly sourceNodeId: string;
1142
+ readonly sourcePortId: string;
1143
+ readonly targetNodeId: string;
1144
+ readonly targetPortId: string;
1145
+ }
909
1146
  /** Injected clock + scheduler so the store is deterministic and testable. */
910
1147
  interface LiveSignalStoreDeps {
911
1148
  now(): number;
912
1149
  schedule(cb: () => void): number;
913
1150
  cancel(handle: number): void;
914
1151
  }
1152
+ /** The browser's clock and frame scheduler, with fallbacks outside a browser. */
1153
+ declare const defaultLiveDeps: LiveSignalStoreDeps;
915
1154
  declare class LiveSignalStore {
916
1155
  private readonly deps;
917
1156
  /** nodeId -> last-active timestamp. */
918
1157
  private readonly nodeActiveAt;
919
1158
  /** nodeId set that errored during this run. */
920
1159
  private readonly erroredNodes;
1160
+ /** nodeId set that finished during this run (RFC 0001). */
1161
+ private readonly finishedNodes;
921
1162
  /** edgeId -> last-flowing timestamp. */
922
1163
  private readonly edgeFlowAt;
923
1164
  /** Per-id listeners. */
@@ -928,21 +1169,42 @@ declare class LiveSignalStore {
928
1169
  private readonly lastEdgeFlowing;
929
1170
  /** Edges sourced by a given (nodeId@portId), for pulse -> edge mapping. */
930
1171
  private edgesBySourcePort;
1172
+ /** Edges by the connection they carry (source port -> target port). */
1173
+ private edgesByConnection;
1174
+ /** How many messages each node has handled or sent, and each edge carried, this run. */
1175
+ private readonly nodePulse;
1176
+ private readonly edgePulse;
1177
+ /** The pulse counts last notified, so a new message notifies even while the state stays active. */
1178
+ private readonly lastNodePulse;
1179
+ private readonly lastEdgePulse;
931
1180
  private rafHandle;
932
1181
  private running;
933
1182
  constructor(deps?: LiveSignalStoreDeps);
934
1183
  /** Provide the current edge index (call whenever edges change). */
935
1184
  setEdgeIndex(edges: readonly EdgeIndexEntry[]): void;
1185
+ /**
1186
+ * Record a message delivered on a connection, when it happens: its source
1187
+ * and its target each pulse once (each ran), and so does the edge that
1188
+ * carried it, and only that edge. Subscribers are notified on the next
1189
+ * frame, once however many messages arrived in it.
1190
+ */
1191
+ markDelivery(signal: DeliverySignal): void;
1192
+ /** How many messages a node has handled or sent this run; it changes each time the node runs. */
1193
+ getNodePulse(nodeId: string): number;
1194
+ /** How many messages an edge has carried this run. */
1195
+ getEdgePulse(edgeId: string): number;
936
1196
  /** Record a node emitting on an output port (drives node + edge activity). */
937
1197
  markActivity(nodeId: string, portId: string): void;
1198
+ /** Record a node finishing (RFC 0001): shown once its last flash has faded. */
1199
+ markFinished(nodeId: string): void;
938
1200
  /** Record a node erroring (sticky for the run). */
939
1201
  markError(nodeId: string): void;
940
1202
  /** Clear all live state (on stop/teardown) and notify everyone. */
941
1203
  reset(): void;
942
1204
  /** Subscribe to a single node's live state. */
943
- subscribeNode(nodeId: string, listener: Listener): () => void;
1205
+ subscribeNode(nodeId: string, listener: Listener$1): () => void;
944
1206
  /** Subscribe to a single edge's flowing state. */
945
- subscribeEdge(edgeId: string, listener: Listener): () => void;
1207
+ subscribeEdge(edgeId: string, listener: Listener$1): () => void;
946
1208
  /** Current live state of a node. */
947
1209
  getNodeState(nodeId: string): NodeLiveState;
948
1210
  /** Whether an edge is currently flowing. */
@@ -1000,8 +1262,288 @@ declare function LiveSignalProvider({ store, children }: LiveSignalProviderProps
1000
1262
  declare function useLiveSignalStore(): LiveSignalStore | null;
1001
1263
  /** Subscribe to a single node's live state. Idle when no store is present. */
1002
1264
  declare function useNodeLiveState(nodeId: string): NodeLiveState;
1265
+ /** How many messages a node has handled or sent this run; it changes each time the node runs. */
1266
+ declare function useNodePulse(nodeId: string): number;
1267
+ /** How many messages an edge has carried this run. */
1268
+ declare function useEdgePulse(edgeId: string): number;
1003
1269
  /** Subscribe to a single edge's flowing state. False when no store is present. */
1004
1270
  declare function useEdgeFlowing(edgeId: string): boolean;
1005
1271
 
1006
- export { AssetGrid, AssetNodeChrome, CanvasChrome, CanvasDock, Cheatsheet, DiagnosticBadge, EditorShell, EditorUIThemeProvider, EmptyState, FlowEdge, HlangNode, InspectorPanel, KIND_ROLE, KIND_TOKEN, LiveSignalProvider, LiveSignalStore, NodeBodySlot, NodeChrome, NodeContextMenu, NodeInteractionProvider, NodeSearch, OutlineTree, ParamForm, PillNodeChrome, PortRow, PositionPanel, PreviewPanel, RailTabs, RunButton, SignalBar, TOKEN_NAMES, ToastProvider, ToolCluster, Toolbar, ToolbarGhostButton, ToolbarStat, UndoRedo, connectableCandidates, createHlangFlowTypes, createTheme, deriveParamSchema, descriptorCandidates, fuzzyRank, fuzzyScore, isEditableTarget, searchNodes, tokenVar, useEdgeFlowing, useEditorHotkeys, useLiveSignalStore, useLiveSignals, useNodeContextMenu, useNodeInteraction, useNodeLiveState, useNodeParams, useNodeSearch, useNodeSearchTriggers, useToast };
1007
- export type { AssetGridProps, AssetItem, AssetNodeChromeProps, CanvasChromeProps, CanvasDockProps, CanvasMode, CheatsheetProps, ConnectableResult, ConnectionEnd, ContextMenuItem, DiagnosticBadgeProps, EdgeIndexEntry, EditorHotkeysOptions, EditorShellProps, EditorUIThemeProviderProps, EmptyStateProps, FuzzyCandidate, FuzzyMatch, FuzzyResult, HlangFlowTypes, InspectorPanelProps, LiveSignalProviderProps, LiveSignalStoreDeps, NodeBodySlotProps, NodeCandidate, NodeChromeProps, NodeContextMenuController, NodeContextMenuProps, NodeContextMenuState, NodeInteraction, NodeInteractionProviderProps, NodeLiveState, NodeParamsHandle, NodeSearchController, NodeSearchProps, NodeSearchResult, NodeSearchResultView, NodeSearchState, NodeSearchTriggerOptions, NodeSearchTriggers, OutlineTreeProps, ParamField, ParamFieldBase, ParamFormProps, PendingConnection, PillNodeChromeProps, PointerPosition, PortRowProps, PositionPanelProps, PreviewPanelProps, RailTab, RailTabsProps, RunButtonProps, ShortcutGroup, ShortcutRow, ThemeOverride, ToastApi, ToastProviderProps, TokenName, ToolClusterProps, ToolItem, ToolbarProps, TouchPositions, UndoRedoProps };
1272
+ /**
1273
+ * `RunTrace` — what a running graph did: a bounded log of every delivery and
1274
+ * error, and per-node statistics (messages in and out, errors, time spent,
1275
+ * the latest values). It is fed from an execution handle's `onDelivery` and
1276
+ * `onError` (see `useRunTrace`) and read by the run log and the inspector.
1277
+ *
1278
+ * Like `LiveSignalStore`, it records synchronously and notifies at most once
1279
+ * per frame, so a flow delivering thousands of messages a second costs one
1280
+ * render per frame. The log is a ring buffer: memory stays bounded however
1281
+ * long the run, while the statistics keep counting everything.
1282
+ */
1283
+
1284
+ /** How many log entries a trace keeps unless told otherwise. */
1285
+ declare const RUN_LOG_CAPACITY = 500;
1286
+ /** A message delivered on a connection. `at` is milliseconds since the run started. */
1287
+ interface RunDeliveryEntry {
1288
+ readonly kind: 'delivery';
1289
+ readonly seq: number;
1290
+ readonly at: number;
1291
+ readonly sourceNodeId: string;
1292
+ readonly sourcePortId: string;
1293
+ readonly targetNodeId: string;
1294
+ readonly targetPortId: string;
1295
+ readonly message: unknown;
1296
+ /** How long the receiving node's handlers took, in milliseconds. */
1297
+ readonly duration: number;
1298
+ }
1299
+ /** A node error. `at` is milliseconds since the run started. */
1300
+ interface RunErrorEntry {
1301
+ readonly kind: 'error';
1302
+ readonly seq: number;
1303
+ readonly at: number;
1304
+ readonly nodeId: string;
1305
+ readonly error: unknown;
1306
+ }
1307
+ /** An input that ended (RFC 0001): every connection into it closed and its messages done. */
1308
+ interface RunEndedEntry {
1309
+ readonly kind: 'ended';
1310
+ readonly seq: number;
1311
+ readonly at: number;
1312
+ readonly nodeId: string;
1313
+ readonly portId: string;
1314
+ }
1315
+ /** A node that finished (RFC 0001): its inputs ended and its outputs closed. */
1316
+ interface RunFinishedEntry {
1317
+ readonly kind: 'finished';
1318
+ readonly seq: number;
1319
+ readonly at: number;
1320
+ readonly nodeId: string;
1321
+ }
1322
+ /** A message a full `drop-oldest` connection dropped (RFC 0001). */
1323
+ interface RunDroppedEntry {
1324
+ readonly kind: 'dropped';
1325
+ readonly seq: number;
1326
+ readonly at: number;
1327
+ readonly sourceNodeId: string;
1328
+ readonly sourcePortId: string;
1329
+ readonly targetNodeId: string;
1330
+ readonly targetPortId: string;
1331
+ readonly message: unknown;
1332
+ }
1333
+ type RunLogEntry = RunDeliveryEntry | RunErrorEntry | RunEndedEntry | RunFinishedEntry | RunDroppedEntry;
1334
+ /** One node's activity during the run. */
1335
+ interface NodeRunSummary {
1336
+ readonly received: number;
1337
+ readonly sent: number;
1338
+ readonly errors: number;
1339
+ /** Time spent handling what it received, in milliseconds: in total and the longest once. */
1340
+ readonly totalMs: number;
1341
+ readonly maxMs: number;
1342
+ readonly lastReceived: unknown;
1343
+ readonly lastSent: unknown;
1344
+ /** Messages a full `drop-oldest` connection into this node dropped. */
1345
+ readonly dropped: number;
1346
+ /** Whether the node has finished: its inputs ended and its outputs closed. */
1347
+ readonly finished: boolean;
1348
+ }
1349
+ interface RunTraceOptions {
1350
+ /** How many log entries to keep. Default `RUN_LOG_CAPACITY`. */
1351
+ readonly capacity?: number;
1352
+ /** Clock and frame scheduler; injected in tests. */
1353
+ readonly deps?: LiveSignalStoreDeps;
1354
+ }
1355
+ type Listener = () => void;
1356
+ declare class RunTrace {
1357
+ private readonly capacity;
1358
+ private readonly deps;
1359
+ /** The log as a ring: `ring[(head + i) % capacity]` is the i-th oldest entry. */
1360
+ private readonly ring;
1361
+ private head;
1362
+ private droppedCount;
1363
+ private seq;
1364
+ private startedAt;
1365
+ private readonly stats;
1366
+ private readonly listeners;
1367
+ /** Bumped by every change; the entries cache follows it. */
1368
+ private revision;
1369
+ /** Bumped once per notified frame; what subscribers compare. */
1370
+ private version;
1371
+ private frame;
1372
+ /** The entries as an array, rebuilt only after a change. */
1373
+ private entriesCache;
1374
+ private entriesRevision;
1375
+ constructor(options?: RunTraceOptions);
1376
+ /** Begins a run: clears everything and times entries from now. */
1377
+ start(): void;
1378
+ /** Forgets the log and the statistics. */
1379
+ clear(): void;
1380
+ recordDelivery(event: INodeDeliveryEvent): void;
1381
+ recordError(event: INodeErrorEvent): void;
1382
+ recordEnded(event: INodeEndedEvent): void;
1383
+ recordFinished(event: INodeFinishedEvent): void;
1384
+ recordDropped(event: INodeDroppedEvent): void;
1385
+ /** The log, oldest first. The same array is returned until something changes. */
1386
+ entries(): readonly RunLogEntry[];
1387
+ /** A node's statistics, or `undefined` when it has done nothing this run. */
1388
+ statsOf(nodeId: string): NodeRunSummary | undefined;
1389
+ /** How many entries the log has let go of to stay within its capacity. */
1390
+ get dropped(): number;
1391
+ /** Changes once per notified frame; a snapshot for `useSyncExternalStore`. */
1392
+ getVersion(): number;
1393
+ subscribe(listener: Listener): () => void;
1394
+ private append;
1395
+ private nextSeq;
1396
+ private elapsed;
1397
+ private statsFor;
1398
+ /** Schedules one notification for the next frame, however many changes come before it. */
1399
+ private changed;
1400
+ }
1401
+ /** Whether `entry` concerns `nodeId`: either end of a delivery, or the node of an error. */
1402
+ declare function involves(entry: RunLogEntry, nodeId: string): boolean;
1403
+
1404
+ /**
1405
+ * React glue for `RunTrace`. A host provides one trace for the editor; the run
1406
+ * log and the inspector read it through `useRunTraceVersion`, which changes at
1407
+ * most once per frame, so a busy flow costs one render per frame.
1408
+ */
1409
+
1410
+ interface RunTraceProviderProps {
1411
+ readonly trace: RunTrace;
1412
+ readonly children: ReactNode;
1413
+ }
1414
+ declare function RunTraceProvider({ trace, children }: RunTraceProviderProps): ReactElement;
1415
+ /** The trace, or `null` when there is no provider (run views stay hidden). */
1416
+ declare function useRunTraceStore(): RunTrace | null;
1417
+ /** Re-renders the caller when the trace changes; returns its version (0 without a trace). */
1418
+ declare function useRunTraceVersion(trace: RunTrace | null): number;
1419
+
1420
+ /**
1421
+ * `useRunTrace` — feeds a run's deliveries and errors into a `RunTrace`. Each
1422
+ * new handle starts a new run in the trace; when the handle goes away (the
1423
+ * run stopped) the trace keeps what it recorded, so the log can still be read.
1424
+ * A handle that cannot trace (no `onDelivery`) still feeds its errors.
1425
+ */
1426
+
1427
+ declare function useRunTrace(trace: RunTrace, handle: IExecutionHandle | undefined): void;
1428
+
1429
+ /**
1430
+ * `useFollowScroll` — a scrolling list that stays on its newest entry, as a
1431
+ * terminal does. While following, every render scrolls it to the bottom. When
1432
+ * the reader scrolls back to read, following stops so the view holds still;
1433
+ * scrolling back to the bottom (or `setFollow(true)`) resumes it.
1434
+ */
1435
+
1436
+ /** Within this many pixels of the bottom, the list counts as at the bottom. */
1437
+ declare const FOLLOW_SLACK_PX = 24;
1438
+ interface FollowScroll<T extends HTMLElement> {
1439
+ /** Attach to the scrolling element. */
1440
+ readonly ref: RefObject<T | null>;
1441
+ /** Attach as the scrolling element's `onScroll`. */
1442
+ readonly onScroll: (event: UIEvent<T>) => void;
1443
+ readonly follow: boolean;
1444
+ readonly setFollow: (follow: boolean) => void;
1445
+ }
1446
+ declare function useFollowScroll<T extends HTMLElement>(): FollowScroll<T>;
1447
+
1448
+ /**
1449
+ * `RunLog` — the editor's live console. Every message the running graph
1450
+ * delivers, and every node error, as it happens: when (since the run
1451
+ * started), from which node and port to which, a preview of the value, and
1452
+ * how long the receiving node took. Choosing a row selects its node, and the
1453
+ * log can follow the selection. It reads the `RunTrace` from
1454
+ * `<RunTraceProvider>` and renders nothing without one.
1455
+ */
1456
+
1457
+ interface RunLogProps {
1458
+ readonly core: EditorCore;
1459
+ /** Also called when a row is chosen, after its node is selected (e.g. to bring it into view). */
1460
+ readonly onSelectNode?: (nodeId: string) => void;
1461
+ /** The log's name, shown at the start of its header and used as its accessible name. */
1462
+ readonly title?: string;
1463
+ /** Shown at the start of the header in place of the title, e.g. the host's tabs. */
1464
+ readonly leading?: ReactNode;
1465
+ }
1466
+ declare function RunLog({ core, onSelectNode, title, leading }: RunLogProps): ReactElement | null;
1467
+
1468
+ /**
1469
+ * `NodeRunStats` — what one node did in the current run: messages in and out,
1470
+ * errors, the average and longest time it spent on a message, and the latest
1471
+ * values. Shown in the inspector when the host provides a `RunTrace`.
1472
+ */
1473
+
1474
+ declare function NodeRunStats({ nodeId }: {
1475
+ readonly nodeId: string;
1476
+ }): ReactElement | null;
1477
+
1478
+ /**
1479
+ * `ConnectionInspector` — the inspector for one selected connection: where it
1480
+ * runs from and to, and its back-pressure (RFC 0001): whether it is bounded,
1481
+ * its capacity, and whether a full connection drops its oldest message.
1482
+ * Changes are undoable commands.
1483
+ */
1484
+
1485
+ declare function ConnectionInspector({ core, edgeId }: {
1486
+ readonly core: EditorCore;
1487
+ readonly edgeId: EdgeId;
1488
+ }): ReactElement | null;
1489
+
1490
+ /**
1491
+ * `useEdgeBounds` — one connection and its bounds (RFC 0001), kept current
1492
+ * as commands change it, with a setter that commits an undoable
1493
+ * `SetEdgeBoundsCommand`.
1494
+ */
1495
+
1496
+ interface EdgeBoundsHandle {
1497
+ /** The edge, or undefined if it no longer exists. */
1498
+ readonly edge: EditorEdge | undefined;
1499
+ /** Bound the connection, or unbound it, as one undoable step. */
1500
+ readonly setBounds: (bounds: EdgeBounds) => void;
1501
+ }
1502
+ declare function useEdgeBounds(core: EditorCore, edgeId: EdgeId | undefined): EdgeBoundsHandle;
1503
+
1504
+ /**
1505
+ * A connection's bounds (RFC 0001) as the editor shows and edits them: the
1506
+ * fields of the inspector's Connection section, the bounds a change to one of
1507
+ * them makes, and the label a bounded edge carries.
1508
+ */
1509
+
1510
+ /** The capacity a connection gets when it is first bounded: JavaFBP's production default. */
1511
+ declare const DEFAULT_EDGE_CAPACITY = 10;
1512
+ /** The inspector's fields for a connection with `bounds`. */
1513
+ declare function boundsFields(bounds: EdgeBounds): ParamField[];
1514
+ /** The bounds after the inspector's field `key` is set to `value`. */
1515
+ declare function nextBounds(bounds: EdgeBounds, key: string, value: ParamValue): EdgeBounds;
1516
+ /** A bounded edge's label, in FBP notation: `[4]`; `undefined` when unbounded. */
1517
+ declare function capacityLabel(bounds: EdgeBounds): string | undefined;
1518
+
1519
+ /**
1520
+ * `previewValue` — a short, single-line text for any value a node sends, for
1521
+ * the run log and the inspector. Messages are `unknown`: they can be huge,
1522
+ * cyclic, class instances, or objects whose getters throw, so the preview
1523
+ * never throws, never prints a function's source, and is cut to a limit.
1524
+ */
1525
+ /** The longest preview, in characters, unless a caller asks for another. */
1526
+ declare const PREVIEW_LENGTH = 120;
1527
+ /** A single-line preview of `value`, at most `maxLength` characters. */
1528
+ declare function previewValue(value: unknown, maxLength?: number): string;
1529
+
1530
+ /** Short, aligned text for the times the run views show. */
1531
+ /** How long a delivery took: `<0.01 ms`, `0.12 ms`, `4.6 ms`, `42 ms`, `1.52 s`. */
1532
+ declare function formatDuration(ms: number): string;
1533
+ /** When something happened, in seconds since the run started: `0.013s`. */
1534
+ declare function formatElapsed(ms: number): string;
1535
+
1536
+ /**
1537
+ * `LivePulse` — one flash on a node each time it runs during a live run. The
1538
+ * pulse count from the live store keys the element, so every new count
1539
+ * mounts a fresh element and its CSS animation plays once: the node flashes
1540
+ * exactly as often as it actually ran (per frame at most). Place it as the
1541
+ * first child of a node's rounded, positioned root.
1542
+ */
1543
+
1544
+ declare function LivePulse({ nodeId }: {
1545
+ readonly nodeId: string;
1546
+ }): ReactElement | null;
1547
+
1548
+ export { AssetGrid, AssetNodeChrome, CanvasChrome, CanvasDock, Cheatsheet, ClearIcon, ConnectionInspector, DEFAULT_EDGE_CAPACITY, DEFAULT_MIN_CANVAS_HEIGHT, DiagnosticBadge, EditorShell, EditorUIThemeProvider, EmptyState, FOLLOW_SLACK_PX, FOOTER_KEY_STEP, FilterIcon, FlowEdge, FollowIcon, FooterResizer, HlangNode, InspectorPanel, KIND_ROLE, KIND_TOKEN, LivePulse, LiveSignalProvider, LiveSignalStore, NodeBodySlot, NodeChrome, NodeContextMenu, NodeInteractionProvider, NodeRunStats, NodeSearch, OutlineTree, PREVIEW_LENGTH, PanelAction, PanelHead, ParamForm, PillNodeChrome, PortRow, PositionPanel, PreviewPanel, RUN_LOG_CAPACITY, RailTabs, ReadOnlyBadge, RunButton, RunLog, RunTrace, RunTraceProvider, SELF_LOOP_CLEARANCE, SELF_LOOP_RADIUS, SignalBar, TOKEN_NAMES, ToastProvider, ToolCluster, Toolbar, ToolbarGhostButton, ToolbarStat, UndoRedo, boundsFields, capacityLabel, clampFooter, connectableCandidates, createHlangFlowTypes, createTheme, defaultLiveDeps, deriveParamSchema, descriptorCandidates, footerBounds, formatDuration, formatElapsed, fuzzyRank, fuzzyScore, involves, isEditableTarget, nextBounds, previewValue, resizeByKey, searchNodes, selfLoopPath, toggleCollapsed, tokenVar, useEdgeBounds, useEdgeFlowing, useEdgePulse, useEditorHotkeys, useFollowScroll, useFooterResize, useLiveSignalStore, useLiveSignals, useNodeContextMenu, useNodeInteraction, useNodeLiveState, useNodeParams, useNodePulse, useNodeSearch, useNodeSearchTriggers, useRunTrace, useRunTraceStore, useRunTraceVersion, useToast };
1549
+ export type { AssetGridProps, AssetItem, AssetNodeChromeProps, CanvasChromeProps, CanvasDockProps, CanvasMode, CheatsheetProps, ConnectableResult, ConnectionEnd, ContextMenuItem, DeliverySignal, DiagnosticBadgeProps, EdgeBoundsHandle, EdgeIndexEntry, EditorHotkeysOptions, EditorShellProps, EditorUIThemeProviderProps, EmptyStateProps, FollowScroll, FooterBounds, FooterResize, FooterResizeOptions, FooterResizerProps, FooterSize, FuzzyCandidate, FuzzyMatch, FuzzyResult, HlangFlowTypes, InspectorPanelProps, LiveSignalProviderProps, LiveSignalStoreDeps, NodeBodySlotProps, NodeCandidate, NodeChromeProps, NodeContextMenuController, NodeContextMenuProps, NodeContextMenuState, NodeInteraction, NodeInteractionProviderProps, NodeLiveState, NodeParamsHandle, NodeRunSummary, NodeSearchController, NodeSearchProps, NodeSearchResult, NodeSearchResultView, NodeSearchState, NodeSearchTriggerOptions, NodeSearchTriggers, OutlineTreeProps, PanelActionProps, PanelHeadProps, ParamField, ParamFieldBase, ParamFormProps, PendingConnection, PillNodeChromeProps, PointerPosition, PortRowProps, PositionPanelProps, PreviewPanelProps, RailTab, RailTabsProps, ReadOnlyBadgeProps, RunButtonProps, RunDeliveryEntry, RunErrorEntry, RunLogEntry, RunLogProps, RunTraceOptions, RunTraceProviderProps, SelfLoop, SelfLoopInput, ShortcutGroup, ShortcutRow, ThemeOverride, ToastApi, ToastProviderProps, TokenName, ToolClusterProps, ToolItem, ToolbarProps, TouchPositions, UndoRedoProps };