@you-agent-factory/factory-visualizers 0.0.2 → 0.0.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.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
- import { Text, Button, Heading, FactoryEmulatorControls as FactoryEmulatorControls$1 } from "@you-agent-factory/components";
3
- import { Component, useId, useRef, useState, useEffect, useMemo } from "react";
4
- import { ReactFlow, Background, Controls } from "@xyflow/react";
5
- import { safeParseFactoryVisualizationLayout, safeParseFactoryRecording } from "@you-agent-factory/client";
6
- import { GraphNodeShell, GraphNodeButton } from "@you-agent-factory/components/graphs";
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { FactoryEmulatorControls as FactoryEmulatorControls$1 } from "@you-agent-factory/components/factory-emulator";
3
+ import { Text, Button, Heading } from "@you-agent-factory/components/primitives";
4
+ import { Component, useId, useMemo, useRef, useEffect, useState } from "react";
5
+ import { FactoryGraphReplaySurface, createFactoryGraphSource } from "@you-agent-factory/factory-graph";
6
+ import { safeParseFactoryRecording } from "@you-agent-factory/client";
7
7
  import { canonicalizeFactoryEvents, projectFactoryTopologyAtTick, projectFactoryWorkProgressAtTick, projectFactoryLoadAtTick, projectFactoryActivityAtTick } from "@you-agent-factory/factory-replay";
8
8
  const ERROR_MESSAGES = {
9
9
  endpoint: "The prepared topology contains invalid edge endpoints.",
@@ -248,445 +248,6 @@ function FactoryEmulatorControls({
248
248
  }
249
249
  );
250
250
  }
