{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"file-diff","type":"registry:component","title":"File diff","description":"A change to one file as a unified diff: line numbers, tinted lines, only the changed words marked, long unchanged stretches folded; give rows or just before and after.","categories":["display"],"dependencies":["@radix-ui/react-direction@1.1.4","@radix-ui/react-slot@1.3.3","@radix-ui/react-tooltip@1.2.16","clsx@^2.1.1","lowlight@3.3.0","lucide-react@^1.48.0","motion@^13.4.4","tailwind-merge@^3.7.0"],"registryDependencies":["https://beamline.io/registry/look-prism.json"],"files":[{"path":"beamline/components/button/button-variants.ts","type":"registry:file","target":"src/ui/beamline/components/button/button-variants.ts","content":"import { cn } from \"../../lib/utils\";\nimport styles from \"./button.module.css\";\n\nexport type ButtonVariant = \"primary\" | \"secondary\" | \"outline\" | \"ghost\" | \"danger\" | \"accent\";\n/** sm 32 · md 36 · lg 40 · touch 44 (a bottom action bar on a phone). */\nexport type ButtonSize = \"sm\" | \"md\" | \"lg\" | \"touch\";\n/** Market side: buy fills with --pui-up, sell with --pui-down (a red-up market flips the two tokens, not the code). */\nexport type ButtonTone = \"up\" | \"down\";\n\n/** Which paint role paints each variant: filled variants are solids, the quiet ones keep their own light paint. */\nconst ROLE: Record<ButtonVariant, string | undefined> = {\n  primary: \"m-solid\",\n  accent: \"m-solid\",\n  secondary: \"m-control\",\n  outline: undefined,\n  ghost: undefined,\n  danger: undefined,\n};\n\n/** A variant's paint without the button's layout, for a group that wears one button's look (SplitButton paints the\n *  pair as one button and leaves its halves transparent). */\nexport function buttonPaint(variant: ButtonVariant = \"primary\", tone?: ButtonTone) {\n  return cn(ROLE[variant], styles[variant], tone && styles[tone]);\n}\n\n/** The classes that make any element look like a Button, for parts that render their own element. */\nexport function buttonClassName({\n  variant = \"primary\",\n  size = \"md\",\n  tone,\n  className,\n}: { variant?: ButtonVariant; size?: ButtonSize; tone?: ButtonTone; className?: string } = {}) {\n  return cn(styles.root, ROLE[variant], styles[variant], tone && styles[tone], styles[size], className);\n}\n"},{"path":"beamline/components/button/button.module.css","type":"registry:file","target":"src/ui/beamline/components/button/button.module.css","content":"@layer properties, theme, base, components, utilities;\n@layer components {\n  /* Button. Every colour, size and duration is a token; a variant only sets the four local properties below. */\n  .root {\n    --_button-bg: transparent;\n    --_button-bg-hover: var(--pui-hover);\n    --_button-fg: var(--pui-ink);\n    --_button-edge: none;\n    --_button-height: var(--pui-control-height-md);\n    --_button-pad: 14px;\n    position: relative;\n    isolation: isolate;\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    gap: var(--pui-space-1-5);\n    flex-shrink: 0;\n    height: var(--_button-height);\n    min-width: var(--_button-height);\n    /* never wider than its room: in a narrow card the label ends in an ellipsis instead of spilling (the full words\n       stay the accessible name) */\n    max-width: 100%;\n    padding-inline: var(--_button-pad);\n    border: 0;\n    /* Nested context: inside a frame that sets --pui-nest-radius and --pui-nest-inset (and --pui-nest-ceiling for pills)\n       the corner is the shared nested corner (.nest-corner, utilities.css: radius − inset, or the button's own radius\n       where that would fall under the chip radius); elsewhere the control radius. --pui-button-radius wins over both. */\n    border-radius: var(--pui-button-radius, var(--pui-nested, var(--pui-radius-control)));\n    color: var(--_button-fg);\n    font-family: var(--pui-font-body);\n    font-size: var(--pui-text-sm);\n    font-weight: var(--pui-weight-medium);\n    line-height: 1;\n    letter-spacing: var(--pui-tracking-body);\n    white-space: nowrap;\n    text-decoration: none;\n    cursor: pointer;\n    user-select: none;\n    -webkit-tap-highlight-color: transparent;\n    /* frequency law: hover arrives at 0ms (below), leaves over the ruled departure; opacity and translate are listed\n       so a button that waits for attention (.reveal) fades away like every reveal instead of vanishing */\n    transition:\n      var(--m-transition),\n      background-color var(--pui-motion-out) var(--pui-ease-default),\n      color var(--pui-motion-out) var(--pui-ease-default),\n      opacity var(--pui-motion-out) var(--pui-ease-default),\n      translate var(--pui-motion-out) var(--pui-ease-default);\n  }\n  /* the quiet variants paint themselves; filled and secondary variants are painted by their look role (m-solid,\n   m-control), which these zero-specificity rules can never override */\n  :where(.root) {\n    background: var(--_button-bg);\n    box-shadow: var(--_button-edge);\n  }\n  :where(.root:hover:not(:disabled, [aria-disabled=\"true\"])) {\n    background: var(--_button-bg-hover);\n  }\n  .root:hover:not(:disabled, [aria-disabled=\"true\"]) {\n    transition-duration: 0ms;\n  }\n  /* the asChild form has no motion: a CSS press instead */\n  .root:not(button):active {\n    transform: scale(0.97);\n  }\n  .root:disabled,\n  .root[aria-disabled=\"true\"]:not([data-loading]) {\n    opacity: var(--pui-opacity-disabled);\n    cursor: not-allowed;\n  }\n  .root[data-loading] {\n    cursor: progress;\n  }\n\n  /* Icons: 16px unless the icon says otherwise (zero specificity, so size-5 or an inline size wins) */\n  :where(.root) :where(svg) {\n    width: var(--pui-icon-md);\n    height: var(--pui-icon-md);\n    flex-shrink: 0;\n  }\n\n  .sm {\n    --_button-height: var(--pui-control-height-sm);\n    --_button-pad: var(--pui-space-2-5);\n    /* the base gap (--pui-space-1-5); 5px had no scale step, and 6px also lets the loader's negative margin cancel\n       the gap exactly at every size */\n  }\n  .md {\n    --_button-height: var(--pui-control-height-md);\n    --_button-pad: 14px;\n  }\n  .lg {\n    --_button-height: var(--pui-control-height-lg);\n    --_button-pad: var(--pui-space-4);\n    font-size: var(--pui-text-base);\n  }\n  /* a bottom action bar on a phone: the 44 px touch target (a platform minimum, not a spacing step, so it holds when\n     the spacing parameter changes — O1 §14.2), the large label */\n  .touch {\n    --_button-height: calc(var(--pui-control-height-lg) + 0.25rem);\n    --_button-pad: var(--pui-space-5);\n    font-size: var(--pui-text-base);\n  }\n  .root[data-icon-only] {\n    padding-inline: 0;\n    width: var(--_button-height);\n  }\n\n  /* the main action: ink on the page, inverted per theme */\n  .primary {\n    --m-color: var(--pui-ink);\n    --_button-fg: var(--pui-ink-inverse);\n  }\n  .secondary {\n    --_button-fg: var(--pui-ink);\n  }\n  .outline {\n    --_button-edge: inset 0 0 0 1px var(--pui-line-strong);\n    --_button-bg-hover: var(--pui-hover);\n  }\n  .ghost {\n    --_button-fg: var(--pui-ink-2);\n    --_button-bg-hover: var(--pui-hover);\n  }\n  .ghost:hover:not(:disabled) {\n    color: var(--pui-ink);\n  }\n  .danger {\n    --_button-fg: var(--pui-red);\n    --_button-edge: inset 0 0 0 1px color-mix(in oklab, var(--pui-red) 42%, transparent);\n    --_button-bg-hover: var(--pui-red-tint);\n  }\n  .accent {\n    --m-color: var(--pui-accent);\n    --_button-fg: var(--pui-accent-foreground);\n  }\n\n  /* Market side. Declared after the variants so it wins: a filled variant takes the side's colour and its on-colour\n     ink; a quiet variant keeps its paint and colours the label. */\n  .up {\n    --m-color: var(--pui-up);\n    --_button-fg: var(--pui-up);\n  }\n  .down {\n    --m-color: var(--pui-down);\n    --_button-fg: var(--pui-down);\n  }\n  .ghost:is(.up, .down):hover:not(:disabled) {\n    color: var(--_button-fg);\n  }\n  .primary.up,\n  .accent.up {\n    --_button-fg: var(--pui-on-up);\n  }\n  .primary.down,\n  .accent.down {\n    --_button-fg: var(--pui-on-down);\n  }\n\n  /* The main actions' light, where the look provides one, as a spectrum. Glow spills a soft light in the brand's\n     spectrum from under the button's bottom edge (clipped at it, so it never tints the face) that brightens under the\n     pointer. Prism keeps it inside the glass (--m-solid-glow-fit: 1): the face the light leaves by, the bottom edge,\n     splits it into a crisp line with a faint wash rising into the face, only while pointed at. Every other look\n     leaves --m-solid-glow at none, so the layer does not exist. */\n  .primary::after,\n  .accent::after {\n    --_fit: var(--m-solid-glow-fit, 0);\n    content: var(--m-solid-glow);\n    position: absolute;\n    z-index: -1;\n    top: calc((1 - var(--_fit)) * (100% + var(--m-solid-glow-gap, 0px)));\n    inset-inline: var(--m-solid-glow-inset, 14%);\n    height: var(--m-solid-glow-height, 14px);\n    border-radius: calc(\n      var(--_fit) * var(--pui-button-radius, var(--pui-nested, var(--pui-radius-control))) + (1 - var(--_fit)) *\n        var(--pui-radius-pill)\n    );\n    background: var(--m-spectrum);\n    background-size: var(--m-spectrum-size, 100% 100%);\n    /* the part of the spectrum that shows: this button's own angle (--m-prism-seed, new at every press), turned by\n       where the pointer is across the face (--m-prism-x) in a look that turns it (--m-solid-glow-turn) */\n    background-position: calc((var(--m-prism-seed, 0.3) + var(--m-prism-x, 0.5) * var(--m-solid-glow-turn, 0)) * 100%) 0;\n    filter: blur(var(--m-solid-glow-blur, 9px));\n    clip-path: var(--m-solid-glow-clip, inset(0 -32px -32px -32px));\n    mask: var(--m-solid-glow-mask, none);\n    mask-position:\n      0 0,\n      calc(var(--m-prism-seed, 0.3) * 100%) 0;\n    mask-composite: intersect;\n    opacity: var(--m-solid-glow-rest, 0.75);\n    scale: var(--m-solid-glow-scale, 1) 1;\n    pointer-events: none;\n    transition:\n      opacity var(--pui-motion-out) var(--pui-ease-default),\n      scale var(--pui-motion-out) var(--pui-ease-default),\n      background-position var(--pui-duration-considered) var(--pui-ease-default),\n      mask-position var(--pui-duration-considered) var(--pui-ease-default);\n  }\n  /* pointed at: the light brightens (glow) or grows out from the centre and drifts along while the pointer stays\n     (prism) */\n  .primary:hover::after,\n  .accent:hover::after {\n    opacity: 1;\n    scale: 1 1;\n    transition-duration: var(--m-solid-glow-in, 0ms);\n    animation: var(--m-solid-glow-flow, none);\n  }\n  /* A change of word or colour (Save → Saved, a tone turning green) passes a band of split light across the face once,\n     in a look that has one (prism: --m-solid-shift-light; the pen hook's ::before is free on a solid otherwise). */\n  .primary[data-shift]::before,\n  .accent[data-shift]::before {\n    content: var(--m-solid-shift-light, none);\n    position: absolute;\n    inset: 0;\n    z-index: -1;\n    border: 0;\n    border-radius: inherit;\n    background: var(--m-spectrum) 0 0 / var(--m-spectrum-size, 100% 100%);\n    mask: linear-gradient(105deg, transparent 39%, #000 47.5%, #000 52.5%, transparent 61%) 0 0 / 300% 100% no-repeat;\n    opacity: 0;\n    animation: prism-shift calc(var(--pui-duration-considered) * 2.2) var(--pui-ease-default);\n    pointer-events: none;\n  }\n  @keyframes prism-shift {\n    from {\n      mask-position: 100% 0;\n      opacity: 0.25;\n    }\n    35% {\n      opacity: 0.55;\n    }\n    to {\n      mask-position: 0 0;\n      opacity: 0;\n    }\n  }\n  @media (prefers-reduced-motion: reduce) {\n    .primary::after,\n    .accent::after {\n      transition: none;\n      animation: none !important;\n    }\n    .primary[data-shift]::before,\n    .accent[data-shift]::before {\n      content: none;\n    }\n  }\n  .root:disabled::after,\n  .root[aria-disabled=\"true\"]::after {\n    content: none;\n  }\n\n  /* The loader opens its own width, and the negative margin cancels the gap so the label does not jump */\n  .loader {\n    display: inline-flex;\n    overflow: hidden;\n    margin-inline-end: calc(var(--pui-space-1-5) * -1);\n  }\n  .loader > * {\n    margin-inline-end: var(--pui-space-1-5);\n  }\n  .spinner {\n    color: currentColor;\n  }\n  .root[data-icon-only] .loader {\n    margin-inline-end: 0;\n  }\n  .root[data-icon-only] .loader > * {\n    margin-inline-end: 0;\n  }\n\n  /* The label holds its place while the text inside swaps; the leaving text pops out of flow against it */\n  .label {\n    position: relative;\n    display: inline-flex;\n    justify-content: center;\n    min-width: 0;\n    overflow: visible;\n  }\n  /* the clip box is let out by a little over a line so descenders are never cut, without changing the layout */\n  .text {\n    display: inline-block;\n    max-width: 100%;\n    margin-block: -0.2em;\n    padding-block: 0.2em;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n  }\n  .label[data-morphing] > .text {\n    overflow: visible;\n    text-overflow: clip;\n  }\n}\n\n@layer components {\n  :global(.pui[data-motion=\"reduced\"]) .root:not(button):active,\n  :global(.pui[data-motion=\"none\"]) .root:not(button):active {\n    transform: none;\n  }\n  @media (prefers-reduced-motion: reduce) {\n    .root:not(button):active {\n      transform: none;\n    }\n  }\n}\n/* CSS-module composition only; visual rules stay in the component layer. */\n.root {\n  composes: nest-corner from global;\n}\n"},{"path":"beamline/components/button/button.tsx","type":"registry:file","target":"src/ui/beamline/components/button/button.tsx","content":"\"use client\";\n\nimport { useMessages } from \"../../runtime/localization\";\nimport { english as englishMessages } from \"./messages\";\nimport {\n  Children,\n  Fragment,\n  isValidElement,\n  useLayoutEffect,\n  useRef,\n  type ButtonHTMLAttributes,\n  type PointerEvent,\n  type ReactNode,\n  type Ref,\n  type RefObject,\n  type ComponentProps,\n} from \"react\";\nimport { AnimatePresence, LazyMotion, m, spring, useIsPresent } from \"motion/react\";\nimport { animate } from \"motion/mini\";\nimport * as Slot from \"@radix-ui/react-slot\";\nimport { Spinner } from \"../spinner/spinner\";\nimport type { BuiltInLook as Look } from \"../../runtime/preset\";\nimport { useComposedRef } from \"../../hooks/use-composed-ref\";\nimport { useMotionTokens } from \"../../hooks/use-motion-tokens\";\nimport { cn } from \"../../lib/utils\";\nimport styles from \"./button.module.css\";\n\nexport type { ButtonVariant, ButtonSize, ButtonTone } from \"./button-variants\";\nexport { buttonPaint, buttonClassName } from \"./button-variants\";\nimport { buttonClassName, type ButtonVariant, type ButtonSize, type ButtonTone } from \"./button-variants\";\nimport type { ButtonDefaults } from \"./defaults\";\nexport type { ButtonDefaults } from \"./defaults\";\nimport { useComponentDefaults } from \"../../runtime/appearance\";\nimport { useReducedMotionSafe } from \"../../hooks/use-reduced-motion-safe\";\n\ntype NativeProps = Omit<\n  ButtonHTMLAttributes<HTMLButtonElement>,\n  \"onDrag\" | \"onDragEnd\" | \"onDragStart\" | \"onAnimationStart\"\n>;\n\nexport interface ButtonProps extends NativeProps {\n  /** Visual weight: primary (ink-filled), secondary, outline, ghost, danger, or accent (filled with the brand). */\n  variant?: ButtonVariant;\n  /** Height 32 / 36 / 40 px; touch 44 px for a bottom action bar on a phone. */\n  size?: ButtonSize;\n  /** Market side. On primary/accent the fill becomes --pui-up (buy) or --pui-down (sell); on the quiet variants the\n   *  label takes that colour. Pair it with a word or arrow: colour is never the only signal. */\n  tone?: ButtonTone;\n  /** Shows a loader in front of the label, sets aria-busy and ignores clicks; size and focus stay. */\n  loading?: boolean;\n  classNames?: Partial<Record<\"root\" | \"label\" | \"loader\", string>>;\n  /** Paint this button in another look than the one in force around it (DESIGN-LANGUAGE.md \"Looks\"). */\n  look?: Look;\n  /** Render the single child (a link, a Radix trigger) with the button's look instead of a <button>. */\n  asChild?: boolean;\n  /** A square button holding only an icon (give it an aria-label). Decided by the children when left out: icon-only when\n   *  no words sit anywhere among them (an icon, or a frame of icons), so `<span>Close</span>` is a label. */\n  iconOnly?: boolean;\n  ref?: Ref<HTMLButtonElement>;\n}\n\n// Motion's animation features arrive in a deferred chunk (AT11): the button renders and works at once, and its press,\n// loader and label motion run once they land, a frame or two after mount. Buttons that mount after that get the\n// loaded features directly, so they render once instead of again when a lazy load resolves.\ntype Features = typeof import(\"./motion-features\").default;\nlet loaded: Features | null = null;\nconst features = () =>\n  import(\"./motion-features\").then((module) => {\n    loaded = module.default;\n    return loaded;\n  });\n\n/** Children with fragments opened up, so `{busy ? <><Icon />Saving</> : …}` reads as icon plus text. */\nconst flat = (children: ReactNode): ReactNode[] =>\n  Children.toArray(children).flatMap((c) =>\n    isValidElement<{ children?: ReactNode }>(c) && c.type === Fragment ? flat(c.props.children) : [c],\n  );\n\n/** Whether any words sit anywhere inside (an icon, or a frame of icons, has none). */\nfunction hasText(node: ReactNode): boolean {\n  if (typeof node === \"string\") return node.trim() !== \"\";\n  if (typeof node === \"number\") return true;\n  if (Array.isArray(node)) return node.some(hasText);\n  return isValidElement<{ children?: ReactNode }>(node) && hasText(node.props.children);\n}\n\n/** Splits children into the text run (the label that can change) and everything else (icons), in order, and says\n *  whether any element carries content of its own (a `<span>Close</span>` label, unlike an icon). */\nfunction split(children: ReactNode) {\n  const parts = flat(children);\n  const text = parts\n    .filter((p) => typeof p === \"string\" || typeof p === \"number\")\n    .join(\"\")\n    .trim();\n  const firstText = parts.findIndex((p) => typeof p === \"string\" || typeof p === \"number\");\n  const before = firstText === -1 ? parts : parts.slice(0, firstText).filter(isValidElement);\n  const after = firstText === -1 ? [] : parts.slice(firstText).filter(isValidElement);\n  const content = parts.some((p) => isValidElement<{ children?: ReactNode }>(p) && hasText(p.props.children));\n  return { text, before, after, content };\n}\n\n/**\n * The action control. Hover answers at once and lets go over --pui-motion-out; a press scales down at once, or swells\n * on the snappy spring where the look's --pui-press-scale is above 1 (glass), never on a trigger that anchors a\n * floating layer, which would measure a resized box; a changed label slides in while the\n * width follows on a critically damped spring; loading keeps the size and the focus.\n */\nexport function Button({\n  variant: variantProp,\n  size: sizeProp,\n  tone: toneProp,\n  loading = false,\n  look,\n  asChild = false,\n  iconOnly: iconOnlyProp,\n  className,\n  classNames,\n  children,\n  type,\n  onClick,\n  onPointerMove,\n  onPointerDown,\n  disabled,\n  ref,\n  ...props\n}: ButtonProps) {\n  const i18n = useMessages(\"button\", englishMessages);\n\n  const defaults = useComponentDefaults<ButtonDefaults>(\"button\", [\"variant\", \"size\", \"tone\"]);\n  const variant = variantProp ?? defaults.variant ?? \"primary\";\n  const size = sizeProp ?? defaults.size ?? \"md\";\n  const tone = toneProp ?? defaults.tone;\n  const host = useRef<HTMLButtonElement>(null);\n  const attach = useComposedRef(host, ref);\n  const motionTokens = useMotionTokens(host);\n  const requestedReduced = useReducedMotionSafe();\n  const reduced = requestedReduced || motionTokens.reduced;\n  // Reduced motion keeps a restrained fade; none removes it (APPEARANCE A06).\n  const fade = motionTokens.mode === \"none\" ? 0 : motionTokens.fade.in;\n  // a caller's aria-disabled keeps the button focusable (focus never drops after Save resolves) but inert to presses\n  const softDisabled = props[\"aria-disabled\"] === true || props[\"aria-disabled\"] === \"true\";\n  const { text, before, after, content } = split(children);\n  const prism = usePrismLight(host, variant === \"primary\" || variant === \"accent\", `${text}|${tone}|${variant}`);\n  // with asChild the text sits inside the child (the link), not among our own children\n  const inner = asChild && isValidElement<{ children?: ReactNode }>(children) ? split(children.props.children) : null;\n  const iconOnly = iconOnlyProp ?? (inner ? !inner.text && !inner.content : !text && !content);\n  const anchor =\n    (props[\"aria-haspopup\"] != null && props[\"aria-haspopup\"] !== false && props[\"aria-haspopup\"] !== \"false\") ||\n    \"data-state\" in props;\n  const shared = {\n    \"data-slot\": \"button\",\n    \"data-variant\": variant,\n    \"data-size\": size,\n    \"data-tone\": tone,\n    \"data-icon-only\": iconOnly ? \"\" : undefined,\n    \"data-loading\": loading ? \"\" : undefined,\n    \"data-look\": look,\n    className: buttonClassName({ variant, size, tone, className: cn(classNames?.root, className) }),\n  };\n\n  if (asChild)\n    return (\n      <Slot.Root {...shared} {...props} ref={attach}>\n        {children}\n      </Slot.Root>\n    );\n\n  const button = (\n    <m.button\n      ref={attach}\n      type={type ?? \"button\"}\n      {...shared}\n      {...props}\n      disabled={disabled}\n      aria-busy={loading || undefined}\n      aria-disabled={loading || softDisabled || undefined}\n      onPointerMove={(e) => {\n        prism.turn(e);\n        onPointerMove?.(e);\n      }}\n      onPointerDown={(e) => {\n        prism.press();\n        onPointerDown?.(e);\n      }}\n      onClick={(e) => {\n        if (loading || softDisabled) {\n          e.preventDefault();\n          return;\n        }\n        onClick?.(e);\n      }}\n      whileTap={\n        reduced || anchor || disabled || loading || softDisabled\n          ? undefined\n          : {\n              scale: motionTokens.pressScale,\n              // a press that sinks lands at once; one that swells toward the finger (glass) grows on the spring\n              transition: motionTokens.pressScale > 1 ? motionTokens.spring.snappy : { duration: 0 },\n            }\n      }\n      transition={{ scale: motionTokens.spring.snappy }}\n    >\n      <AnimatePresence initial={false}>\n        {loading && (\n          <m.span\n            key=\"loader\"\n            data-slot=\"button-loader\"\n            className={cn(styles.loader, classNames?.loader)}\n            initial={reduced ? { opacity: 0 } : { width: 0, opacity: 0 }}\n            animate={{ width: \"auto\", opacity: 1 }}\n            exit={reduced ? { opacity: 0 } : { width: 0, opacity: 0 }}\n            transition={{ duration: reduced ? fade : motionTokens.duration.standard, ease: motionTokens.ease.default }}\n          >\n            <Spinner size=\"sm\" label={i18n(\"working\")} className={styles.spinner} />\n          </m.span>\n        )}\n      </AnimatePresence>\n      {iconOnly ? !loading && before : before}\n      {!iconOnly && text && (\n        <Label text={text} reduced={!!reduced} fade={fade} motionTokens={motionTokens} className={classNames?.label} />\n      )}\n      {after}\n    </m.button>\n  );\n  return <LazyMotion features={loaded ?? features}>{button}</LazyMotion>;\n}\n\n/**\n * The main action's light inside the glass, in a look that draws one (prism): its own angle from the start, turned by\n * where the pointer is across the face, a new angle at every press, and a sweep of split light (data-shift) when the\n * button's word, tone or variant changes after it first appears. Writes two style properties and one attribute on the\n * button; nothing runs at rest. Looks without this light ignore all three.\n */\nfunction usePrismLight(host: RefObject<HTMLButtonElement | null>, solid: boolean, state: string) {\n  const frame = useRef(0);\n  const shown = useRef(state);\n  const ending = useRef(0);\n  useLayoutEffect(() => {\n    const el = host.current;\n    if (el && solid) el.style.setProperty(\"--m-prism-seed\", Math.random().toFixed(3));\n  }, [host, solid]);\n  useLayoutEffect(() => {\n    const el = host.current;\n    if (!el || !solid || shown.current === state) return;\n    shown.current = state;\n    // restart the sweep if it is still running\n    delete el.dataset.shift;\n    void el.offsetWidth;\n    el.dataset.shift = \"\";\n    clearTimeout(ending.current);\n    ending.current = window.setTimeout(() => delete el.dataset.shift, 1200);\n  }, [host, solid, state]);\n  useLayoutEffect(\n    () => () => {\n      clearTimeout(ending.current);\n      cancelAnimationFrame(frame.current);\n    },\n    [],\n  );\n  return {\n    turn: (e: PointerEvent<HTMLButtonElement>) => {\n      if (!solid || e.pointerType !== \"mouse\" || frame.current) return;\n      const el = e.currentTarget;\n      const x = e.clientX;\n      frame.current = requestAnimationFrame(() => {\n        frame.current = 0;\n        const r = el.getBoundingClientRect();\n        if (r.width) el.style.setProperty(\"--m-prism-x\", ((x - r.left) / r.width).toFixed(3));\n      });\n    },\n    press: () => {\n      if (solid) host.current?.style.setProperty(\"--m-prism-seed\", Math.random().toFixed(3));\n    },\n  };\n}\n\n/** The text run. When it changes, the old words leave upward and the new ones rise in, while the width moves from the\n *  old measure to the new one; no measuring happens while the label is unchanged. */\nfunction Label({\n  text,\n  reduced,\n  fade,\n  motionTokens,\n  className,\n}: {\n  text: string;\n  reduced: boolean;\n  /** Seconds of the label crossfade under reduced motion (0 under none). */\n  fade: number;\n  /** The button's own, resolved at the button: the label sits inside it, in the same look and motion. */\n  motionTokens: ReturnType<typeof useMotionTokens>;\n  className?: string;\n}) {\n  const slot = useRef<HTMLSpanElement>(null);\n  const latest = useRef(motionTokens);\n  useLayoutEffect(() => {\n    latest.current = motionTokens;\n  }, [motionTokens]);\n  const last = useRef<{ text: string; width: number } | null>(null);\n  useLayoutEffect(() => {\n    const el = slot.current;\n    if (!el) return;\n    const prev = last.current;\n    // first mount: read the width after paint, when layout is already clean, instead of forcing a layout per button\n    if (!prev) {\n      last.current = { text, width: Number.NaN };\n      const id = requestAnimationFrame(() => {\n        if (last.current?.text === text) last.current = { text, width: el.getBoundingClientRect().width };\n      });\n      return () => cancelAnimationFrame(id);\n    }\n    el.style.width = \"\";\n    const width = el.getBoundingClientRect().width;\n    last.current = { text, width };\n    // a label that is not rendered (a hidden toolbar on a phone) has nothing to animate\n    if (prev.text === text || reduced || Number.isNaN(prev.width) || !el.getClientRects().length) return;\n    // The same spring, played natively: motion/mini samples the generator into a linear() easing.\n    const { visualDuration, bounce } = latest.current.spring.morph;\n    // while the width eases the words are clipped, not cut short with an ellipsis (that is for a room too narrow)\n    el.dataset.morphing = \"\";\n    const run = animate(el, { width: [`${prev.width}px`, `${width}px`] }, { type: spring, visualDuration, bounce });\n    run.then(() => {\n      el.style.width = \"\";\n      delete el.dataset.morphing;\n    });\n    // cancel, not stop: stop commits the running value, which throws once the element stops being rendered, and the\n    // next change measures afresh anyway\n    return () => {\n      run.cancel();\n      el.style.width = \"\";\n      delete el.dataset.morphing;\n    };\n  }, [text, reduced]);\n  // A label that changes faster than its words can rise in (a count read live while a hand drags) shows each new\n  // value at once: otherwise every value would start invisible and the button would read blank for the whole drag.\n  const rapid = useRapid(text, motionTokens.duration.standard * 1000);\n  const enter = reduced ? { opacity: 0 } : { opacity: 0, y: \"0.45em\", filter: `blur(${motionTokens.blur.subtle}px)` };\n  const leave = reduced\n    ? { opacity: 0 }\n    : {\n        opacity: 0,\n        y: \"-0.45em\",\n        filter: `blur(${motionTokens.blur.subtle}px)`,\n        transition: { duration: motionTokens.duration.fast },\n      };\n  return (\n    <span ref={slot} data-slot=\"button-label\" className={cn(styles.label, className)}>\n      <AnimatePresence mode=\"popLayout\" initial={false} custom={rapid}>\n        <Words\n          key={text}\n          className={styles.text}\n          custom={rapid}\n          variants={{\n            enter: (fast: boolean) => (fast ? { opacity: 1, y: \"0em\", filter: \"blur(0px)\" } : enter),\n            shown: { opacity: 1, y: \"0em\", filter: \"blur(0px)\" },\n            leave: (fast: boolean) => (fast ? { opacity: 0, transition: { duration: 0 } } : leave),\n          }}\n          initial=\"enter\"\n          animate=\"shown\"\n          exit=\"leave\"\n          transition={{ duration: reduced ? fade : motionTokens.duration.standard, ease: motionTokens.ease.default }}\n        >\n          {text}\n        </Words>\n      </AnimatePresence>\n    </span>\n  );\n}\n\n/** Whether `text` changed again within `ms` of its last change. Read in render: the answer belongs to this text. */\nfunction useRapid(text: string, ms: number) {\n  const seen = useRef({ text, at: Number.NEGATIVE_INFINITY, rapid: false });\n  if (seen.current.text !== text) {\n    const at = performance.now();\n    seen.current = { text, at, rapid: at - seen.current.at < ms };\n  }\n  return seen.current.rapid;\n}\n\n/** One label's words. Leaving, they are already gone for assistive tech: the button's name is the new label at once,\n *  not both while the old words animate away (or for good, where time stands still in a test). */\nfunction Words(props: ComponentProps<typeof m.span>) {\n  const present = useIsPresent();\n  return <m.span {...props} aria-hidden={present ? undefined : true} />;\n}\n\nexport default Button;\n"},{"path":"beamline/components/button/defaults.ts","type":"registry:file","target":"src/ui/beamline/components/button/defaults.ts","content":"import type { ButtonSize, ButtonTone, ButtonVariant } from \"./button-variants\";\n/** Scoped design choices only; no loading, events or domain state. */\nexport interface ButtonDefaults {\n  variant?: ButtonVariant;\n  size?: ButtonSize;\n  tone?: ButtonTone;\n}\n"},{"path":"beamline/components/button/messages.ts","type":"registry:file","target":"src/ui/beamline/components/button/messages.ts","content":"import type { MessageDefinitions } from \"../../runtime/messages\";\n/** Built-in English copy; stable keys are the public translation API. */\nexport const english = {\n  working: \"Working\",\n} satisfies MessageDefinitions;\n"},{"path":"beamline/components/button/motion-features.ts","type":"registry:file","target":"src/ui/beamline/components/button/motion-features.ts","content":"// REASON: Motion's DOM animation features (tween, spring, presence, press) are loaded after the first paint, so a\n// Button costs a small part of Motion on first load; LazyMotion in button.tsx renders until they arrive.\nimport { domAnimation } from \"motion/react\";\n\nexport default domAnimation;\n"},{"path":"beamline/components/code-block/code-block.module.css","type":"registry:file","target":"src/ui/beamline/components/code-block/code-block.module.css","content":"@layer properties, theme, base, components, utilities;\n@layer components {\n  /* Code block: a well (the inset role) that answers its room. Below 20rem the line numbers give way to the code and,\n     until someone chooses otherwise, long lines wrap under their own indent instead of scrolling out of sight. */\n  .root {\n    container-type: inline-size;\n    overflow: hidden;\n    border-radius: var(--pui-radius-card);\n  }\n  .header {\n    display: flex;\n    height: 40px;\n    align-items: center;\n    gap: var(--pui-space-2);\n    padding-inline: var(--pui-space-3) var(--pui-space-1-5);\n    border-bottom: 1px solid var(--pui-line-soft);\n  }\n  .title {\n    display: flex;\n    min-width: 0;\n    flex: 1;\n    color: var(--pui-ink-2);\n    font-family: var(--pui-font-code);\n    font-size: var(--pui-text-xs);\n  }\n  /* a path keeps its file name: the folders before it truncate first */\n  .path {\n    display: flex;\n    min-width: 0;\n    white-space: nowrap;\n  }\n  .dir {\n    overflow: hidden;\n    min-width: 0;\n    flex-shrink: 1;\n    color: var(--pui-ink-3);\n    text-overflow: ellipsis;\n  }\n  .base {\n    overflow: hidden;\n    min-width: 0;\n    flex-shrink: 0;\n    max-width: 100%;\n    text-overflow: ellipsis;\n  }\n  .language {\n    color: var(--pui-ink-3);\n    font-family: var(--pui-font-code);\n    font-size: var(--pui-text-2xs);\n  }\n  .viewport {\n    position: relative;\n  }\n  .viewport[data-folded] {\n    mask-image: linear-gradient(to bottom, black calc(100% - 64px), transparent);\n  }\n  .pre {\n    overflow-x: auto;\n    padding-block: var(--pui-space-3);\n    text-align: start;\n  }\n  .code {\n    display: grid;\n    min-width: max-content;\n  }\n  .rest {\n    display: grid;\n  }\n  .line {\n    position: relative;\n    display: flex;\n    min-width: 0;\n    padding-inline-end: var(--pui-space-4);\n    transition: background-color var(--pui-motion-out) var(--pui-ease-default);\n  }\n  /* the line under the pointer steps one rung, so a long line can be followed across the block */\n  .line:hover {\n    background-color: var(--pui-hover);\n    transition-duration: 0ms;\n  }\n  /* a marked line (O1 §6 selected): tint plus a 2px accent-ink bar on the start side */\n  .line[data-marked],\n  .line[data-marked]:hover {\n    background-color: var(--pui-accent-tint);\n  }\n  .line[data-marked]::before {\n    content: \"\";\n    position: absolute;\n    inset-block: 0;\n    inset-inline-start: 0;\n    width: 2px;\n    background: var(--pui-accent-ink);\n  }\n  .number {\n    width: 40px;\n    flex-shrink: 0;\n    padding-inline-end: var(--pui-space-3);\n    color: var(--pui-ink-3);\n    font-variant-numeric: tabular-nums;\n    text-align: end;\n    user-select: none;\n  }\n  .content {\n    min-width: 0;\n    color: var(--pui-ink);\n  }\n  .root:not([data-numbers]) .content {\n    padding-inline-start: var(--pui-space-3);\n  }\n  /* wrapped: a long line breaks anywhere it must and hangs its rest two characters under its own indent */\n  .root[data-wrap] .code {\n    min-width: 0;\n  }\n  .root[data-wrap] .pre {\n    white-space: pre-wrap;\n  }\n  .root[data-wrap] .content {\n    flex: 1;\n    overflow-wrap: anywhere;\n    padding-inline-start: calc((var(--_indent, 0) + 2) * 1ch);\n    text-indent: calc((var(--_indent, 0) + 2) * -1ch);\n  }\n  /* a word stays whole on its line (code breaks between words, not at a hyphen or a slash) unless it is longer than\n     the line itself */\n  .word {\n    display: inline-block;\n    max-width: 100%;\n    text-indent: 0;\n  }\n  .root[data-wrap]:not([data-numbers]) .content {\n    padding-inline-start: calc(var(--pui-space-3) + (var(--_indent, 0) + 2) * 1ch);\n  }\n  @container (max-width: 20rem) {\n    .number {\n      display: none;\n    }\n    .content,\n    .root[data-wrap] .content {\n      padding-inline-start: calc(var(--pui-space-3) + (var(--_indent, 0) + 2) * 1ch);\n    }\n    .root:not([data-wrap]) .content {\n      padding-inline-start: var(--pui-space-3);\n    }\n    .language {\n      display: none;\n    }\n  }\n  .toggle {\n    display: flex;\n    width: 100%;\n    height: 36px;\n    align-items: center;\n    justify-content: center;\n    gap: var(--pui-space-1-5);\n    border-top: 1px solid var(--pui-line-soft);\n    color: var(--pui-ink-3);\n    font-size: var(--pui-text-xs);\n    transition:\n      background-color var(--pui-motion-out) var(--pui-ease-default),\n      color var(--pui-motion-out) var(--pui-ease-default);\n  }\n  .toggle:hover {\n    background-color: var(--pui-hover);\n    color: var(--pui-ink);\n    transition-duration: 0ms;\n  }\n  .chevron {\n    width: 14px;\n    height: 14px;\n    transition: rotate var(--pui-motion-out) var(--pui-ease-default);\n  }\n  .toggle[aria-expanded=\"true\"] .chevron {\n    rotate: 180deg;\n  }\n}\n/* CSS-module composition only; visual rules stay in the component layer. */\n.pre {\n  composes: text-code from global;\n}\n"},{"path":"beamline/components/code-block/code-block.tsx","type":"registry:file","target":"src/ui/beamline/components/code-block/code-block.tsx","content":"\"use client\";\n\nimport { useMessages } from \"../../runtime/localization\";\nimport { english as englishMessages } from \"./messages\";\nimport type { CSSProperties, Ref } from \"react\";\n\nimport { useMemo, useRef, useState } from \"react\";\nimport { ChevronDown, WrapText } from \"lucide-react\";\nimport { Button } from \"../button/button\";\nimport { CopyButton } from \"../copy-button/copy-button\";\nimport { useComposedRef } from \"../../hooks/use-composed-ref\";\nimport { useWidthAs } from \"../../hooks/use-content-width\";\nimport { highlightLines, type HighlightToken } from \"../../lib/highlight\";\nimport { cn } from \"../../lib/utils\";\nimport { useControllable } from \"../../hooks/use-controllable\";\nimport { Tooltip } from \"../tooltip/tooltip\";\nimport styles from \"./code-block.module.css\";\n\nexport interface CodeBlockClassNames {\n  /** Classes for the root part; no override by default. */\n  root?: string;\n  /** Classes for the header part; no override by default. */\n  header?: string;\n  /** Classes for the title part; no override by default. */\n  title?: string;\n  /** Classes for the language part; no override by default. */\n  language?: string;\n  /** Classes for the wrap part; no override by default. */\n  wrap?: string;\n  /** Classes for the copy part; no override by default. */\n  copy?: string;\n  /** Classes for the viewport part; no override by default. */\n  viewport?: string;\n  /** Classes for the pre part; no override by default. */\n  pre?: string;\n  /** Classes for the code part; no override by default. */\n  code?: string;\n  /** Classes for the line part; no override by default. */\n  line?: string;\n  /** Classes for the number part; no override by default. */\n  number?: string;\n  /** Classes for the content part; no override by default. */\n  content?: string;\n  /** Classes for the rest part; no override by default. */\n  rest?: string;\n  /** Classes for the toggle part; no override by default. */\n  toggle?: string;\n  /** Classes for the chevron part; no override by default. */\n  chevron?: string;\n}\n\nexport interface CodeBlockProps {\n  /** Source code displayed by the code block. Required. */\n  code: string;\n  /** Highlighting: \"tsx\", \"ts\", \"json\", \"bash\", \"python\", \"sql\", \"css\"… Unknown languages show plain text. */\n  language?: string;\n  /** File name displayed with the code. Defaults to no file name. */\n  filename?: string;\n  /** Collapse longer code to this many lines behind \"Show all\". */\n  maxLines?: number;\n  /** Line numbers in the gutter. Default true; a room narrower than 20rem drops them for the code. */\n  lineNumbers?: boolean;\n  /** Lines to mark (1-based), e.g. the ones a guide talks about. */\n  highlight?: number[];\n  /** Wrap long lines instead of scrolling sideways (a wrapped line keeps its indent and hangs the rest under it).\n   *  Unset, it follows the room: wrapped below 20rem, scrolling above; the header's wrap button changes it. */\n  wrap?: boolean;\n  /** Where the wrap starts when it is not controlled. */\n  defaultWrap?: boolean;\n  /** Reports line wrapping after a change; no callback by default. */\n  onWrapChange?: (wrap: boolean) => void;\n  /** Classes on the documented visual root; no override by default. */\n  className?: string;\n  /** Inline styles on the documented visual root; no inline overrides by default. */\n  style?: CSSProperties;\n  /** Classes for the named visual parts; no part overrides by default. */\n  classNames?: CodeBlockClassNames;\n  /** Receives the documented root element; no ref by default. */\n  ref?: Ref<HTMLElement>;\n}\n\n/** How many of the lines, from the first, fill `rows` rows when wrapped lines take several (at least one line). */\nfunction linesInRows(root: HTMLElement | null, rows: number) {\n  const lines = root?.querySelectorAll<HTMLElement>('[data-slot=\"code-block-line\"]');\n  if (!lines?.length) return rows;\n  const row = parseFloat(getComputedStyle(lines[0]).lineHeight) || Math.min(...[...lines].map((l) => l.offsetHeight));\n  let used = 0;\n  let n = 0;\n  for (const l of lines) {\n    used += Math.max(1, Math.round(l.offsetHeight / row));\n    if (used > rows) break;\n    n++;\n  }\n  return Math.min(Math.max(n, 1), rows);\n}\n\n/** A path keeps its file name: the folders before it give way first (\"…/health/churn-risk.ts\"). */\nexport function PathName({\n  path,\n  className,\n}: {\n  /** Path displayed beside the file or record. Required. */\n  path: string;\n  /** Classes on the documented visual root; no override by default. */\n  className?: string;\n}) {\n  const cut = path.lastIndexOf(\"/\") + 1;\n  return (\n    <bdi dir=\"ltr\" className={cn(styles.path, className)}>\n      {cut > 0 && <span className={styles.dir}>{path.slice(0, cut)}</span>}\n      <span className={styles.base}>{path.slice(cut)}</span>\n    </bdi>\n  );\n}\n\n/** A line as unbreakable runs and the spaces between them: a run keeps its coloured pieces together and ends at a\n *  space or right after a path or member separator (/ . , ( -), so a wrapped line breaks where code reads naturally\n *  (between words, after a slash) and never strands a closing quote or a semicolon. */\nfunction wordsOf(tokens: HighlightToken[]) {\n  const out: (string | HighlightToken[])[] = [];\n  let run: HighlightToken[] | null = null;\n  for (const t of tokens)\n    for (const part of t.content.split(/(\\s+)/)) {\n      if (!part) continue;\n      if (/^\\s/.test(part)) {\n        run = null;\n        out.push(part);\n        continue;\n      }\n      for (const piece of part.split(/(?<=[/.,(-])(?=.)/)) {\n        if (!run) out.push((run = []));\n        run.push({ ...t, content: piece });\n        if (/[/.,(-]$/.test(piece)) run = null;\n      }\n    }\n  return out;\n}\n\n/** Leading spaces of a line, a tab counted as two: a wrapped line hangs its rest under its own indent. */\nconst indentOf = (tokens: HighlightToken[]) => {\n  const lead = (\n    tokens\n      .map((t) => t.content)\n      .join(\"\")\n      .match(/^[ \\t]*/)?.[0] ?? \"\"\n  ).replace(/\\t/g, \"  \");\n  return lead.length;\n};\n\n/**\n * Code to read and copy: highlighted in the system's colours, numbered, marked lines, long files folded. It answers its\n * room: narrow, the line numbers give way and long lines wrap under their own indent; wide, lines scroll sideways. The\n * wrap button (shown on hover or focus, always on touch) switches it; \"Show all\" opens the rest in place.\n */\nexport function CodeBlock({\n  code,\n  language,\n  filename,\n  maxLines,\n  lineNumbers = true,\n  highlight = [],\n  wrap: wrapProp,\n  defaultWrap,\n  onWrapChange,\n  className,\n  style,\n  classNames,\n  ref,\n}: CodeBlockProps) {\n  const i18n = useMessages(\"code-block\", englishMessages);\n\n  const lines = useMemo(() => highlightLines(code.replace(/\\n$/, \"\"), language), [code, language]);\n  const [open, setOpen] = useState(false);\n  const root = useRef<HTMLElement>(null);\n  const rootRef = useComposedRef(root, ref);\n  // the room decides the wrap until someone chooses: below 20rem a scrolled line hides most of itself\n  const narrow = useWidthAs(root, (w) => w < 320, false);\n  const [wrapValue, setWrapValue] = useControllable<boolean | undefined, boolean>(wrapProp, defaultWrap, onWrapChange);\n  const wrap = wrapValue ?? narrow;\n  const canToggle = wrapProp === undefined || !!onWrapChange;\n  const toggleWrap = () => {\n    setWrapValue(!wrap);\n  };\n  const folding = !!maxLines && lines.length > maxLines;\n  // wrapped, one line can take several rows: the fold keeps `maxLines` rows in view, not `maxLines` lines\n  const cut = useWidthAs(\n    root,\n    () => (folding && wrap ? linesInRows(root.current, maxLines) : (maxLines ?? 0)),\n    maxLines ?? 0,\n    `${folding}:${wrap}:${code}`,\n  );\n  const marks = new Set(highlight);\n  const line = (tokens: HighlightToken[], i: number) => (\n    <span\n      data-slot=\"code-block-line\"\n      key={i}\n      data-marked={marks.has(i + 1) || undefined}\n      className={cn(styles.line, classNames?.line)}\n      style={wrap ? ({ \"--_indent\": indentOf(tokens) } as CSSProperties) : undefined}\n    >\n      {lineNumbers && (\n        <span aria-hidden data-slot=\"code-block-number\" className={cn(styles.number, classNames?.number)}>\n          {i + 1}\n        </span>\n      )}\n      <span data-slot=\"code-block-content\" className={cn(styles.content, classNames?.content)}>\n        {!tokens.length\n          ? \" \"\n          : wrap\n            ? wordsOf(tokens).map((w, k) =>\n                typeof w === \"string\" ? (\n                  w\n                ) : (\n                  // a word (its coloured pieces together) stays whole on its line unless it is longer than the line\n                  <span key={k} className={styles.word}>\n                    {w.map((p, j) => (\n                      <span key={j} style={p.htmlStyle}>\n                        {p.content}\n                      </span>\n                    ))}\n                  </span>\n                ),\n              )\n            : tokens.map((t, k) => (\n                <span key={k} style={t.htmlStyle}>\n                  {t.content}\n                </span>\n              ))}\n      </span>\n    </span>\n  );\n  return (\n    <figure\n      ref={rootRef}\n      style={style}\n      data-slot=\"code-block\"\n      data-wrap={wrap || undefined}\n      data-numbers={lineNumbers || undefined}\n      className={cn(\"m-inset reveal-host\", styles.root, classNames?.root, className)}\n    >\n      <figcaption data-slot=\"code-block-header\" className={cn(styles.header, classNames?.header)}>\n        <span data-slot=\"code-block-title\" className={cn(styles.title, classNames?.title)}>\n          {filename ? <PathName path={filename} /> : <bdi dir=\"ltr\">{language ?? i18n(\"code\")}</bdi>}\n        </span>\n        {filename && language && (\n          <span data-slot=\"code-block-language\" className={cn(styles.language, classNames?.language)}>\n            {language}\n          </span>\n        )}\n        {canToggle && (\n          // secondary: it waits for attention (hover or focus in the block; always shown on touch)\n          <span className=\"reveal\">\n            <Tooltip content={i18n(\"wrapLongLines\")}>\n              <Button\n                variant=\"ghost\"\n                size=\"sm\"\n                aria-label={i18n(\"wrapLongLines\")}\n                aria-pressed={wrap}\n                onClick={toggleWrap}\n                data-slot=\"code-block-wrap\"\n                className={cn(\"aria-pressed:bg-accent-tint aria-pressed:text-accent-ink\", classNames?.wrap)}\n              >\n                <WrapText />\n              </Button>\n            </Tooltip>\n          </span>\n        )}\n        <CopyButton\n          className={classNames?.copy}\n          value={code}\n          label={filename ? i18n(\"copy\", { filename: filename }) : i18n(\"copyCode\")}\n          iconOnly\n          variant=\"ghost\"\n        />\n      </figcaption>\n      {/* code reads left to right in any page direction; a folded file fades out through a mask, so the fade matches\n          whatever the look paints the well with */}\n      <div\n        dir=\"ltr\"\n        data-folded={(folding && !open) || undefined}\n        data-slot=\"code-block-viewport\"\n        className={cn(styles.viewport, classNames?.viewport)}\n      >\n        <pre\n          data-slot=\"code-block-pre\"\n          className={cn(styles.pre, classNames?.pre)}\n          tabIndex={0}\n          aria-label={filename ?? i18n(\"code\")}\n        >\n          <code data-slot=\"code-block-code\" className={cn(styles.code, classNames?.code)}>\n            {(folding ? lines.slice(0, cut) : lines).map(line)}\n            {folding && (\n              // the rest of a long file opens in place to its own height, and folds back the same way\n              <span\n                data-slot=\"code-block-rest\"\n                data-open={open || undefined}\n                className={cn(\"disclosure-panel\", classNames?.rest)}\n              >\n                <span className={styles.rest}>{lines.slice(cut).map((t, k) => line(t, k + cut))}</span>\n              </span>\n            )}\n          </code>\n        </pre>\n      </div>\n      {folding && (\n        <button\n          type=\"button\"\n          aria-expanded={open}\n          onClick={() => setOpen((o) => !o)}\n          data-slot=\"code-block-toggle\"\n          className={cn(styles.toggle, classNames?.toggle)}\n        >\n          {open ? i18n(\"showLess\") : i18n(\"showAllLines\", { length: lines.length })}\n          <ChevronDown data-slot=\"code-block-chevron\" className={cn(styles.chevron, classNames?.chevron)} />\n        </button>\n      )}\n    </figure>\n  );\n}\n\nexport default CodeBlock;\n"},{"path":"beamline/components/code-block/messages.ts","type":"registry:file","target":"src/ui/beamline/components/code-block/messages.ts","content":"import type { MessageDefinitions } from \"../../runtime/messages\";\n/** Built-in English copy; stable keys are the public translation API. */\nexport const english = {\n  code: \"Code\",\n  wrapLongLines: \"Wrap long lines\",\n  copy: \"Copy {filename}\",\n  copyCode: \"Copy code\",\n  showLess: \"Show less\",\n  showAllLines: \"Show all {length} lines\",\n} satisfies MessageDefinitions;\n"},{"path":"beamline/components/copy-button/copy-button.tsx","type":"registry:file","target":"src/ui/beamline/components/copy-button/copy-button.tsx","content":"\"use client\";\n\nimport { useComponentDefaults } from \"../../runtime/appearance\";\nimport type { CopyButtonDefaults } from \"./defaults\";\nexport type { CopyButtonDefaults } from \"./defaults\";\n\nimport { useEffect, useRef, useState, type CSSProperties, type Ref } from \"react\";\nimport { Check, Copy, X } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"motion/react\";\nimport { Button, type ButtonSize, type ButtonVariant } from \"../button/button\";\nimport { useComposedRef } from \"../../hooks/use-composed-ref\";\nimport { useMotionTokens } from \"../../hooks/use-motion-tokens\";\nimport { useReducedMotionSafe } from \"../../hooks/use-reduced-motion-safe\";\nimport { copyText } from \"../../lib/clipboard\";\nimport { useMessages, type MessageLabels } from \"../../runtime/localization\";\nimport { english } from \"./messages\";\nimport { cn } from \"../../lib/utils\";\n\nexport interface CopyButtonClassNames {\n  root?: string;\n  label?: string;\n  loader?: string;\n  iconFrame?: string;\n  icon?: string;\n  status?: string;\n}\n\nexport interface CopyButtonProps {\n  /** The text that lands on the clipboard. */\n  value: string;\n  /** What is copied, e.g. \"Copy API key\"; the accessible name when icon-only. Default \"Copy\". */\n  label?: string;\n  /** Per-instance copy; overrides UIRoot messages. */\n  labels?: MessageLabels<typeof english>;\n  /** The confirmation in your words, shown in place of the label and said to a screen reader: \"Link copied\". Default\n   *  \"Copied\" (heard as \"Copied to clipboard\"). */\n  copiedLabel?: string;\n  iconOnly?: boolean;\n  /** Any Button variant; \"plain\" is kept as an alias of ghost. Default secondary. */\n  variant?: ButtonVariant | \"plain\";\n  size?: ButtonSize;\n  disabled?: boolean;\n  /** Runs after a successful copy. */\n  onCopied?: () => void;\n  /** How long \"Copied\" stays, ms. Default 1600. */\n  timeout?: number;\n  className?: string;\n  classNames?: CopyButtonClassNames;\n  style?: CSSProperties;\n  ref?: Ref<HTMLButtonElement>;\n}\n\nconst ICONS = { idle: Copy, copied: Check, failed: X } as const;\n\n/**\n * Copies a value and confirms in place: the icon turns into a check and the label into \"Copied\" for a moment, and a\n * screen reader hears it. If the browser refuses, it says so instead of pretending.\n */\nexport function CopyButton({\n  value,\n  label,\n  labels,\n  copiedLabel,\n  iconOnly = false,\n  variant: variantProp,\n  size: sizeProp,\n  disabled,\n  onCopied,\n  timeout = 1600,\n  className,\n  classNames,\n  style,\n  ref,\n}: CopyButtonProps) {\n  const t = useMessages(\"copy-button\", english, labels);\n  const scopeDefaults = useComponentDefaults<CopyButtonDefaults>(\"copy-button\", [\"variant\", \"size\"]);\n  const variant = variantProp ?? scopeDefaults.variant ?? \"secondary\";\n  const size = sizeProp ?? scopeDefaults.size ?? \"sm\";\n\n  const [state, setState] = useState<\"idle\" | \"copied\" | \"failed\">(\"idle\");\n  const timer = useRef<ReturnType<typeof setTimeout>>(undefined);\n  const host = useRef<HTMLButtonElement>(null);\n  const attach = useComposedRef(host, ref);\n  const tokens = useMotionTokens(host);\n  const managedReduced = useReducedMotionSafe();\n  const reduced = tokens.reduced || managedReduced;\n  useEffect(() => () => clearTimeout(timer.current), []);\n  const run = async () => {\n    const ok = await copyText(value);\n    setState(ok ? \"copied\" : \"failed\");\n    if (ok) onCopied?.();\n    clearTimeout(timer.current);\n    timer.current = setTimeout(() => setState(\"idle\"), ok ? timeout : timeout + 1200);\n  };\n  const Icon = ICONS[state];\n  const text =\n    state === \"copied\" ? (copiedLabel ?? t(\"copied\")) : state === \"failed\" ? t(\"error\") : (label ?? t(\"label\"));\n  return (\n    <Button\n      ref={attach}\n      style={style}\n      classNames={classNames}\n      data-slot=\"copy-button\"\n      data-state={state}\n      // a result is never hidden while it shows: a copy button that waits for attention (.reveal) stays until it rests\n      data-shown={state !== \"idle\" ? \"\" : undefined}\n      variant={variant === \"plain\" ? \"ghost\" : variant}\n      iconOnly={iconOnly}\n      size={size}\n      disabled={disabled}\n      onClick={run}\n      aria-label={iconOnly ? text : undefined}\n      className={cn(state === \"copied\" && \"[&_svg]:text-green\", state === \"failed\" && \"[&_svg]:text-red\", className)}\n    >\n      <span\n        data-slot=\"copy-button-icon-frame\"\n        className={cn(\"relative grid size-4 place-items-center\", classNames?.iconFrame)}\n        aria-hidden\n      >\n        <AnimatePresence mode=\"popLayout\" initial={false}>\n          <motion.span\n            key={state}\n            data-slot=\"copy-button-icon\"\n            className={cn(\"grid place-items-center\", classNames?.icon)}\n            initial={reduced ? { opacity: 0 } : { opacity: 0, scale: 0.5, filter: \"blur(2px)\" }}\n            animate={{ opacity: 1, scale: 1, filter: \"blur(0px)\" }}\n            exit={reduced ? { opacity: 0 } : { opacity: 0, scale: 0.5, filter: \"blur(2px)\" }}\n            transition={{ duration: reduced ? 0 : tokens.duration.feedback }}\n          >\n            <Icon className=\"size-4\" />\n          </motion.span>\n        </AnimatePresence>\n      </span>\n      {!iconOnly && text}\n      <span data-slot=\"copy-button-status\" className={cn(\"sr-only\", classNames?.status)} role=\"status\">\n        {state === \"copied\" ? (copiedLabel ?? t(\"copiedDescription\")) : state === \"failed\" ? t(\"errorDescription\") : \"\"}\n      </span>\n    </Button>\n  );\n}\n\nexport default CopyButton;\n"},{"path":"beamline/components/copy-button/defaults.ts","type":"registry:file","target":"src/ui/beamline/components/copy-button/defaults.ts","content":"/** Pure allowed presentation defaults for CopyButton; no component or runtime imports. */\nexport interface CopyButtonDefaults {\n  variant?: \"primary\" | \"secondary\" | \"outline\" | \"ghost\" | \"danger\" | \"accent\" | \"plain\";\n  size?: \"sm\" | \"md\" | \"lg\" | \"touch\";\n}\n"},{"path":"beamline/components/copy-button/messages.ts","type":"registry:file","target":"src/ui/beamline/components/copy-button/messages.ts","content":"import type { MessageDefinitions } from \"../../runtime/messages\";\n/** Copy-button English defaults. Instance labels override these namespaced messages. */\nexport const english = {\n  label: \"Copy\",\n  copied: \"Copied\",\n  error: \"Copy failed\",\n  copiedDescription: \"Copied to clipboard\",\n  errorDescription: \"Could not copy; select the text and copy it yourself\",\n} satisfies MessageDefinitions;\n"},{"path":"beamline/components/file-diff/file-diff.module.css","type":"registry:file","target":"src/ui/beamline/components/file-diff/file-diff.module.css","content":"@layer properties, theme, base, components, utilities;\n@layer components {\n  /* File diff: a well (the inset role) that answers its room. One column with old and new numbers; below 24rem the\n     old numbers give way, below 20rem both do and long lines wrap; from 56rem (layout auto) old and new sit side by side,\n     each half wrapping its own lines. */\n  .root {\n    position: relative;\n    container-type: inline-size;\n    overflow: hidden;\n    border-radius: var(--pui-radius-card);\n  }\n  .header {\n    display: flex;\n    height: 40px;\n    align-items: center;\n    gap: var(--pui-space-3);\n    padding-inline: var(--pui-space-3) var(--pui-space-1-5);\n    border-bottom: 1px solid var(--pui-line-soft);\n  }\n  .filename {\n    display: flex;\n    min-width: 0;\n    flex: 1;\n    color: var(--pui-ink-2);\n    font-family: var(--pui-font-code);\n    font-size: var(--pui-text-xs);\n  }\n  .count {\n    flex-shrink: 0;\n    font-family: var(--pui-font-code);\n    font-size: var(--pui-text-xs);\n  }\n  .tools {\n    display: flex;\n    gap: var(--pui-space-0-5);\n  }\n  .tools:empty {\n    display: none;\n  }\n  .viewport {\n    overflow-x: auto;\n    padding-block: var(--pui-space-2);\n    text-align: start;\n  }\n  .grid {\n    display: grid;\n    min-width: max-content;\n  }\n  .root[data-wrap] .grid {\n    min-width: 0;\n  }\n  .run {\n    display: grid;\n  }\n\n  /* one column */\n  .row {\n    display: flex;\n    min-width: 0;\n    padding-inline-end: var(--pui-space-4);\n  }\n  .row[data-type=\"add\"] {\n    background: var(--pui-green-tint);\n  }\n  .row[data-type=\"del\"] {\n    background: var(--pui-red-tint);\n  }\n  .number {\n    width: 40px;\n    flex-shrink: 0;\n    padding-inline-end: var(--pui-space-2);\n    color: var(--pui-ink-3);\n    font-variant-numeric: tabular-nums;\n    text-align: end;\n    user-select: none;\n  }\n  /* a changed line carries a deeper tint in the number gutter, so a long diff can be scanned by its left edge */\n  :is(.row[data-type=\"add\"] .number, .number[data-type=\"add\"]) {\n    background: color-mix(in oklab, var(--pui-green) 16%, transparent);\n    color: var(--pui-ink-2);\n  }\n  :is(.row[data-type=\"del\"] .number, .number[data-type=\"del\"]) {\n    background: color-mix(in oklab, var(--pui-red) 16%, transparent);\n    color: var(--pui-ink-2);\n  }\n  .sign {\n    width: 20px;\n    flex-shrink: 0;\n    color: var(--pui-ink-3);\n    text-align: center;\n    user-select: none;\n  }\n  :is(.row[data-type=\"add\"] .sign, .sign[data-type=\"add\"]) {\n    color: var(--pui-green);\n  }\n  :is(.row[data-type=\"del\"] .sign, .sign[data-type=\"del\"]) {\n    color: var(--pui-red);\n  }\n  .code {\n    /* a blank line keeps its line, even when the gutter has given way */\n    min-height: 1lh;\n    min-width: 0;\n    color: var(--pui-ink);\n    white-space: pre;\n  }\n  .mark {\n    border-radius: calc(var(--pui-radius-chip) / 2);\n    color: var(--pui-ink);\n  }\n  .mark[data-change=\"add\"] {\n    background: color-mix(in oklab, var(--pui-green) 32%, transparent);\n  }\n  .mark[data-change=\"del\"] {\n    background: color-mix(in oklab, var(--pui-red) 32%, transparent);\n  }\n\n  /* wrapped: a long line breaks where it must and hangs its rest two characters under its own indent */\n  .root[data-wrap] .code {\n    flex: 1;\n    white-space: pre-wrap;\n    overflow-wrap: anywhere;\n    padding-inline-start: calc((var(--_indent, 0) + 2) * 1ch);\n    text-indent: calc((var(--_indent, 0) + 2) * -1ch);\n  }\n\n  /* side by side: each half has its number, sign and code; a half with no counterpart is a quiet blank */\n  .pair {\n    display: grid;\n    grid-template-columns: 40px 20px minmax(0, 1fr) 40px 20px minmax(0, 1fr);\n  }\n  .pair > :nth-child(4) {\n    box-shadow: inset 1px 0 0 var(--pui-line-soft);\n  }\n  .pair > .code {\n    padding-inline-end: var(--pui-space-3);\n  }\n  .pair > [data-type=\"add\"] {\n    background-color: var(--pui-green-tint);\n  }\n  .pair > [data-type=\"del\"] {\n    background-color: var(--pui-red-tint);\n  }\n  .pair > [data-type=\"none\"] {\n    background-color: var(--pui-hover);\n  }\n\n  .fold {\n    display: flex;\n    height: 28px;\n    align-items: center;\n    gap: var(--pui-space-2);\n    padding-inline-start: var(--pui-space-3);\n    background-color: var(--pui-hover);\n    color: var(--pui-ink-3);\n    font-size: var(--pui-text-2xs);\n    transition:\n      background-color var(--pui-motion-out) var(--pui-ease-default),\n      color var(--pui-motion-out) var(--pui-ease-default);\n  }\n  .fold:hover {\n    background-color: var(--pui-hover-2);\n    color: var(--pui-ink);\n    transition-duration: 0ms;\n  }\n  .fold > svg {\n    width: 14px;\n    height: 14px;\n  }\n\n  @container (max-width: 24rem) {\n    .row .old {\n      display: none;\n    }\n  }\n  @container (max-width: 20rem) {\n    .row .new {\n      display: none;\n    }\n  }\n}\n/* CSS-module composition only; visual rules stay in the component layer. */\n.viewport {\n  composes: text-code from global;\n}\n"},{"path":"beamline/components/file-diff/file-diff.tsx","type":"registry:file","target":"src/ui/beamline/components/file-diff/file-diff.tsx","content":"\"use client\";\n\nimport { useMessages } from \"../../runtime/localization\";\nimport { english as englishMessages } from \"./messages\";\nimport { Fragment, useMemo, useRef, useState, type CSSProperties, type ReactNode, type Ref } from \"react\";\nimport { ChevronsUpDown, Columns2, WrapText } from \"lucide-react\";\nimport { Button } from \"../button/button\";\nimport { PathName } from \"../code-block/code-block\";\nimport { CopyButton } from \"../copy-button/copy-button\";\nimport { useComposedRef } from \"../../hooks/use-composed-ref\";\nimport { useWidthAs } from \"../../hooks/use-content-width\";\nimport { diffLines, type DiffRow } from \"../../lib/diff\";\nimport { cn } from \"../../lib/utils\";\nimport { useControllable } from \"../../hooks/use-controllable\";\nimport { Tooltip } from \"../tooltip/tooltip\";\nimport styles from \"./file-diff.module.css\";\n\nexport type { DiffRow };\nexport type { DiffPiece } from \"../../lib/diff\";\n\nexport type FileDiffClassNames = Partial<\n  Record<\n    | \"root\"\n    | \"header\"\n    | \"filename\"\n    | \"added\"\n    | \"removed\"\n    | \"layout\"\n    | \"wrap\"\n    | \"viewport\"\n    | \"grid\"\n    | \"fold\"\n    | \"row\"\n    | \"oldNumber\"\n    | \"newNumber\"\n    | \"sign\"\n    | \"code\"\n    | \"mark\",\n    string\n  >\n>;\n\nexport type FileDiffLayout = \"auto\" | \"unified\" | \"split\";\n\nexport interface FileDiffProps {\n  ref?: Ref<HTMLElement>;\n  style?: CSSProperties;\n  classNames?: FileDiffClassNames;\n  /** Rows of a unified diff, or give before and after and it is worked out. */\n  diff?: DiffRow[];\n  before?: string;\n  after?: string;\n  filename?: string;\n  /** Unchanged runs longer than this fold into \"N unchanged lines\". Default 6. */\n  context?: number;\n  /** `auto` (default) follows the room: one column, and old and new side by side from 56rem. The header's split\n   *  button (from 40rem) changes it. */\n  layout?: FileDiffLayout;\n  /** Wrap long lines in the one-column view instead of scrolling sideways. Unset, it follows the room (wrapped below\n   *  20rem); the header's wrap button changes it. Side by side, lines always wrap. */\n  wrap?: boolean;\n  defaultWrap?: boolean;\n  onWrapChange?: (wrap: boolean) => void;\n  className?: string;\n}\n\ntype Segment = { rows: DiffRow[]; opened?: boolean } | { fold: number; rows: DiffRow[] };\ntype Pair = { left: DiffRow | null; right: DiffRow | null };\n\nconst SIGN: Record<DiffRow[\"type\"], string> = { add: \"+\", del: \"−\", ctx: \"\" };\n\n/** Old beside new: unchanged lines face themselves; a run of removed lines faces the added run that replaced it. */\nfunction pairsOf(rows: DiffRow[]): Pair[] {\n  const out: Pair[] = [];\n  for (let i = 0; i < rows.length;) {\n    if (rows[i].type === \"ctx\") {\n      out.push({ left: rows[i], right: rows[i] });\n      i++;\n      continue;\n    }\n    let j = i;\n    while (j < rows.length && rows[j].type !== \"ctx\") j++;\n    const dels = rows.slice(i, j).filter((r) => r.type === \"del\");\n    const adds = rows.slice(i, j).filter((r) => r.type === \"add\");\n    for (let k = 0; k < Math.max(dels.length, adds.length); k++)\n      out.push({ left: dels[k] ?? null, right: adds[k] ?? null });\n    i = j;\n  }\n  return out;\n}\n\nconst textOf = (row: DiffRow) => row.pieces.map((p) => p.text).join(\"\");\n/** Leading spaces, a tab counted as two: a wrapped line hangs its rest under its own indent. */\nconst indentOf = (row: DiffRow) => (textOf(row).match(/^[ \\t]*/)?.[0] ?? \"\").replace(/\\t/g, \"  \").length;\n\n/**\n * A change to one file: old and new line numbers, added and removed lines tinted, and inside a changed line only the\n * words that changed. It answers its room: one column with both numbers; narrower, the old numbers and then both give\n * way and long lines wrap under their own indent; from 56rem old and new sit side by side, each removed run facing the\n * run that replaced it. Long unchanged stretches fold and open in place; the header counts the change and copies the\n * new version.\n */\nexport function FileDiff({\n  diff,\n  before,\n  after,\n  filename,\n  context = 6,\n  layout = \"auto\",\n  wrap: wrapProp,\n  defaultWrap,\n  onWrapChange,\n  className,\n  classNames,\n  style,\n  ref,\n}: FileDiffProps) {\n  const i18n = useMessages(\"file-diff\", englishMessages);\n\n  const rows = useMemo(() => diff ?? diffLines(before ?? \"\", after ?? \"\"), [diff, before, after]);\n  const [opened, setOpened] = useState<Set<number>>(new Set());\n  const root = useRef<HTMLElement>(null);\n  const rootRef = useComposedRef(root, ref);\n  const room = useWidthAs(root, (w) => (w >= 896 ? 3 : w >= 640 ? 2 : w >= 320 ? 1 : 0), 1);\n  const [chosenSplit, setChosenSplit] = useState<boolean | undefined>(undefined);\n  const split = layout === \"split\" || (layout === \"auto\" && (chosenSplit ?? room === 3));\n  const [wrapValue, setWrapValue] = useControllable<boolean | undefined, boolean>(wrapProp, defaultWrap, onWrapChange);\n  const wrapOne = wrapValue ?? room === 0;\n  const wrap = split || wrapOne;\n  const canWrap = !split && (wrapProp === undefined || !!onWrapChange);\n  const toggleWrap = () => {\n    setWrapValue(!wrapOne);\n  };\n  const adds = rows.filter((r) => r.type === \"add\").length;\n  const dels = rows.filter((r) => r.type === \"del\").length;\n  const next = rows\n    .filter((r) => r.type !== \"del\")\n    .map(textOf)\n    .join(\"\\n\");\n  // runs of context rows longer than 2×context keep `context` lines at each edge; an opened run grows in place\n  const segments: Segment[] = [];\n  const push = (r: DiffRow) => {\n    const last = segments.at(-1);\n    if (last && !(\"fold\" in last) && !last.opened) last.rows.push(r);\n    else segments.push({ rows: [r] });\n  };\n  for (let i = 0; i < rows.length;) {\n    if (rows[i].type !== \"ctx\") {\n      push(rows[i++]);\n      continue;\n    }\n    let j = i;\n    while (j < rows.length && rows[j].type === \"ctx\") j++;\n    const run = rows.slice(i, j);\n    const keepStart = i === 0 ? 0 : context;\n    const keepEnd = j === rows.length ? 0 : context;\n    if (run.length > keepStart + keepEnd + 2) {\n      run.slice(0, keepStart).forEach(push);\n      const middle = run.slice(keepStart, run.length - keepEnd);\n      segments.push(opened.has(i) ? { rows: middle, opened: true } : { fold: i, rows: middle });\n      run.slice(run.length - keepEnd).forEach(push);\n    } else run.forEach(push);\n    i = j;\n  }\n  const indent = (row: DiffRow | null) => (wrap && row ? ({ \"--_indent\": indentOf(row) } as CSSProperties) : undefined);\n  const code = (row: DiffRow): ReactNode => (\n    <>\n      <span className=\"sr-only\">\n        {row.type === \"add\" ? i18n(\"addedPrefix\") : row.type === \"del\" ? i18n(\"removedPrefix\") : \"\"}\n      </span>\n      {row.pieces.map((p, i) => (\n        <Fragment key={i}>\n          {p.change ? (\n            <mark data-slot=\"file-diff-mark\" data-change={p.change} className={cn(styles.mark, classNames?.mark)}>\n              {p.text}\n            </mark>\n          ) : (\n            p.text\n          )}\n        </Fragment>\n      ))}\n    </>\n  );\n  const unified = (row: DiffRow, k: number) => (\n    <div data-slot=\"file-diff-row\" key={k} role=\"row\" data-type={row.type} className={cn(styles.row, classNames?.row)}>\n      <span\n        data-slot=\"file-diff-oldNumber\"\n        role=\"cell\"\n        aria-label={row.old ? i18n(\"oldLine\", { old: row.old }) : undefined}\n        className={cn(styles.number, styles.old, classNames?.oldNumber)}\n      >\n        {row.old ?? \"\"}\n      </span>\n      <span\n        data-slot=\"file-diff-newNumber\"\n        role=\"cell\"\n        aria-label={row.cur ? i18n(\"newLine\", { cur: row.cur }) : undefined}\n        className={cn(styles.number, styles.new, classNames?.newNumber)}\n      >\n        {row.cur ?? \"\"}\n      </span>\n      <span data-slot=\"file-diff-sign\" aria-hidden className={cn(styles.sign, classNames?.sign)}>\n        {SIGN[row.type]}\n      </span>\n      <span data-slot=\"file-diff-code\" role=\"cell\" className={cn(styles.code, classNames?.code)} style={indent(row)}>\n        {code(row)}\n      </span>\n    </div>\n  );\n  const side = (row: DiffRow | null, which: \"old\" | \"new\") => {\n    const n = row ? (which === \"old\" ? row.old : row.cur) : null;\n    const type = row?.type ?? \"none\";\n    return (\n      <>\n        <span\n          data-slot={which === \"old\" ? \"file-diff-oldNumber\" : \"file-diff-newNumber\"}\n          role=\"cell\"\n          data-type={type}\n          aria-label={n ? i18n(which === \"old\" ? \"oldLine\" : \"newLine\", { old: n, cur: n }) : undefined}\n          className={cn(styles.number, which === \"old\" ? classNames?.oldNumber : classNames?.newNumber)}\n        >\n          {n ?? \"\"}\n        </span>\n        <span data-slot=\"file-diff-sign\" aria-hidden data-type={type} className={cn(styles.sign, classNames?.sign)}>\n          {row ? SIGN[row.type] : \"\"}\n        </span>\n        <span\n          data-slot=\"file-diff-code\"\n          role=\"cell\"\n          data-type={type}\n          className={cn(styles.code, classNames?.code)}\n          style={indent(row)}\n        >\n          {row ? code(row) : \"\"}\n        </span>\n      </>\n    );\n  };\n  const paired = (pair: Pair, k: number) => (\n    <div data-slot=\"file-diff-row\" key={k} role=\"row\" className={cn(styles.pair, classNames?.row)}>\n      {side(pair.left, \"old\")}\n      {side(pair.right, \"new\")}\n    </div>\n  );\n  const body = (list: DiffRow[]) => (split ? pairsOf(list).map(paired) : list.map(unified));\n  return (\n    <figure\n      ref={rootRef}\n      style={style}\n      data-slot=\"file-diff\"\n      data-layout={split ? \"split\" : \"unified\"}\n      data-wrap={wrap || undefined}\n      className={cn(\"m-inset reveal-host\", styles.root, classNames?.root, className)}\n    >\n      <figcaption data-slot=\"file-diff-header\" className={cn(styles.header, classNames?.header)}>\n        <span data-slot=\"file-diff-filename\" className={cn(styles.filename, classNames?.filename)}>\n          {filename ? <PathName path={filename} /> : <bdi dir=\"ltr\">{i18n(\"changes\")}</bdi>}\n        </span>\n        <bdi data-slot=\"file-diff-added\" dir=\"ltr\" className={cn(styles.count, \"text-green\", classNames?.added)}>\n          +{adds}\n        </bdi>\n        <bdi data-slot=\"file-diff-removed\" dir=\"ltr\" className={cn(styles.count, \"text-red\", classNames?.removed)}>\n          −{dels}\n        </bdi>\n        {/* secondary: they wait for attention (hover or focus in the diff; always shown on touch) */}\n        <span className={cn(\"reveal\", styles.tools)}>\n          {layout === \"auto\" && room >= 2 && (\n            <Tooltip content={i18n(\"oldAndNewSideBySide\")}>\n              <Button\n                variant=\"ghost\"\n                size=\"sm\"\n                aria-label={i18n(\"oldAndNewSideBySide\")}\n                aria-pressed={split}\n                onClick={() => setChosenSplit(!split)}\n                data-slot=\"file-diff-layout\"\n                className={cn(\"aria-pressed:bg-accent-tint aria-pressed:text-accent-ink\", classNames?.layout)}\n              >\n                <Columns2 />\n              </Button>\n            </Tooltip>\n          )}\n          {canWrap && (\n            <Tooltip content={i18n(\"wrapLongLines\")}>\n              <Button\n                variant=\"ghost\"\n                size=\"sm\"\n                aria-label={i18n(\"wrapLongLines\")}\n                aria-pressed={wrapOne}\n                onClick={toggleWrap}\n                data-slot=\"file-diff-wrap\"\n                className={cn(\"aria-pressed:bg-accent-tint aria-pressed:text-accent-ink\", classNames?.wrap)}\n              >\n                <WrapText />\n              </Button>\n            </Tooltip>\n          )}\n        </span>\n        <CopyButton value={next} label={i18n(\"copyTheNewVersion\")} iconOnly variant=\"ghost\" />\n      </figcaption>\n      {/* a diff reads left to right in any page direction */}\n      <div\n        data-slot=\"file-diff-viewport\"\n        dir=\"ltr\"\n        className={cn(styles.viewport, classNames?.viewport)}\n        role=\"table\"\n        tabIndex={0}\n        aria-label={i18n(\"changesToAddedRemoved\", { value: filename ?? i18n(\"file\"), adds: adds, dels: dels })}\n      >\n        <div data-slot=\"file-diff-grid\" className={cn(styles.grid, classNames?.grid)}>\n          {segments.map((seg, k) =>\n            \"fold\" in seg ? (\n              <div data-slot=\"file-diff-fold-row\" role=\"row\" key={`f${seg.fold}`}>\n                <div data-slot=\"file-diff-fold-cell\" role=\"cell\">\n                  <button\n                    data-slot=\"file-diff-fold\"\n                    type=\"button\"\n                    onClick={(event) => {\n                      event.currentTarget\n                        .closest<HTMLElement>('[data-slot=\"file-diff-viewport\"]')\n                        ?.focus({ preventScroll: true });\n                      setOpened((s) => new Set(s).add(seg.fold));\n                    }}\n                    className={cn(styles.fold, \"w-full\", classNames?.fold)}\n                  >\n                    <ChevronsUpDown aria-hidden />\n                    {i18n(\"unchangedLines\", { count: seg.rows.length })}\n                  </button>\n                </div>\n              </div>\n            ) : seg.opened ? (\n              // a stretch that was folded opens to its own height while the rows below glide down\n              <div key={`o${k}`} className=\"enter-grow\">\n                <div className={styles.run}>{body(seg.rows)}</div>\n              </div>\n            ) : (\n              <Fragment key={`r${k}`}>{body(seg.rows)}</Fragment>\n            ),\n          )}\n        </div>\n      </div>\n    </figure>\n  );\n}\n\nexport default FileDiff;\n"},{"path":"beamline/components/file-diff/messages.ts","type":"registry:file","target":"src/ui/beamline/components/file-diff/messages.ts","content":"import type { MessageDefinitions } from \"../../runtime/messages\";\n/** Built-in English defaults; instance labels take precedence. */\nexport const english = {\n  oldLine: \"old line {old}\",\n  newLine: \"new line {cur}\",\n  changes: \"Changes\",\n  oldAndNewSideBySide: \"Old and new side by side\",\n  wrapLongLines: \"Wrap long lines\",\n  copyTheNewVersion: \"Copy the new version\",\n  changesToAddedRemoved: \"Changes to {value}: {adds} added, {dels} removed\",\n  unchangedLines: {\n    one: \"{count} unchanged line\",\n    other: \"{count} unchanged lines\",\n  },\n  file: \"the file\",\n  addedPrefix: \"added: \",\n  removedPrefix: \"removed: \",\n} satisfies MessageDefinitions;\n"},{"path":"beamline/components/spinner/defaults.ts","type":"registry:file","target":"src/ui/beamline/components/spinner/defaults.ts","content":"/** Pure allowed presentation defaults for Spinner; no component or runtime imports. */\nexport interface SpinnerDefaults {\n  /** Shared size setting; omit to inherit the surrounding scope or the component default. */\n  size?: \"sm\" | \"md\" | \"lg\";\n}\n"},{"path":"beamline/components/spinner/messages.ts","type":"registry:file","target":"src/ui/beamline/components/spinner/messages.ts","content":"import type { MessageDefinitions } from \"../../runtime/messages\";\n/** Built-in English copy; stable keys are the public translation API. */\nexport const english = {\n  label: \"Loading\",\n  done: \"Done\",\n  error: \"Failed\",\n} satisfies MessageDefinitions;\n"},{"path":"beamline/components/spinner/spinner.module.css","type":"registry:file","target":"src/ui/beamline/components/spinner/spinner.module.css","content":"@layer properties, theme, base, components, utilities;\n@layer components {\n  /* With a delay the ring waits unseen and then fades in, so quick work never flashes it; the result (a check or a\n     cross) always shows at once. */\n  .root[data-delay] {\n    animation: appear var(--pui-fade-in) var(--pui-ease-default) var(--_delay) both;\n  }\n  @keyframes appear {\n    from {\n      opacity: 0;\n    }\n  }\n  /* The turn runs on the compositor (a CSS animation on the svg box) and costs no JS per frame. When the work ends the\n     arc closes into a full ring over the standard duration, so stopping the turn is invisible, and the check or cross\n     draws itself a beat later. All timing comes from the motion tokens; the loop period follows --pui-motion-scale. */\n  .svg[data-state=\"loading\"] {\n    animation: turn calc(0.8s * var(--pui-motion-scale, 1) * var(--pui-motion-look, 1)) linear infinite;\n  }\n  .track {\n    stroke-opacity: 0.18;\n  }\n  .arc {\n    stroke-dasharray: 0.3 1;\n    transition: stroke-dasharray var(--pui-duration-standard) var(--pui-ease-enter);\n  }\n  .svg:not([data-state=\"loading\"]) .arc {\n    stroke-dasharray: 1 1;\n  }\n  .mark {\n    stroke-dasharray: 1 1;\n    animation: draw var(--pui-duration-standard) var(--pui-ease-enter) var(--pui-duration-fast) both;\n  }\n  @keyframes turn {\n    to {\n      transform: rotate(1turn);\n    }\n  }\n  @keyframes draw {\n    from {\n      stroke-dashoffset: 1;\n    }\n    to {\n      stroke-dashoffset: 0;\n    }\n  }\n  .svg[data-paused] {\n    animation-play-state: paused;\n  }\n  .svg[data-reduced],\n  .svg[data-reduced] .mark {\n    animation: none;\n  }\n  /* Reduction retains the loading/result shape without a decorative loop. */\n  @media (prefers-reduced-motion: reduce) {\n    .svg[data-state=\"loading\"],\n    .mark {\n      animation: none;\n    }\n  }\n}\n"},{"path":"beamline/components/spinner/spinner.tsx","type":"registry:file","target":"src/ui/beamline/components/spinner/spinner.tsx","content":"\"use client\";\n\nimport { useMessages } from \"../../runtime/localization\";\nimport { english as englishMessages } from \"./messages\";\nimport { useComponentDefaults } from \"../../runtime/appearance\";\nimport type { SpinnerDefaults } from \"./defaults\";\nexport type { SpinnerDefaults } from \"./defaults\";\n\nimport { useRef, type CSSProperties, type Ref } from \"react\";\nimport { useComposedRef } from \"../../hooks/use-composed-ref\";\nimport { useActive } from \"../../hooks/use-active\";\nimport { useMotionTokens } from \"../../hooks/use-motion-tokens\";\nimport { useReducedMotionSafe } from \"../../hooks/use-reduced-motion-safe\";\nimport { cn } from \"../../lib/utils\";\nimport styles from \"./spinner.module.css\";\n\nexport interface SpinnerClassNames {\n  /** Classes for the root part; no override by default. */\n  root?: string;\n  /** Classes for the svg part; no override by default. */\n  svg?: string;\n  /** Classes for the track part; no override by default. */\n  track?: string;\n  /** Classes for the arc part; no override by default. */\n  arc?: string;\n  /** Classes for the mark part; no override by default. */\n  mark?: string;\n}\n\nexport interface SpinnerProps {\n  /** `success` draws a check out of the ring, `error` a cross; both stop spinning. */\n  state?: \"loading\" | \"success\" | \"error\";\n  /** Control size within the supported size steps. Defaults to \"md\". */\n  size?: \"sm\" | \"md\" | \"lg\";\n  /** Accessible name while loading. */\n  label?: string;\n  /** ms to wait before it shows: work that ends sooner never flashes a spinner (about 300 is a good start). Default 0. */\n  delay?: number;\n  /** Classes on the documented visual root; no override by default. */\n  className?: string;\n  /** Receives the documented root element; no ref by default. */\n  ref?: Ref<HTMLSpanElement>;\n  /** Inline styles on the documented visual root; no inline overrides by default. */\n  style?: CSSProperties;\n  /** Classes for the named visual parts; no part overrides by default. */\n  classNames?: SpinnerClassNames;\n}\n\n/** Icon box from the foundation icon steps (§14.2): sm 14, md 16, lg 20. */\nconst PX = { sm: \"var(--pui-icon-sm)\", md: \"var(--pui-icon-md)\", lg: \"var(--pui-icon-lg)\" } as const;\n\n/**\n * A ring that spins while work runs and closes into a drawn check (or cross) when it ends — the result is a shape as\n * well as a colour. With reduced motion the loading ring holds still.\n */\nexport function Spinner({\n  state = \"loading\",\n  size: sizeProp,\n  label: labelProp,\n  delay = 0,\n  className,\n  classNames,\n  style,\n  ref,\n}: SpinnerProps) {\n  const i18n = useMessages(\"spinner\", englishMessages);\n  const label = labelProp === undefined ? i18n(\"label\") : labelProp;\n\n  const scopeDefaults = useComponentDefaults<SpinnerDefaults>(\"spinner\", [\"size\"]);\n  const size = sizeProp ?? scopeDefaults.size ?? \"md\";\n  const host = useRef<HTMLSpanElement>(null);\n  const attach = useComposedRef(host, ref);\n  const active = useActive(host);\n  const tokens = useMotionTokens(host);\n  const managedReduced = useReducedMotionSafe();\n  const reduced = tokens.reduced || managedReduced;\n\n  const box = PX[size];\n  const done = state !== \"loading\";\n  const tone = state === \"success\" ? \"text-green\" : state === \"error\" ? \"text-red\" : \"text-ink-2\";\n  return (\n    <span\n      ref={attach}\n      data-slot=\"spinner\"\n      data-state={state}\n      data-delay={delay > 0 && state === \"loading\" ? \"\" : undefined}\n      role={done ? \"img\" : \"status\"}\n      aria-label={state === \"success\" ? i18n(\"done\") : state === \"error\" ? i18n(\"error\") : label}\n      className={cn(styles.root, \"relative inline-grid shrink-0 place-items-center\", tone, classNames?.root, className)}\n      style={{ width: box, height: box, \"--_delay\": `${delay}ms`, ...style } as CSSProperties}\n    >\n      <svg\n        viewBox=\"0 0 24 24\"\n        width=\"100%\"\n        height=\"100%\"\n        fill=\"none\"\n        aria-hidden=\"true\"\n        data-state={state}\n        data-slot=\"spinner-svg\"\n        data-reduced={reduced ? \"\" : undefined}\n        data-paused={active ? undefined : \"\"}\n        className={cn(styles.svg, classNames?.svg)}\n      >\n        <circle\n          data-slot=\"spinner-track\"\n          className={cn(styles.track, classNames?.track)}\n          cx=\"12\"\n          cy=\"12\"\n          r=\"9.5\"\n          stroke=\"currentColor\"\n          strokeWidth=\"2.5\"\n        />\n        <circle\n          data-slot=\"spinner-arc\"\n          className={cn(styles.arc, classNames?.arc)}\n          cx=\"12\"\n          cy=\"12\"\n          r=\"9.5\"\n          pathLength={1}\n          stroke=\"currentColor\"\n          strokeWidth=\"2.5\"\n          strokeLinecap=\"round\"\n          transform=\"rotate(-90 12 12)\"\n        />\n        {state === \"success\" && (\n          <path\n            data-slot=\"spinner-mark\"\n            className={cn(styles.mark, classNames?.mark)}\n            d=\"M7.5 12.5l3 3 6-6.5\"\n            pathLength={1}\n            stroke=\"currentColor\"\n            strokeWidth=\"2.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n          />\n        )}\n        {state === \"error\" && (\n          <path\n            data-slot=\"spinner-mark\"\n            className={cn(styles.mark, classNames?.mark)}\n            d=\"M8.5 8.5l7 7M15.5 8.5l-7 7\"\n            pathLength={1}\n            stroke=\"currentColor\"\n            strokeWidth=\"2.5\"\n            strokeLinecap=\"round\"\n          />\n        )}\n      </svg>\n    </span>\n  );\n}\n\nexport default Spinner;\n"},{"path":"beamline/components/tooltip/defaults.ts","type":"registry:file","target":"src/ui/beamline/components/tooltip/defaults.ts","content":"/** Pure allowed presentation defaults for Tooltip; no component or runtime imports. */\nexport interface TooltipDefaults {\n  /** Shared align setting; omit to inherit the surrounding scope or the component default. */\n  align?: \"start\" | \"center\" | \"end\";\n}\n"},{"path":"beamline/components/tooltip/tooltip.module.css","type":"registry:file","target":"src/ui/beamline/components/tooltip/tooltip.module.css","content":"@layer properties, theme, base, components, utilities;\n@layer components {\n  /* A tooltip arrives from its control: it starts a step toward the control on the side it opens on and moves away\n     into place as it fades in (--pui-motion-in), so on any side it reads as coming out of what it explains. Inside a\n     group, the next one opens at once where the last one stood and travels to its own control (--pui-duration-fast),\n     while the last one goes in the arrival time: one tooltip moving along the toolbar, not two blinking. It leaves\n     over the departure fade. Reduced motion keeps the fades and drops the travel (--pui-motion-travel is 0). */\n  .tip {\n    --_dx: 0px;\n    --_dy: 0px;\n    --_tx: 0px;\n    --_ty: 0px;\n    z-index: var(--pui-z-tooltip);\n    transform-origin: var(--radix-tooltip-content-transform-origin);\n  }\n  .tip[data-side=\"top\"] {\n    --_dy: var(--pui-space-1);\n  }\n  .tip[data-side=\"bottom\"] {\n    --_dy: calc(-1 * var(--pui-space-1));\n  }\n  .tip[data-side=\"left\"] {\n    --_dx: var(--pui-space-1);\n  }\n  .tip[data-side=\"right\"] {\n    --_dx: calc(-1 * var(--pui-space-1));\n  }\n  .tip[data-state=\"delayed-open\"] {\n    animation: tip-in var(--pui-motion-in) var(--pui-ease-default);\n  }\n  .tip[data-state=\"instant-open\"] {\n    animation: tip-fade var(--pui-fade-in) var(--pui-ease-default);\n  }\n  .tip[data-state=\"instant-open\"][data-travel] {\n    animation: tip-travel var(--pui-duration-fast) var(--pui-ease-standard);\n  }\n  .tip[data-state=\"closed\"] {\n    animation: tip-out var(--pui-fade-out) var(--pui-ease-default) forwards;\n  }\n  .tip[data-grouped][data-state=\"closed\"] {\n    animation-duration: var(--pui-fade-in);\n  }\n}\n@keyframes tip-in {\n  from {\n    opacity: 0;\n    translate: calc(var(--_dx) * var(--pui-motion-travel, 1)) calc(var(--_dy) * var(--pui-motion-travel, 1));\n  }\n}\n@keyframes tip-fade {\n  from {\n    opacity: 0;\n  }\n}\n@keyframes tip-travel {\n  from {\n    translate: calc(var(--_tx) * var(--pui-motion-travel, 1)) calc(var(--_ty) * var(--pui-motion-travel, 1));\n  }\n}\n@keyframes tip-out {\n  to {\n    opacity: 0;\n  }\n}\n"},{"path":"beamline/components/tooltip/tooltip.tsx","type":"registry:file","target":"src/ui/beamline/components/tooltip/tooltip.tsx","content":"\"use client\";\n\nimport { useComponentDefaults } from \"../../runtime/appearance\";\nimport type { TooltipDefaults } from \"./defaults\";\nexport type { TooltipDefaults } from \"./defaults\";\n\nimport {\n  createContext,\n  useContext,\n  useRef,\n  useState,\n  type CSSProperties,\n  type Ref,\n  type ReactElement,\n  type ReactNode,\n  type RefObject,\n} from \"react\";\nimport * as RadixTooltip from \"@radix-ui/react-tooltip\";\nimport { usePortalContainer } from \"../../runtime/appearance\";\nimport { cn } from \"../../lib/utils\";\nimport styles from \"./tooltip.module.css\";\n\n/** How long after one tooltip of a group closes the next opens at once (and travels from where the last one was). */\nconst SKIP = 400;\n/** The last control of a group whose tooltip showed: its centre and when its tooltip opened or closed. */\ntype Last = { el: Element; x: number; y: number; at: number };\nconst Grouped = createContext<RefObject<Last | null> | null>(null);\n\n/** Tooltips inside a group open instantly one after another (a toolbar): only the first waits, and each next one\n *  travels from where the last one stood. */\nexport function TooltipGroup({\n  children,\n  delay = 500,\n}: {\n  /** Content rendered inside the component. Required. */\n  children: ReactNode;\n  /** Milliseconds before the first tooltip in the group opens. Defaults to 500. */\n  delay?: number;\n}) {\n  const last = useRef<Last | null>(null);\n  return (\n    <RadixTooltip.Provider delayDuration={delay} skipDelayDuration={SKIP}>\n      <Grouped.Provider value={last}>{children}</Grouped.Provider>\n    </RadixTooltip.Provider>\n  );\n}\n\nexport interface TooltipClassNames {\n  /** Classes for the root part; no override by default. */\n  root?: string;\n  /** Classes for the trigger part; no override by default. */\n  trigger?: string;\n  /** Classes for the shortcut part; no override by default. */\n  shortcut?: string;\n}\n\nexport interface TooltipProps {\n  /** What the control does; a few words. */\n  content: ReactNode;\n  /** A keyboard shortcut shown after the text, e.g. \"⌘B\". */\n  shortcut?: string;\n  /** The control: one element that can take a ref (a button). */\n  children: ReactElement;\n  /** Edge from which the panel or anchored content opens. Defaults to \"top\". */\n  side?: \"top\" | \"right\" | \"bottom\" | \"left\";\n  /** Alignment within the available space. Defaults to \"center\". */\n  align?: \"start\" | \"center\" | \"end\";\n  /** Open state, controlled or uncontrolled; the tooltip opens on hover and on keyboard focus either way. */\n  open?: boolean;\n  /** Initial open state when uncontrolled. Defaults to false. */\n  defaultOpen?: boolean;\n  /** Reports open state after a change; no callback by default. */\n  onOpenChange?: (open: boolean) => void;\n  /** Classes on the documented visual root; no override by default. */\n  className?: string;\n  /** Classes for the named visual parts; no part overrides by default. */\n  classNames?: TooltipClassNames;\n  /** Inline styles on the documented visual root; no inline overrides by default. */\n  style?: CSSProperties;\n  /** Receives the documented root element; no ref by default. */\n  ref?: Ref<HTMLDivElement>;\n}\n\n/**\n * Short supporting text for a control: it opens on hover after a moment and at once on keyboard focus, and closes on\n * Escape. It arrives out of its control on whichever side it opens. Inside a TooltipGroup the next one opens instantly\n * and travels from where the last one stood. Never put essential information or anything clickable in a tooltip.\n */\nexport function Tooltip({\n  content,\n  shortcut,\n  children,\n  side = \"top\",\n  align: alignProp,\n  open,\n  defaultOpen,\n  onOpenChange,\n  className,\n  classNames,\n  style,\n  ref,\n}: TooltipProps) {\n  const scopeDefaults = useComponentDefaults<TooltipDefaults>(\"tooltip\", [\"align\"]);\n  const align = alignProp ?? scopeDefaults.align ?? \"center\";\n\n  const group = useContext(Grouped);\n  const container = usePortalContainer();\n  const trigger = useRef<HTMLButtonElement>(null); // the control, whatever element it is (Radix types it as a button)\n  // in a group, how far this tooltip is from the last one, so it can start there and travel to its own control\n  const [travel, setTravel] = useState<{ x: number; y: number } | null>(null);\n  const change = (next: boolean) => {\n    const el = trigger.current;\n    if (group && el) {\n      const r = el.getBoundingClientRect();\n      const x = r.left + r.width / 2;\n      const y = r.top + r.height / 2;\n      const last = group.current;\n      if (next) {\n        const near = last && last.el !== el && performance.now() - last.at < SKIP;\n        setTravel(near ? { x: last.x - x, y: last.y - y } : null);\n        group.current = { el, x, y, at: Infinity };\n      } else if (last?.el === el) last.at = performance.now();\n    }\n    onOpenChange?.(next);\n  };\n  const tip = (\n    <RadixTooltip.Root open={open} defaultOpen={defaultOpen} onOpenChange={change}>\n      <RadixTooltip.Trigger ref={trigger} asChild data-slot=\"tooltip-trigger\" className={classNames?.trigger}>\n        {children}\n      </RadixTooltip.Trigger>\n      {container !== null && (\n        <RadixTooltip.Portal container={container}>\n          <RadixTooltip.Content\n            data-slot=\"tooltip\"\n            data-grouped={group ? \"\" : undefined}\n            data-travel={travel ? \"\" : undefined}\n            style={travel ? ({ \"--_tx\": `${travel.x}px`, \"--_ty\": `${travel.y}px`, ...style } as CSSProperties) : style}\n            ref={ref}\n            side={side}\n            align={align}\n            sideOffset={6}\n            collisionPadding={8}\n            className={cn(\n              \"flex max-w-[min(260px,calc(100vw-16px))] items-center gap-2 rounded-chip bg-tooltip px-2 py-1 text-xs leading-snug text-tooltip-fg shadow-overlay\",\n              styles.tip,\n              classNames?.root,\n              className,\n            )}\n          >\n            {content}\n            {shortcut && (\n              <kbd\n                data-slot=\"tooltip-shortcut\"\n                className={cn(\"font-mono text-2xs text-[var(--pui-tooltip-muted)]\", classNames?.shortcut)}\n              >\n                {shortcut}\n              </kbd>\n            )}\n          </RadixTooltip.Content>\n        </RadixTooltip.Portal>\n      )}\n    </RadixTooltip.Root>\n  );\n  return group ? tip : <RadixTooltip.Provider delayDuration={500}>{tip}</RadixTooltip.Provider>;\n}\n\nexport default Tooltip;\n"},{"path":"beamline/hooks/use-active.ts","type":"registry:file","target":"src/ui/beamline/hooks/use-active.ts","content":"\"use client\";\n\nimport { useContext, useEffect, useState, type RefObject } from \"react\";\n\nimport { ActivityContext } from \"../runtime/activity\";\n\n/**\n * Whether an animated element should be doing work: it is (nearly) on screen and the tab is visible. Every\n * continuously animated component gates its loop, shader or CSS animation on this, so an effect scrolled out of view,\n * behind another tab, or in a collapsed panel costs nothing. One IntersectionObserver per element; no polling.\n */\nexport function useActive(\n  ref: RefObject<Element | null>,\n  {\n    margin = \"120px\",\n    key,\n    idle = true,\n  }: {\n    margin?: string;\n    /** Re-attach when this changes (an element that mounts later). */\n    key?: unknown;\n    /** Pause continuous work at the host idle boundary. Finite sequences set false so they finish. */\n    idle?: boolean;\n  } = {},\n) {\n  const active = useContext(ActivityContext);\n  const [inView, setInView] = useState(false);\n  const [visible, setVisible] = useState(\n    () => typeof document === \"undefined\" || document.visibilityState === \"visible\",\n  );\n  useEffect(() => {\n    const el = ref.current;\n    if (!el || typeof IntersectionObserver === \"undefined\") {\n      setInView(true);\n      return;\n    }\n    const io = new IntersectionObserver(([e]) => setInView(e.isIntersecting), { rootMargin: margin });\n    io.observe(el);\n    return () => io.disconnect();\n  }, [ref, margin, key]);\n  useEffect(() => {\n    const on = () => setVisible(document.visibilityState === \"visible\");\n    document.addEventListener(\"visibilitychange\", on);\n    return () => document.removeEventListener(\"visibilitychange\", on);\n  }, []);\n  return (!idle || active) && inView && visible;\n}\n"},{"path":"beamline/hooks/use-composed-ref.ts","type":"registry:file","target":"src/ui/beamline/hooks/use-composed-ref.ts","content":"\"use client\";\n\nimport { useCallback, type Ref, type RefObject } from \"react\";\n\n/** Keep a control's measurement/focus ref and the caller's ref, including React 19 callback cleanup. */\nexport function useComposedRef<T>(internal: RefObject<T | null>, external?: Ref<T>) {\n  return useCallback(\n    (node: T | null) => {\n      internal.current = node;\n      if (typeof external === \"function\") {\n        const cleanup = external(node);\n        if (typeof cleanup === \"function\")\n          return () => {\n            internal.current = null;\n            cleanup();\n          };\n      } else if (external) external.current = node;\n    },\n    [internal, external],\n  );\n}\n"},{"path":"beamline/hooks/use-content-width.ts","type":"registry:file","target":"src/ui/beamline/hooks/use-content-width.ts","content":"\"use client\";\n\nimport { useLayoutEffect, useRef, useState, type RefObject } from \"react\";\n\nconst same = (width: number) => width;\n\n/**\n * The width of an element's content box, kept current with a ResizeObserver. It is first read in a layout effect, so\n * a layout chosen by width (a narrow mode, a chart's scale) is right in the first frame instead of jumping a frame\n * later. `fallback` stands in on the server and until the element exists; `key` re-measures when the element is\n * replaced (one that exists only in one view).\n */\nexport function useContentWidth(ref: RefObject<HTMLElement | null>, fallback: number, key?: unknown): number {\n  return useWidthAs(ref, same, fallback, key);\n}\n\n/**\n * The same width, kept as what a part needs from it (`(w) => w < 640` for a narrow mode): the part renders again only\n * when that changes, not at every pixel.\n */\nexport function useWidthAs<T>(\n  ref: RefObject<HTMLElement | null>,\n  read: (width: number) => T,\n  fallback: T,\n  key?: unknown,\n): T {\n  const [value, setValue] = useState(fallback);\n  const latest = useRef(read);\n  latest.current = read;\n  useLayoutEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n    // the computed width is fractional and ignores transforms, like the observer's content box\n    const s = getComputedStyle(el);\n    const edges = [\"paddingLeft\", \"paddingRight\", \"borderLeftWidth\", \"borderRightWidth\"] as const;\n    const box = parseFloat(s.width);\n    const outer = Number.isFinite(box) ? box : el.offsetWidth;\n    const inner =\n      Number.isFinite(box) && s.boxSizing !== \"border-box\" ? 0 : edges.reduce((w, k) => w + parseFloat(s[k]), 0);\n    setValue(latest.current(outer - inner));\n    if (typeof ResizeObserver === \"undefined\") return;\n    const ro = new ResizeObserver(([e]) => setValue(latest.current(e.contentRect.width)));\n    ro.observe(el);\n    return () => ro.disconnect();\n  }, [ref, key]);\n  return value;\n}\n\n/**\n * The height of an element's content box, kept current like `useContentWidth`: for parts that lay out at whatever\n * height their parent gives them (a chart, a map or a book that fills its panel). Read in a layout effect, so the first\n * frame is right; with `enabled` false nothing is measured and the fallback stands.\n */\nexport function useContentHeight(\n  ref: RefObject<HTMLElement | null>,\n  fallback: number,\n  key?: unknown,\n  enabled = true,\n): number {\n  const [value, setValue] = useState(fallback);\n  useLayoutEffect(() => {\n    const el = ref.current;\n    if (!enabled || !el) return;\n    const s = getComputedStyle(el);\n    setValue(el.clientHeight - (parseFloat(s.paddingTop) || 0) - (parseFloat(s.paddingBottom) || 0));\n    if (typeof ResizeObserver === \"undefined\") return;\n    const ro = new ResizeObserver(([e]) => setValue(e.contentRect.height));\n    ro.observe(el);\n    return () => ro.disconnect();\n  }, [ref, key, enabled]);\n  return enabled ? value : fallback;\n}\n"},{"path":"beamline/hooks/use-controllable.ts","type":"registry:file","target":"src/ui/beamline/hooks/use-controllable.ts","content":"\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\n\n/**\n * One value in both forms (rules/CONVENTIONS.md): the prop while it is passed, otherwise the element's own copy that\n * starts at `initial` (a value, or a function that makes it once). The setter takes a value or an updater; an updater\n * reads the latest value even twice in one event, and every change reaches `onChange`, controlled or not.\n * `Next` is what a change can carry when it is narrower than the state: a range that starts empty (null) but is only\n * ever changed to a range.\n */\nexport function useControllable<T, Next extends T = T>(\n  value: T | undefined,\n  initial: T | (() => T),\n  onChange?: (next: Next) => void,\n) {\n  const [own, setOwn] = useState<T>(initial);\n  const current = value !== undefined ? value : own;\n  const latest = useRef(current);\n  latest.current = current;\n  const set = useCallback(\n    (next: Next | ((was: T) => Next)) => {\n      const resolved = typeof next === \"function\" ? (next as (was: T) => Next)(latest.current) : next;\n      latest.current = resolved;\n      if (value === undefined) setOwn(resolved);\n      onChange?.(resolved);\n    },\n    [value, onChange],\n  );\n  return [current, set] as const;\n}\n"},{"path":"beamline/hooks/use-media-query.ts","type":"registry:file","target":"src/ui/beamline/hooks/use-media-query.ts","content":"\"use client\";\n\nimport { useSyncExternalStore } from \"react\";\n\n// one MediaQueryList per query: React reads the snapshot on every render, and a part that renders thirty times a\n// second (a playing audio player's clock) would otherwise ask the browser for a new list each time\nconst lists = new Map<string, MediaQueryList>();\nconst list = (query: string) => {\n  if (typeof window === \"undefined\" || typeof window.matchMedia !== \"function\") return undefined;\n  let m = lists.get(query);\n  if (!m) lists.set(query, (m = window.matchMedia(query)));\n  return m;\n};\n\n/** True while the media query matches; false on the server and in the first client render of SSR output. */\nexport function useMediaQuery(query: string): boolean {\n  return useSyncExternalStore(\n    (notify) => {\n      const m = list(query);\n      m?.addEventListener(\"change\", notify);\n      return () => m?.removeEventListener(\"change\", notify);\n    },\n    () => list(query)?.matches ?? false,\n    () => false,\n  );\n}\n\n/** Phone-width layouts: below 768px. */\nexport const usePhone = () => useMediaQuery(\"(max-width: 767px)\");\n"},{"path":"beamline/hooks/use-motion-tokens.ts","type":"registry:file","target":"src/ui/beamline/hooks/use-motion-tokens.ts","content":"\"use client\";\n\nimport { useMemo, type RefObject } from \"react\";\nimport { useAppearanceRoot } from \"../runtime/appearance\";\nimport { useResolvedAppearance } from \"./use-resolved-appearance\";\nimport { motionAt, motionEase, motionTokens, type MotionTokens } from \"../lib/motion-tokens\";\nimport { semanticMotion } from \"../lib/motion-source\";\n\nconst durationToken = (name: string) =>\n  name === \"exit\" ? \"motion-out\" : name === \"in\" || name === \"out\" ? `motion-${name}` : `duration-${name}`;\nconst easeToken = (name: string) => `ease-${name === \"inOut\" ? \"in-out\" : name}`;\nconst REQUEST = {\n  times: [\n    ...Object.keys(semanticMotion.duration).map(durationToken),\n    ...Object.keys(semanticMotion.spring).map((name) => `spring-${name}`),\n    ...Object.keys(semanticMotion.stagger).map((name) => `stagger-${name}`),\n    \"fade-in\",\n    \"fade-out\",\n  ],\n  easings: [...Object.keys(semanticMotion.ease).map(easeToken), \"ease-spring\", \"ease-spring-bounce\", \"ease-overshoot\"],\n  numbers: [\"motion-scale\", \"motion-look\", \"press-scale\"],\n  // timing only: the host's own type would make every control resolve differently from the page around it\n  typography: false,\n};\n\n/**\n * Resolve each timing at the actual host; omitted host uses the nearest managed root. The first/server snapshot is\n * the pure foundation default. Local CSS values, scope changes, font readiness, OS preference and explicit refresh\n * update the same consumer; no document-wide first-root fallback or persistent global listener.\n */\nexport function useMotionTokens(hostRef?: RefObject<HTMLElement | null>): MotionTokens & {\n  ready: boolean;\n  /** Reduced or none: movement, springs and staggers are instant. */\n  reduced: boolean;\n  mode: \"full\" | \"reduced\" | \"none\";\n  /** Opacity-only arrival and departure (seconds): kept under reduced, instant under none (APPEARANCE A06). */\n  fade: { in: number; out: number };\n} {\n  const root = useAppearanceRoot();\n  const scopeRef = useMemo(() => ({ current: root }), [root]);\n  const resolved = useResolvedAppearance(hostRef ?? scopeRef, REQUEST);\n  return useMemo(() => {\n    if (!resolved.ready)\n      return {\n        ...motionTokens,\n        ready: false,\n        reduced: false,\n        mode: \"full\" as const,\n        fade: { in: motionTokens.duration.in, out: motionTokens.duration.out },\n      };\n    const reduced = resolved.reducedMotion;\n    const speed = (resolved.numbers[\"motion-scale\"] ?? 1) * (resolved.numbers[\"motion-look\"] ?? 1);\n    const fade =\n      resolved.motionMode === \"none\"\n        ? { in: 0.001, out: 0.001 }\n        : {\n            in: resolved.times[\"fade-in\"] ?? semanticMotion.duration.in * speed,\n            out: resolved.times[\"fade-out\"] ?? semanticMotion.duration.out * speed,\n          };\n    const factor = reduced ? 0.001 / semanticMotion.duration.standard : speed;\n    const base = motionAt(factor);\n    const duration = Object.fromEntries(\n      Object.entries(base.duration).map(([name, fallback]) => [\n        name,\n        reduced ? 0.001 : (resolved.times[durationToken(name)] ?? fallback),\n      ]),\n    ) as MotionTokens[\"duration\"];\n    const stagger = Object.fromEntries(\n      Object.entries(base.stagger).map(([name, fallback]) => [\n        name,\n        reduced ? 0 : (resolved.times[`stagger-${name}`] ?? fallback),\n      ]),\n    ) as MotionTokens[\"stagger\"];\n    const ease = Object.fromEntries(\n      Object.entries(base.ease).map(([name, fallback]) => [\n        name,\n        motionEase(resolved.easings[easeToken(name)], fallback),\n      ]),\n    ) as MotionTokens[\"ease\"];\n    const spring = { ...base.spring };\n    for (const name of Object.keys(semanticMotion.spring) as (keyof typeof semanticMotion.spring)[])\n      spring[name] = {\n        ...base.spring[name],\n        visualDuration: reduced ? 0.001 : (resolved.times[`spring-${name}`] ?? base.spring[name].visualDuration),\n        bounce: reduced ? 0 : base.spring[name].bounce,\n      };\n    return {\n      ...base,\n      ready: true,\n      reduced,\n      mode: resolved.motionMode,\n      fade,\n      duration,\n      stagger,\n      ease,\n      spring,\n      pressScale: resolved.numbers[\"press-scale\"] ?? base.pressScale,\n      css: {\n        spring: resolved.easings[\"ease-spring\"] ?? base.css.spring,\n        springBounce: resolved.easings[\"ease-spring-bounce\"] ?? base.css.springBounce,\n        overshoot: resolved.easings[\"ease-overshoot\"] ?? base.css.overshoot,\n      },\n    };\n  }, [resolved]);\n}\n"},{"path":"beamline/hooks/use-reduced-motion-safe.ts","type":"registry:file","target":"src/ui/beamline/hooks/use-reduced-motion-safe.ts","content":"\"use client\";\n\nimport { useMediaQuery } from \"./use-media-query\";\nimport { useScopeMotion } from \"../runtime/appearance\";\n\n/** Managed motion policy plus the OS preference, with a stable server snapshot. */\nexport function useReducedMotionSafe() {\n  const requested = useScopeMotion();\n  const os = useMediaQuery(\"(prefers-reduced-motion: reduce)\");\n  return requested !== \"auto\" || os;\n}\n"},{"path":"beamline/lib/clipboard.ts","type":"registry:file","target":"src/ui/beamline/lib/clipboard.ts","content":"/**\n * Copies text to the clipboard and says whether it worked. Uses the async Clipboard API where the page is allowed to\n * (a secure context with focus), and falls back to a hidden textarea and execCommand elsewhere (http previews,\n * embedded frames).\n */\nexport async function copyText(text: string): Promise<boolean> {\n  try {\n    if (navigator.clipboard && window.isSecureContext) {\n      await navigator.clipboard.writeText(text);\n      return true;\n    }\n  } catch {\n    /* fall through to the textarea */\n  }\n  if (typeof document === \"undefined\") return false;\n  const area = document.createElement(\"textarea\");\n  const previous = document.activeElement as HTMLElement | null;\n  try {\n    area.value = text;\n    area.setAttribute(\"readonly\", \"\");\n    area.style.cssText = \"position:fixed;top:0;left:0;opacity:0;pointer-events:none\";\n    document.body.appendChild(area);\n    area.select();\n    return document.execCommand?.(\"copy\") ?? false;\n  } catch {\n    return false;\n  } finally {\n    area.remove();\n    previous?.focus({ preventScroll: true });\n  }\n}\n"},{"path":"beamline/lib/diff.ts","type":"registry:file","target":"src/ui/beamline/lib/diff.ts","content":"/**\n * Line and word diffs for code views. Longest-common-subsequence on lines, then on words inside each changed pair, so\n * a small edit tints only the words that changed. Meant for files of a few thousand lines, not whole repositories.\n */\nexport interface DiffPiece {\n  text: string;\n  change?: \"add\" | \"del\";\n}\n/** One row of a unified diff: line numbers in the old and new file (null where the line does not exist). */\nexport interface DiffRow {\n  old: number | null;\n  cur: number | null;\n  type: \"ctx\" | \"add\" | \"del\";\n  pieces: DiffPiece[];\n}\n\n/** Matched index pairs of a longest common subsequence. The equal items the two lists start with match outright (the\n *  walk below would match them first anyway), so the table covers only the rest; items are compared as integers. */\nfunction lcs<T>(a: T[], b: T[]): [number, number][] {\n  let start = 0;\n  while (start < a.length && start < b.length && a[start] === b[start]) start++;\n  const pairs: [number, number][] = [];\n  for (let k = 0; k < start; k++) pairs.push([k, k]);\n  // each distinct item becomes a number: comparing long lines that share a beginning walked every character\n  const ids = new Map<T, number>();\n  const id = (item: T) => {\n    let known = ids.get(item);\n    if (known === undefined) ids.set(item, (known = ids.size));\n    return known;\n  };\n  const x = Int32Array.from(a.slice(start), id),\n    y = Int32Array.from(b.slice(start), id);\n  const n = x.length,\n    m = y.length;\n  const dp = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));\n  for (let i = n - 1; i >= 0; i--) {\n    const row = dp[i],\n      below = dp[i + 1],\n      xi = x[i];\n    for (let j = m - 1; j >= 0; j--) row[j] = xi === y[j] ? below[j + 1] + 1 : Math.max(below[j], row[j + 1]);\n  }\n  let i = 0,\n    j = 0;\n  while (i < n && j < m) {\n    if (x[i] === y[j]) {\n      pairs.push([start + i, start + j]);\n      i++;\n      j++;\n    } else if (dp[i + 1][j] >= dp[i][j + 1]) i++;\n    else j++;\n  }\n  return pairs;\n}\n\n/** Word-level pieces for a removed line and the line that replaced it. */\nexport function wordDiff(before: string, after: string): { del: DiffPiece[]; add: DiffPiece[] } {\n  const a = before.split(/(\\s+|\\b)/).filter(Boolean),\n    b = after.split(/(\\s+|\\b)/).filter(Boolean);\n  const pairs = lcs(a, b);\n  const del: DiffPiece[] = [],\n    add: DiffPiece[] = [];\n  const push = (list: DiffPiece[], text: string, change?: \"add\" | \"del\") => {\n    const last = list[list.length - 1];\n    if (last && last.change === change) last.text += text;\n    else list.push({ text, change });\n  };\n  let i = 0,\n    j = 0;\n  for (const [pi, pj] of [...pairs, [a.length, b.length] as [number, number]]) {\n    while (i < pi) push(del, a[i++], \"del\");\n    while (j < pj) push(add, b[j++], \"add\");\n    if (pi < a.length) {\n      push(del, a[pi]);\n      push(add, b[pj]);\n    }\n    i = pi + 1;\n    j = pj + 1;\n  }\n  return { del, add };\n}\n\n/** A unified diff between two texts, with word-level changes inside paired lines. */\nexport function diffLines(before: string, after: string): DiffRow[] {\n  const a = before.split(\"\\n\"),\n    b = after.split(\"\\n\");\n  const pairs = lcs(a, b);\n  const rows: DiffRow[] = [];\n  let i = 0,\n    j = 0;\n  for (const [pi, pj] of [...pairs, [a.length, b.length] as [number, number]]) {\n    const dels = a.slice(i, pi),\n      adds = b.slice(j, pj);\n    const paired = Math.min(dels.length, adds.length);\n    const words = Array.from({ length: paired }, (_, k) => wordDiff(dels[k], adds[k]));\n    dels.forEach((line, k) =>\n      rows.push({ old: i + k + 1, cur: null, type: \"del\", pieces: k < paired ? words[k].del : [{ text: line }] }),\n    );\n    adds.forEach((line, k) =>\n      rows.push({ old: null, cur: j + k + 1, type: \"add\", pieces: k < paired ? words[k].add : [{ text: line }] }),\n    );\n    if (pi < a.length) rows.push({ old: pi + 1, cur: pj + 1, type: \"ctx\", pieces: [{ text: a[pi] }] });\n    i = pi + 1;\n    j = pj + 1;\n  }\n  return rows;\n}\n"},{"path":"beamline/lib/highlight.ts","type":"registry:file","target":"src/ui/beamline/lib/highlight.ts","content":"import { common, createLowlight } from \"lowlight\";\n\n/** One syntax highlighter for the whole system (editor code blocks and model answers), coloured from tokens. */\nexport const lowlight: ReturnType<typeof createLowlight> = createLowlight(common);\n\nconst PALETTE: [RegExp, string][] = [\n  [/comment|quote/, \"var(--pui-ink-3)\"],\n  [/keyword|selector-tag|built_in|type|doctag/, \"var(--pui-violet)\"],\n  [/string|attr|template-tag|regexp|addition/, \"var(--pui-green)\"],\n  [/number|literal|symbol|bullet/, \"var(--pui-orange)\"],\n  [/title|section|function/, \"var(--pui-blue)\"],\n  [/meta|tag|name|deletion/, \"var(--pui-red)\"],\n];\nconst colorOf = (classes: string[]) => {\n  const c = classes.join(\" \");\n  return PALETTE.find(([re]) => re.test(c))?.[1];\n};\n\nexport type HastNode = { type: string; value?: string; properties?: { className?: string[] }; children?: HastNode[] };\ntype Token = { content: string; htmlStyle?: Record<string, string> };\n\n/** Flattens lowlight's tree into lines of coloured tokens (the shape Streamdown renders). */\nexport function toLines(root: HastNode) {\n  const lines: Token[][] = [[]];\n  const walk = (node: HastNode, classes: string[]) => {\n    if (node.type === \"text\") {\n      const color = colorOf(classes);\n      node.value!.split(\"\\n\").forEach((part, i) => {\n        if (i) lines.push([]);\n        if (part) lines[lines.length - 1].push(color ? { content: part, htmlStyle: { color } } : { content: part });\n      });\n      return;\n    }\n    const next = node.properties?.className ? [...classes, ...node.properties.className] : classes;\n    node.children?.forEach((c) => walk(c, next));\n  };\n  walk(root, []);\n  return lines;\n}\n\n/** Lines of coloured tokens for a code view; plain text when the language is unknown. */\nexport function highlightLines(code: string, language?: string): Token[][] {\n  const tree =\n    language && lowlight.registered(language)\n      ? lowlight.highlight(language, code)\n      : { type: \"root\", children: [{ type: \"text\", value: code }] };\n  return toLines(tree as HastNode);\n}\nexport type { Token as HighlightToken };\n"},{"path":"beamline/lib/utils.ts","type":"registry:file","target":"src/ui/beamline/lib/utils.ts","content":"import { clsx, type ClassValue } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\n/* The type roles (utilities.css) and the 2xs step are sizes, not text colours: without this, tailwind-merge reads\n   `text-overline` as a colour and drops it next to `text-ink-3`. A role and a size step still replace each other.\n   Likewise `font-title` is the look's title weight, not a family: read as a family it dropped `font-display` beside it,\n   and every merged page title lost the look's display face. */\nconst twMerge = extendTailwindMerge({\n  extend: {\n    classGroups: {\n      \"font-weight\": [{ font: [\"title\"] }],\n      \"font-family\": [{ font: [\"display\", \"numeric\", \"figure\"] }],\n      \"font-size\": [\n        {\n          text: [\n            \"2xs\",\n            \"display\",\n            \"title\",\n            \"heading\",\n            \"subheading\",\n            \"body\",\n            \"body-sm\",\n            \"reading\",\n            \"label\",\n            \"caption\",\n            \"overline\",\n            \"numeric\",\n            \"code\",\n          ],\n        },\n      ],\n    },\n  },\n});\n\n/** Merge class names, letting later Tailwind utilities win over earlier ones. */\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n\n/** A small seeded random generator (LCG): the same seed gives the same sequence, for stable sample data and jitter. */\nexport function rng(seed = 1) {\n  let s = seed >>> 0 || 1;\n  return () => (s = (s * 1664525 + 1013904223) >>> 0) / 2 ** 32;\n}\n\nexport const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\n"}],"docs":"File diff is in src/ui/beamline/components/file-diff/file-diff.tsx. The source is in src/ui/beamline (ui/beamline in a project without src/). After Tailwind, import its styles/beamline.css once, then styles/look-prism.css in your app stylesheet (use relative paths). Keep the shared base before all chosen looks. Wrap your app in <UIRoot preset={prism}> with UIRoot from its runtime/appearance and prism from its looks/prism.  shadcn replaces files when asked; use the Beamline managed-source CLI from the start for three-way upgrades that preserve edits.","meta":{"free":true,"version":"0.2.0-rc.42","library":"https://beamline.io/components/file-diff"}}