"use client" /** * The flow canvas: a sign-in flow drawn as a diagram you read top to bottom. * * Why a canvas at all. The previous builder rendered a flow as nested cards, so * every branch was a setting you had to click into and open one at a time. That * is the well-documented failure of card-stack builders: control flow becomes a * hidden property, and a four-branch flow looks like a pile of cards to hunt * through. On a canvas control flow is *spatial* — a fork is a fork you can see * without opening anything. * * Why auto-layout rather than draggable nodes. Positions are derived from the * flow, never stored. Free-form dragging buys arrangements nobody needs, and buys * them at the price of saved coordinates that go stale the moment a step is * inserted, plus diagrams whose meaning depends on where someone left a box. * Salesforce reached the same conclusion and recommends auto-layout by default. * * Why no graph library. Auto-layout means positions come from the DOM, so the * connectors are CSS rules on laid-out elements rather than computed SVG paths. * That keeps the diagram responsive, keeps DOM order equal to flow order (so Tab * walks the flow), and keeps a heavyweight dependency out of the workspace for one * prototype surface. * * Accessibility. A diagram of boxes and lines is unreadable without sight, so the * same structure ships as prose from `describeFlowForScreenReaders`, every line is * `aria-hidden`, and every node is a real button in flow order. */ import * as React from "react" import { Button } from "@/components/ui/button" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" import { cn } from "@/lib/utils" import { AUTH_METHOD_LABEL, IDENTIFIER_SUBJECT_LABEL, alwaysAuthenticates, describeConditions, describeFlowForScreenReaders, describeOutcome, flowSession, reachesEnd, sessionEffects, type DryRunStatus, type FlowStep, type LoginFlowDefinition, } from "@/lib/login-flow" /** What the properties pane is editing. */ export type CanvasSelection = | { kind: "flow" } | { kind: "step"; stepId: string } | { kind: "option"; stepId: string; optionId: string } /** The end of the flow: which apps the session gets, and what home shows. */ | { kind: "session" } const STEP_ICON: Record = { password: "fa-light fa-key", sso: "fa-light fa-shield-check", choice: "fa-light fa-split", } // ── Primitives ────────────────────────────────────────────────────────────── /** A vertical run of line with an optional insert button on it. */ function Connector({ onInsert, label }: { onInsert?: () => void; label?: string }) { return (