251
- const VISIBLE_ACTIVE_WORK_ROWS = 3;
252
- function projectFactoryTopologyActiveWork(activity) {
253
- const durationByWorkId = /* @__PURE__ */ new Map();
254
- for (const overlay of activity.activeDispatchOverlays) {
255
- const durationTicks = Math.max(
256
- 0,
257
- activity.selectedTick - overlay.startedTick
258
- );
259
- for (const workId of overlay.workIds ?? []) {
260
- const previousDuration = durationByWorkId.get(workId);
261
- if (previousDuration === void 0 || durationTicks > previousDuration)
262
- durationByWorkId.set(workId, durationTicks);
263
- }
264
- }
265
- const rows = [...durationByWorkId].sort(([left], [right]) => left.localeCompare(right)).map(([id, durationTicks]) => ({ durationTicks, id }));
266
- return {
267
- overflowCount: Math.max(0, rows.length - VISIBLE_ACTIVE_WORK_ROWS),
268
- rows: rows.slice(0, VISIBLE_ACTIVE_WORK_ROWS)
269
- };
270
- }
271
- const DEFAULT_FACTORY_TOPOLOGY_CHROME_PRESET = "full";
272
- const FACTORY_TOPOLOGY_CHROME_PRESETS = {
273
- full: {
274
- background: true,
275
- legend: true,
276
- viewportControls: true,
277
- visibilityControls: true
278
- },
279
- minimal: {
280
- background: true,
281
- legend: false,
282
- viewportControls: true,
283
- visibilityControls: false
284
- },
285
- none: {
286
- background: false,
287
- legend: false,
288
- viewportControls: false,
289
- visibilityControls: false
290
- }
291
- };
292
- function resolveFactoryTopologyChrome(configuration = {}) {
293
- const preset = configuration.preset ?? DEFAULT_FACTORY_TOPOLOGY_CHROME_PRESET;
294
- const resolvedPreset = FACTORY_TOPOLOGY_CHROME_PRESETS[preset];
295
- return {
296
- background: configuration.background ?? resolvedPreset.background,
297
- legend: configuration.legend ?? resolvedPreset.legend,
298
- viewportControls: configuration.viewportControls ?? resolvedPreset.viewportControls,
299
- visibilityControls: configuration.visibilityControls ?? resolvedPreset.visibilityControls
300
- };
301
- }
302
- const columnByKind = {
303
- resource: 0,
304
- worker: 1,
305
- "work-type": 2,
306
- "work-state": 3,
307
- workstation: 4
308
- };
309
- const DEFAULT_ANNOTATION_WIDTH = 240;
310
- function projectFactoryTopologyFlow(projection, messages, selectedNodeId, onSelectNode, prefersReducedMotion = false, layout) {
311
- try {
312
- const { connections, nodes: topologyNodes } = projection.topology;
313
- const nodeById = new Map(topologyNodes.map((node) => [node.id, node]));
314
- const validEndpoints = connections.every(
315
- (connection) => connectionHasRenderedEndpoints(connection, nodeById)
316
- );
317
- const nodeData = nodePresentationData(projection, connections, layout);
318
- return {
319
- edges: validEndpoints ? projectEdges(connections, projection.activity, prefersReducedMotion) : [],
320
- nodes: [
321
- ...projectTopologyNodes(
322
- topologyNodes,
323
- messages,
324
- selectedNodeId,
325
- onSelectNode,
326
- nodeData
327
- ),
328
- ...projectAnnotations(layout, messages)
329
- ],
330
- validEndpoints
331
- };
332
- } catch (error) {
333
- if (error instanceof FactoryVisualizerInternalError) throw error;
334
- throw new FactoryVisualizerInternalError("projection", error);
335
- }
336
- }
337
- function nodePresentationData(projection, connections, layout) {
338
- return {
339
- activeDetailNodeIds: activityDetailNodeIds(
340
- projection.activity,
341
- connections,
342
- projection.load.resourceOccupancy,
343
- projection.load.workStateCounts
344
- ),
345
- activityCountByNode: activityCounts(projection.activity),
346
- emptyStateByNode: new Map(
347
- (layout?.nodeEmptyStates ?? []).map((state) => [
348
- state.nodeId,
349
- state.content
350
- ])
351
- ),
352
- occupancyByNode: new Map(
353
- projection.load.resourceOccupancy.map((occupancy) => [
354
- occupancy.resourceNodeId,
355
- occupancy
356
- ])
357
- ),
358
- workStateCountByNode: new Map(
359
- projection.load.workStateCounts.map((count) => [
360
- count.workStateNodeId,
361
- count
362
- ])
363
- )
364
- };
365
- }
366
- function projectTopologyNodes(topologyNodes, messages, selectedNodeId, onSelectNode, data) {
367
- const rowByKind = /* @__PURE__ */ new Map();
368
- return topologyNodes.map((node) => {
369
- const row = rowByKind.get(node.kind) ?? 0;
370
- rowByKind.set(node.kind, row + 1);
371
- const occupancy = data.occupancyByNode.get(node.id);
372
- const workStateCount = data.workStateCountByNode.get(node.id);
373
- return {
374
- data: {
375
- activityCount: data.activityCountByNode.get(node.id) ?? 0,
376
- ...data.activeDetailNodeIds.has(node.id) ? {} : { emptyState: data.emptyStateByNode.get(node.id) },
377
- messages,
378
- node,
379
- ...occupancy ? {
380
- occupancy: {
381
- capacity: occupancy.capacity,
382
- evidence: occupancy.evidence,
383
- occupied: occupancy.occupiedQuantity
384
- }
385
- } : {},
386
- onSelectNode,
387
- selected: selectedNodeId === node.id,
388
- ...workStateCount ? {
389
- workStateCount: {
390
- count: workStateCount.count,
391
- evidence: workStateCount.evidence
392
- }
393
- } : {}
394
- },
395
- draggable: false,
396
- id: node.id,
397
- position: layoutNode(node.kind, row),
398
- selectable: false,
399
- type: "factoryTopologyNode"
400
- };
401
- });
402
- }
403
- function projectAnnotations(layout, messages) {
404
- return (layout?.annotations ?? []).map((annotation) => ({
405
- data: { annotation, messages },
406
- draggable: false,
407
- id: `annotation:${annotation.id}`,
408
- position: annotation.position,
409
- selectable: false,
410
- type: "factoryTopologyAnnotation",
411
- style: {
412
- ...annotation.size ? { height: annotation.size.height } : {},
413
- width: annotation.size?.width ?? DEFAULT_ANNOTATION_WIDTH
414
- }
415
- }));
416
- }
417
- function projectEdges(connections, activity, prefersReducedMotion) {
418
- return connections.map((connection) => ({
419
- animated: !prefersReducedMotion && activity.activeDispatchOverlays.some(
420
- (overlay) => overlay.connectionIds.includes(connection.id)
421
- ),
422
- data: { relationship: connection.kind },
423
- id: connection.id,
424
- source: connection.source.nodeId,
425
- sourceHandle: connection.source.handleId,
426
- target: connection.target.nodeId,
427
- targetHandle: connection.target.handleId
428
- }));
429
- }
430
- function connectionHasRenderedEndpoints(connection, nodeById) {
431
- const source = nodeById.get(connection.source.nodeId);
432
- const target = nodeById.get(connection.target.nodeId);
433
- return Boolean(
434
- source?.handles.some(
435
- (handle) => handle.id === connection.source.handleId && handle.role === "source"
436
- ) && target?.handles.some(
437
- (handle) => handle.id === connection.target.handleId && handle.role === "target"
438
- )
439
- );
440
- }
441
- function activityCounts(activity) {
442
- const counts = /* @__PURE__ */ new Map();
443
- for (const overlay of activity.activeDispatchOverlays) {
444
- for (const nodeId of /* @__PURE__ */ new Set([
445
- overlay.workerNodeId,
446
- overlay.workstationNodeId,
447
- ...overlay.resourceNodeIds ?? []
448
- ])) {
449
- if (nodeId) counts.set(nodeId, (counts.get(nodeId) ?? 0) + 1);
450
- }
451
- }
452
- return counts;
453
- }
454
- function activityDetailNodeIds(activity, connections, resourceOccupancy, workStateCounts) {
455
- const nodeIds = new Set(activity.activeWorkstationNodeIds);
456
- const connectionById = new Map(
457
- connections.map((connection) => [connection.id, connection])
458
- );
459
- for (const occupancy of resourceOccupancy)
460
- if (occupancy.evidence === "known") nodeIds.add(occupancy.resourceNodeId);
461
- for (const state of workStateCounts)
462
- if (state.evidence === "known" && typeof state.count === "number" && state.count > 0)
463
- nodeIds.add(state.workStateNodeId);
464
- for (const overlay of activity.activeDispatchOverlays) {
465
- for (const nodeId of [
466
- overlay.workerNodeId,
467
- overlay.workstationNodeId,
468
- ...overlay.resourceNodeIds ?? []
469
- ])
470
- if (nodeId) nodeIds.add(nodeId);
471
- for (const connectionId of overlay.connectionIds) {
472
- const connection = connectionById.get(connectionId);
473
- if (connection) {
474
- nodeIds.add(connection.source.nodeId);
475
- nodeIds.add(connection.target.nodeId);
476
- }
477
- }
478
- }
479
- return nodeIds;
480
- }
481
- function layoutNode(kind, row) {
482
- const column = columnByKind[kind];
483
- if (column === void 0 || !Number.isSafeInteger(row) || row < 0)
484
- throw new FactoryVisualizerInternalError("layout");
485
- return { x: column * 260, y: row * 170 };
486
- }
487
- const nodeTypes = {
488
- factoryTopologyAnnotation: FactoryTopologyAnnotationView,
489
- factoryTopologyNode: FactoryTopologyNodeView
490
- };
491
- function FactoryTopologyAnnotationView({
492
- data
493
- }) {
494
- const { annotation, messages } = data;
495
- if (annotation.kind === "image") {
496
- return /* @__PURE__ */ jsx(
497
- FactoryTopologyAnnotationImage,
498
- {
499
- annotation,
500
- messages
501
- }
502
- );
503
- }
504
- return /* @__PURE__ */ jsxs(
505
- "aside",
506
- {
507
- className: "factory-topology-replay__annotation",
508
- "data-tone": annotation.tone ?? "neutral",
509
- children: [
510
- annotation.title ? /* @__PURE__ */ jsx("strong", { className: "factory-topology-replay__annotation-title", children: annotation.title }) : null,
511
- /* @__PURE__ */ jsx("span", { className: "factory-topology-replay__annotation-body", children: annotation.body })
512
- ]
513
- }
514
- );
515
- }
516
- function FactoryTopologyAnnotationImage({
517
- annotation,
518
- messages
519
- }) {
520
- const image = useEmbeddedImageUrl(annotation.source);
521
- return /* @__PURE__ */ jsx("figure", { className: "factory-topology-replay__annotation factory-topology-replay__annotation--image", children: image.status === "ready" ? /* @__PURE__ */ jsx(
522
- "img",
523
- {
524
- alt: annotation.altText,
525
- className: "factory-topology-replay__annotation-image",
526
- onError: image.fail,
527
- src: image.url
528
- }
529
- ) : /* @__PURE__ */ jsxs(
530
- "div",
531
- {
532
- className: "factory-topology-replay__annotation-image-state",
533
- role: image.status === "failed" ? "alert" : "status",
534
- children: [
535
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: annotation.altText }),
536
- image.status === "failed" ? messages.imageFailed : messages.imageLoading
537
- ]
538
- }
539
- ) });
540
- }
541
- function FactoryTopologyNodeView({ data }) {
542
- const {
543
- activityCount,
544
- emptyState,
545
- messages,
546
- node,
547
- occupancy,
548
- onSelectNode,
549
- selected,
550
- workStateCount
551
- } = data;
552
- const state = selected ? "selected" : "default";
553
- const handles = node.handles.map((handle) => ({
554
- connectable: false,
555
- id: handle.id,
556
- label: handle.id,
557
- side: handle.role === "target" ? "left" : "right",
558
- type: handle.role
559
- }));
560
- const content = /* @__PURE__ */ jsxs(
561
- GraphNodeShell,
562
- {
563
- className: activityCount > 0 ? "factory-topology-replay__node--active" : "",
564
- "data-dispatch-activity": activityCount > 0 ? "active" : "inactive",
565
- handles,
566
- nodeKind: node.kind,
567
- showStateIndicator: false,
568
- state,
569
- children: [
570
- /* @__PURE__ */ jsx("strong", { className: "factory-topology-replay__node-title", children: node.label }),
571
- /* @__PURE__ */ jsx("span", { className: "factory-topology-replay__node-kind", children: node.kind }),
572
- /* @__PURE__ */ jsx("div", { className: "factory-topology-replay__node-activity-detail", children: emptyState ? /* @__PURE__ */ jsx(
573
- FactoryTopologyNodeEmptyStateView,
574
- {
575
- content: emptyState,
576
- messages
577
- }
578
- ) : /* @__PURE__ */ jsxs("span", { className: "factory-topology-replay__node-cue", children: [
579
- activityCount > 0 ? "●" : "○",
580
- " ",
581
- activityCount > 0 ? messages.activeDispatches(activityCount) : messages.inactiveDispatches
582
- ] }) }),
583
- node.kind === "resource" ? /* @__PURE__ */ jsxs("span", { className: "factory-topology-replay__node-cue", children: [
584
- "◫",
585
- " ",
586
- occupancy?.evidence === "known" && occupancy.occupied !== void 0 && occupancy.capacity !== void 0 ? messages.resourceOccupancy(occupancy.occupied, occupancy.capacity) : messages.resourceOccupancyUnavailable
587
- ] }) : null,
588
- node.kind === "work-state" ? /* @__PURE__ */ jsxs("span", { className: "factory-topology-replay__node-cue", children: [
589
- "∑",
590
- " ",
591
- workStateCount?.evidence === "known" && workStateCount.count !== void 0 ? messages.workStateCount(workStateCount.count) : messages.workStateCountUnavailable
592
- ] }) : null,
593
- selected ? /* @__PURE__ */ jsxs("span", { className: "factory-topology-replay__node-cue", children: [
594
- "✓ ",
595
- messages.selectedNode
596
- ] }) : null
597
- ]
598
- }
599
- );
600
- return onSelectNode ? /* @__PURE__ */ jsx(
601
- GraphNodeButton,
602
- {
603
- "aria-label": messages.nodeLabel(node.kind, node.label),
604
- className: "factory-topology-replay__node-button",
605
- graphState: state,
606
- onClick: () => onSelectNode(node),
607
- children: content
608
- }
609
- ) : /* @__PURE__ */ jsx(
610
- "figure",
611
- {
612
- "aria-label": messages.nodeLabel(node.kind, node.label),
613
- className: "factory-topology-replay__node-static",
614
- children: content
615
- }
616
- );
617
- }
618
- function FactoryTopologyNodeEmptyStateView({
619
- content,
620
- messages
621
- }) {
622
- return content.kind === "image" ? /* @__PURE__ */ jsx(FactoryTopologyEmptyStateImage, { content, messages }) : /* @__PURE__ */ jsx("span", { className: "factory-topology-replay__node-empty-state", children: content.text });
623
- }
624
- function FactoryTopologyEmptyStateImage({
625
- content,
626
- messages
627
- }) {
628
- const image = useEmbeddedImageUrl(content.source);
629
- return image.status === "ready" ? /* @__PURE__ */ jsx(
630
- "img",
631
- {
632
- alt: content.altText,
633
- className: "factory-topology-replay__node-empty-state-image",
634
- onError: image.fail,
635
- src: image.url
636
- }
637
- ) : /* @__PURE__ */ jsxs(
638
- "span",
639
- {
640
- className: "factory-topology-replay__node-empty-state",
641
- role: image.status === "failed" ? "alert" : "status",
642
- children: [
643
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: content.altText }),
644
- image.status === "failed" ? messages.imageFailed : messages.imageLoading
645
- ]
646
- }
647
- );
648
- }
649
- function useEmbeddedImageUrl(source) {
650
- const urlRef = useRef(void 0);
651
- const [state, setState] = useState({ status: "loading" });
652
- useEffect(() => {
653
- try {
654
- const url = URL.createObjectURL(
655
- new Blob([decodeEmbeddedImage(source.base64)], {
656
- type: source.mediaType
657
- })
658
- );
659
- urlRef.current = url;
660
- setState({ status: "ready", url });
661
- return () => {
662
- if (urlRef.current === url) {
663
- URL.revokeObjectURL(url);
664
- urlRef.current = void 0;
665
- }
666
- };
667
- } catch {
668
- setState({ status: "failed" });
669
- return void 0;
670
- }
671
- }, [source]);
672
- return {
673
- fail: () => {
674
- if (urlRef.current) {
675
- URL.revokeObjectURL(urlRef.current);
676
- urlRef.current = void 0;
677
- }
678
- setState({ status: "failed" });
679
- },
680
- ...state
681
- };
682
- }
683
- function decodeEmbeddedImage(base64) {
684
- const decoded = atob(base64);
685
- const bytes = new Uint8Array(decoded.length);
686
- for (let index = 0; index < decoded.length; index += 1)
687
- bytes[index] = decoded.charCodeAt(index);
688
- return bytes.buffer;
689
- }
690
251
  function FactoryTopologyStateRegion({
691
252
  messages,
692
253
  onRetry,
@@ -771,18 +332,6 @@ class FactoryTopologyErrorBoundary extends Component {
771
332
  this.props.onError?.(error);
772
333
  }
773
334
  }
774
- function useDistinctTopologyErrorReport(error, onError) {
775
- const reportedErrors = useRef(/* @__PURE__ */ new Set());
776
- useEffect(() => {
777
- if (!error) return;
778
- const key = error.kind === "layout-validation" ? error.issues.map(
779
- (issue) => `${issue.category}:${issue.code}:${issue.path.join(".")}`
780
- ).join("|") : factoryVisualizerErrorKey(error);
781
- if (reportedErrors.current.has(key)) return;
782
- reportedErrors.current.add(key);
783
- onError?.(error);
784
- }, [error, onError]);
785
- }
786
335
  function resetKeysChanged(previous, current) {
787
336
  return previous.length !== current.length || previous.some((value, index) => value !== current[index]);
788
337
  }
@@ -800,10 +349,7 @@ function FactoryTopologyReplay(props) {
800
349
  );
801
350
  }
