# All files / src/components/streams MessageDotEdge.tsx

0% Statements 0/47
0% Branches 0/1
0% Functions 0/1
0% Lines 0/47

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68

     import { BaseEdge, getSmoothStepPath, type EdgeProps } from "@xyflow/react";
    import type { JSX } from "react";
    import type { CoordEdgeData } from "./coordinationGraphModel.js";
    import styles from "./CoordinationGraph.module.scss";

    /** Duration of a single message-dot traversal. */
    const DOT_DURATION: string = "0.7s";
    /** Radius of the message dot (pixels). */
    const DOT_RADIUS: number = 3;

    /**
     * Custom React Flow edge for the Coordination graph. Renders the same smoothstep
     * path as Phase A (via {@link BaseEdge}) and, when a new message arrives on the
     * edge's stream, fires a one-shot dot that travels source -> target.
     *
     * The dot is keyed on `data.pulseSeq` (the stream's latest message seq): when the
     * seq advances, the keyed `<circle>` remounts and the SMIL animation replays. The
     * dot rests at `opacity: 0` (base) with non-frozen SMIL, so it leaves no artifact
     * once the traversal completes.
     */
    export function MessageDotEdge(props: EdgeProps): JSX.Element {
      const {
        sourceX,
        sourceY,
        targetX,
        targetY,
        sourcePosition,
        targetPosition,
        markerStart,
        markerEnd,
        style,
        data,
      } = props;
      const [edgePath] = getSmoothStepPath({
        sourceX,
        sourceY,
        targetX,
        targetY,
        sourcePosition,
        targetPosition,
      });
      const pulseSeq = (data as CoordEdgeData | undefined)?.pulseSeq;

      return (
        <>
          <BaseEdge path={edgePath} markerStart={markerStart} markerEnd={markerEnd} style={style} />
          {pulseSeq !== undefined && (
            <circle
              key={pulseSeq}
              r={DOT_RADIUS}
              opacity={0}
              className={styles.messageDot}
              data-testid="coordination-message-dot"
            >
              <animateMotion dur={DOT_DURATION} repeatCount="1" path={edgePath} />
              <animate
                attributeName="opacity"
                dur={DOT_DURATION}
                repeatCount="1"
                values="0;1;1;0"
                keyTimes="0;0.1;0.85;1"
              />
            </circle>
          )}
        </>
      );
    }