Deadlock Graph Viz

The three production-grade failure modes that hang every distributed-training job, side by side. Pick a scenario (1 / 2 / 3), hit Play (or Space), and the storyboard auto-advances through running → waiting → blocked (or timeout) on a 2×2 grid of four ranks. Wait-for arrows trace the circular dependency once it forms; on the deadlock frame, the cycle edges pulse red and the grid glows. After the run settles, a fix card surfaces the one-line remediation.

Ready
0.0s
Rank 0
idle
Rank 1
idle
Rank 2
idle
Rank 3
idle
1-3 select scenario / Space to play

Pick a scenario and press Play to watch operation order unfold across four ranks.

Operation order scenario selected. Press Play or Space to start the animation.
Customize
Playback
order
2200ms

Installation

npx shadcn@latest add https://craftbits.dev/r/deadlock-graph-viz.json

Usage

import { DeadlockGraphViz } from "@craft-bits/viz/deadlock-graph-viz";
 
<DeadlockGraphViz />

Drive the active scenario from outside (controlled mode):

const [scenario, setScenario] = useState<DeadlockGraphVizScenario>("branch");
 
<DeadlockGraphViz scenario={scenario} onScenarioChange={setScenario} />;

Subscribe to phase transitions for an external counter:

<DeadlockGraphViz
  onPhaseChange={(phase) => {
    if (phase === "done") trackDeadlockSeen();
  }}
/>

Understanding the component

  1. Three chips. Each chip corresponds to one scenario and is wired to a keyboard shortcut (1, 2, 3). The active chip gets the accent palette; the rest stay muted.
  2. 2×2 rank grid. Four rank boxes laid out at fixed positions. Each rank renders the current operation and a status pill; waiting ranks pulse, blocked and dead ranks shake with a small x keyframe and shrink to scale: 0.97.
  3. Frame engine. Each scenario compiles to three frames: active → waiting → blocked (or timeout). The playback loop walks the array on a 2.2s cadence and lands on phase: "done" once the last frame settles.
  4. Wait-for arrows. Curved quadratic paths trace the dependency between ranks. Non-cycle arrows render warning-coloured and dashed; cycle arrows render error-coloured, solid, and breathe in opacity / stroke width to spotlight the loop.
  5. Status bar. Headline status (Running / Waiting... / DEADLOCK DETECTED / TIMEOUT) with a colour-coded dot that pulses on the final frame, plus an elapsed-time chip that shifts colour as it grows.
  6. Fix card. Surfaces the scenario's one-line remediation once phase === "done". Wrapped in a role="note" for screen readers.
  7. Reduced motion. Under prefers-reduced-motion: reduce, every entrance collapses to duration: 0, the cycle arrows stop pulsing, the rank shake disappears, and the auto-advance falls to a 90ms cadence so the final state still resolves.

Props

PropTypeDefaultDescription
scenarioDeadlockGraphVizScenarioControlled active scenario. Pair with onScenarioChange.
defaultScenarioDeadlockGraphVizScenario"order"Uncontrolled initial scenario.
onScenarioChange(scenario) => voidFires after the active scenario changes.
onPhaseChange(phase) => voidFires on every idle → playing → done transition.
scenariosreadonly DeadlockGraphVizScenarioDef[]three canonical NCCL scenariosOverride the built-in scenario list — supply your own chips, frames, and fix copy.
frameDurationMsnumber2200Milliseconds between auto-advanced frames while phase === "playing".
transitionTransitionSPRINGS.snapOverride the rank / arrow entrance spring.
classNamestringMerged onto the root via cn().

Accessibility

  • The root is role="figure" with an aria-label describing the viz; the live status region announces the active scenario, the current frame's prose label while playing, and the final "complete" line.
  • Chips are real <button type="button"> elements with aria-pressed reflecting the active scenario and aria-label naming the keyboard shortcut.
  • The fix card is role="note" with aria-label="Remediation" so screen-reader users can navigate to it directly.
  • Keyboard: 13 selects a scenario, Space toggles play / reset. Focus is visible via focus-visible:ring.
  • Colour is never the only signal — every rank carries its status pill, the headline status carries its label, the narration paragraph encodes state in prose, and the SR live region repeats it all.
  • Motion respects prefers-reduced-motion: reduce — every entrance, the cycle-arrow pulse, the box-shadow glow on the deadlock frame, and the rank-distress shake collapse to instant, and the auto-advance shortens to 90ms so the final state still resolves quickly.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/systems/DeadlockGraphViz.tsx). The source was a tri-mode lesson primitive (Explore / Predict / Challenge) wrapped in the project's Widget chrome with hard dependencies on ModeStrip, ChallengeBtn, FeedbackBadge, and ScoreDots, plus per-track palette tokens (--color-accent-400, --color-warn-400, --color-fail-400, --color-ink-*) and lesson-specific SPRINGS.snappy / SPRINGS.gentle / STAGGER.tight aliases. The craft-bits extract drops the predict and challenge modes (and their scoring chrome) to ship a focused, single-purpose scenario stepper; rebuilds the chip strip, status bar, narration panel, and fix card as inline token-styled elements; routes every colour through the semantic --cb-accent / --cb-warning / --cb-error / --cb-fg-* palette so consumer themes repaint freely; re-keys every transition to the canonical SPRINGS.snap / SPRINGS.smooth from @craft-bits/core/motion; replaces STAGGER.tight with the library-wide STAGGER constant; exposes a Radix-style controlled / uncontrolled scenario API with onScenarioChange + onPhaseChange; lifts the hardcoded scenario list into a scenarios prop with the canonical NCCL triad as the default; and wraps the component in forwardRef accepting className via cn() and spreading ...props on the root.