802
351
  function FactoryTopologyReplayContent({
803
- chrome,
804
352
  messages,
805
- onError,
806
- layout,
807
353
  onRetry,
808
354
  onSelectNode,
809
355
  selectedNodeId,
@@ -814,93 +360,8 @@ function FactoryTopologyReplayContent({
814
360
  FactoryTopologyStateRegion,
815
361
  {
816
362
  messages,
817
- state: state.status,
818
- onRetry
819
- }
820
- );
821
- }
822
- return /* @__PURE__ */ jsx(
823
- PreparedTopology,
824
- {
825
- chrome,
826
- messages,
827
- onError,
828
- onRetry,
829
- onSelectNode,
830
- layout,
831
- projection: state.projection,
832
- selectedNodeId
833
- }
834
- );
835
- }
836
- function PreparedTopology({
837
- chrome,
838
- messages,
839
- onError,
840
- layout,
841
- onRetry,
842
- onSelectNode,
843
- projection,
844
- selectedNodeId
845
- }) {
846
- const prefersReducedMotion = usePrefersReducedMotion();
847
- const resolvedChrome = resolveFactoryTopologyChrome(chrome);
848
- const prepared = useMemo(() => {
849
- try {
850
- const parsedLayout = layout === void 0 ? void 0 : safeParseFactoryVisualizationLayout(layout, {
851
- canonicalNodeIds: new Set(
852
- projection.topology.nodes.map((node) => node.id)
853
- )
854
- });
855
- if (parsedLayout && !parsedLayout.success) {
856
- return {
857
- error: {
858
- issues: parsedLayout.issues.map(({ category, code, path }) => ({
859
- category,
860
- code,
861
- path
862
- })),
863
- kind: "layout-validation",
864
- message: "The topology layout could not be prepared.",
865
- recoverable: true
866
- },
867
- status: "failed"
868
- };
869
- }
870
- const flow = projectFactoryTopologyFlow(
871
- projection,
872
- messages,
873
- selectedNodeId,
874
- onSelectNode,
875
- prefersReducedMotion,
876
- parsedLayout?.data
877
- );
878
- return flow.validEndpoints ? { flow, status: "ready" } : { error: toFactoryVisualizerError("endpoint"), status: "failed" };
879
- } catch (error) {
880
- return {
881
- error: normalizeFactoryVisualizerError(error, "projection"),
882
- status: "failed"
883
- };
884
- }
885
- }, [
886
- messages,
887
- onSelectNode,
888
- prefersReducedMotion,
889
- projection,
890
- layout,
891
- selectedNodeId
892
- ]);
893
- useDistinctTopologyErrorReport(
894
- prepared.status === "failed" ? prepared.error : void 0,
895
- onError
896
- );
897
- if (prepared.status === "failed") {
898
- return /* @__PURE__ */ jsx(
899
- FactoryTopologyStateRegion,
900
- {
901
- messages,
902
- state: "failed",
903
- onRetry
363
+ onRetry,
364
+ state: state.status
904
365
  }
905
366
  );
906
367
  }
@@ -909,156 +370,22 @@ function PreparedTopology({
909
370
  {
910
371
  "aria-label": messages.regionLabel,
911
372
  className: "factory-topology-replay",
912
- "data-endpoints-valid": "true",
913
- "data-reduced-motion": prefersReducedMotion ? "true" : "false",
914
373
  children: /* @__PURE__ */ jsx(
915
- FactoryTopologyErrorBoundary,
374
+ FactoryGraphReplaySurface,
916
375
  {
917
- errorKind: "react-flow",
918
- messages,
919
- onError,
920
- onRetry,
921
- resetKeys: [projection, messages, selectedNodeId, onSelectNode, layout],
922
- withinRegion: true,
923
- children: /* @__PURE__ */ jsx(
924
- ReactFlowCanvas,
925
- {
926
- activity: projection.activity,
927
- chrome: resolvedChrome,
928
- flow: prepared.flow,
929
- messages
930
- }
931
- )
376
+ onSelectNode: onSelectNode ? (nodeId) => {
377
+ const node = state.source.runtime.topology.nodes.find(
378
+ (entry) => entry.id === nodeId
379
+ );
380
+ if (node) onSelectNode(node);
381
+ } : void 0,
382
+ selectedNodeId,
383
+ source: state.source
932
384
  }
933
385
  )
934
386
  }
935
387
  );
936
388
  }
937
- function ReactFlowCanvas({
938
- activity,
939
- chrome,
940
- flow,
941
- messages
942
- }) {
943
- const chromeMessages = resolveTopologyChromeMessages(messages);
944
- const [annotationsVisible, setAnnotationsVisible] = useState(true);
945
- const visibleNodes = annotationsVisible ? flow.nodes : flow.nodes.filter((node) => node.type !== "factoryTopologyAnnotation");
946
- const hasAnnotations = flow.nodes.some(
947
- (node) => node.type === "factoryTopologyAnnotation"
948
- );
949
- return /* @__PURE__ */ jsxs(Fragment, { children: [
950
- /* @__PURE__ */ jsx(ActiveWorkSummary, { activity, messages }),
951
- chrome.legend ? /* @__PURE__ */ jsx(TopologyLegend, { messages: chromeMessages }) : null,
952
- chrome.visibilityControls && hasAnnotations ? /* @__PURE__ */ jsx(
953
- "button",
954
- {
955
- "aria-pressed": annotationsVisible,
956
- className: "factory-topology-replay__annotation-toggle",
957
- onClick: () => setAnnotationsVisible((visible) => !visible),
958
- type: "button",
959
- children: annotationsVisible ? messages.annotationsVisible : messages.annotationsHidden
960
- }
961
- ) : null,
962
- /* @__PURE__ */ jsxs(
963
- ReactFlow,
964
- {
965
- edges: flow.edges,
966
- edgesFocusable: false,
967
- elementsSelectable: false,
968
- fitView: true,
969
- fitViewOptions: { includeHiddenNodes: false },
970
- nodes: visibleNodes,
971
- nodesConnectable: false,
972
- nodesDraggable: false,
973
- nodeTypes,
974
- onNodeClick: preserveNestedNodePointerEvents,
975
- panOnDrag: true,
976
- proOptions: { hideAttribution: true },
977
- children: [
978
- chrome.background ? /* @__PURE__ */ jsx(Background, {}) : null,
979
- chrome.viewportControls ? /* @__PURE__ */ jsx(
980
- Controls,
981
- {
982
- "aria-label": chromeMessages.viewportControlsLabel,
983
- showInteractive: false
984
- }
985
- ) : null
986
- ]
987
- },
988
- annotationsVisible ? "annotations-visible" : "annotations-hidden"
989
- )
990
- ] });
991
- }
992
- function ActiveWorkSummary({
993
- activity,
994
- messages
995
- }) {
996
- const activeWork = projectFactoryTopologyActiveWork(activity);
997
- if (activeWork.rows.length === 0) return null;
998
- return /* @__PURE__ */ jsxs("fieldset", { className: "factory-topology-replay__active-work", children: [
999
- /* @__PURE__ */ jsx("legend", { children: messages.activeWorkRegionLabel ?? "Active Work" }),
1000
- /* @__PURE__ */ jsx("ul", { children: activeWork.rows.map((work) => /* @__PURE__ */ jsxs("li", { children: [
1001
- /* @__PURE__ */ jsx("span", { children: work.id }),
1002
- /* @__PURE__ */ jsx("span", { children: messages.activeWorkDuration?.(work.durationTicks) ?? `Active for ${work.durationTicks} ticks` })
1003
- ] }, work.id)) }),
1004
- activeWork.overflowCount > 0 ? /* @__PURE__ */ jsx("p", { children: messages.activeWorkOverflow?.(activeWork.overflowCount) ?? `${activeWork.overflowCount} more active Work` }) : null
1005
- ] });
1006
- }
1007
- function TopologyLegend({ messages }) {
1008
- return /* @__PURE__ */ jsxs("fieldset", { className: "factory-topology-replay__legend", children: [
1009
- /* @__PURE__ */ jsx("legend", { children: messages.legendLabel }),
1010
- /* @__PURE__ */ jsxs("ul", { children: [
1011
- /* @__PURE__ */ jsxs("li", { children: [
1012
- /* @__PURE__ */ jsx(
1013
- "span",
1014
- {
1015
- "aria-hidden": "true",
1016
- className: "factory-topology-replay__legend-swatch factory-topology-replay__legend-swatch--active"
1017
- }
1018
- ),
1019
- messages.legendActiveRoute
1020
- ] }),
1021
- /* @__PURE__ */ jsxs("li", { children: [
1022
- /* @__PURE__ */ jsx(
1023
- "span",
1024
- {
1025
- "aria-hidden": "true",
1026
- className: "factory-topology-replay__legend-swatch"
1027
- }
1028
- ),
1029
- messages.legendInactiveRoute
1030
- ] })
1031
- ] })
1032
- ] });
1033
- }
1034
- function resolveTopologyChromeMessages(messages) {
1035
- return {
1036
- legendActiveRoute: messages.legendActiveRoute ?? "Active route",
1037
- legendInactiveRoute: messages.legendInactiveRoute ?? "Inactive route",
1038
- legendLabel: messages.legendLabel ?? "Topology legend",
1039
- viewportControlsLabel: messages.viewportControlsLabel ?? "Topology viewport controls"
1040
- };
1041
- }
1042
- function preserveNestedNodePointerEvents() {
1043
- }
1044
- const REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
1045
- function usePrefersReducedMotion() {
1046
- const [prefersReducedMotion, setPrefersReducedMotion] = useState(
1047
- () => reducedMotionMediaQuery()?.matches ?? false
1048
- );
1049
- useEffect(() => {
1050
- const mediaQuery = reducedMotionMediaQuery();
1051
- if (!mediaQuery) return;
1052
- const updatePreference = () => setPrefersReducedMotion(mediaQuery.matches);
1053
- updatePreference();
1054
- mediaQuery.addEventListener("change", updatePreference);
1055
- return () => mediaQuery.removeEventListener("change", updatePreference);
1056
- }, []);
1057
- return prefersReducedMotion;
1058
- }
1059
- function reducedMotionMediaQuery() {
1060
- return typeof window === "undefined" || typeof window.matchMedia !== "function" ? void 0 : window.matchMedia(REDUCED_MOTION_QUERY);
1061
- }
1062
389
  const CATEGORY_PRESENTATION = {
1063
390
  queued: { cue: "○" },
1064
391
  active: { cue: "▶" },
@@ -1241,7 +568,6 @@ function createRecordingProjectionCache() {
1241
568
  function FactoryRecordingTopologyReplay({
1242
569
  defaultSelectedTick,
1243
570
  formatNumber,
1244
- layout,
1245
571
  messages,
1246
572
  onError,
1247
573
  onSelectNode,
@@ -1285,7 +611,6 @@ function FactoryRecordingTopologyReplay({
1285
611
  {
1286
612
  defaultSelectedTick,
1287
613
  formatNumber,
1288
- layout,
1289
614
  messages,
1290
615
  onError,
1291
616
  onSelectNode,
@@ -1298,7 +623,6 @@ function FactoryRecordingTopologyReplay({
1298
623
  function ValidatedRecordingReplay({
1299
624
  defaultSelectedTick,
1300
625
  formatNumber,
1301
- layout,
1302
626
  messages,
1303
627
  onError,
1304
628
  onSelectNode,
@@ -1364,20 +688,23 @@ function ValidatedRecordingReplay({
1364
688
  /* @__PURE__ */ jsx(
1365
689
  FactoryTopologyReplay,
1366
690
  {
1367
- layout,
1368
691
  messages: messages.topology,
1369
692
  onError,
1370
693
  onSelectNode,
1371
694
  selectedNodeId,
1372
695
  state: {
1373
- ...prepared.topology.nodes.length === 0 ? { status: "empty" } : {
1374
- projection: {
1375
- activity: prepared.activity,
1376
- load: prepared.load,
1377
- topology: prepared.topology
1378
- },
696
+ ...prepared.topology.nodes.length === 0 ? { status: "empty" } : recording.factory ? {
697
+ source: createFactoryGraphSource({
698
+ factory: recording.factory,
699
+ runtime: {
700
+ activity: prepared.activity,
701
+ load: prepared.load,
702
+ topology: prepared.topology
703
+ },
704
+ selectedTick
705
+ }),
1379
706
  status: "ready"
1380
- }
707
+ } : { status: "failed" }
1381
708
  }
1382
709
  }
1383
710
  ),
@@ -1457,13 +784,10 @@ function useDistinctVisualizerErrorReport(error, onError) {
1457
784
  }, [error, onError]);
1458
785
  }
1459
786
  export {
1460
- DEFAULT_FACTORY_TOPOLOGY_CHROME_PRESET,
1461
787
  FactoryEmulatorControls,
1462
788
  FactoryEmulatorView,
1463
789
  FactoryRecordingTopologyReplay,
1464
790
  FactoryTimelineScrubber,
1465
791
  FactoryTopologyReplay,
1466
- WorkProgressVisualizer,
1467
- projectFactoryTopologyFlow,
1468
- resolveFactoryTopologyChrome
792
+ WorkProgressVisualizer
1469
793
  };