{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"command-menu","type":"registry:component","title":"Command menu","description":"Search-first list of every action: ranked matching, groups, keycap shortcuts and nested pages; inline as CommandPalette or in a ⌘K dialog as CommandMenu.","categories":["overlays"],"dependencies":["@radix-ui/react-dialog@1.1.23","@radix-ui/react-direction@1.1.4","clsx@^2.1.1","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/_shared/glide.tsx","type":"registry:file","target":"src/ui/beamline/components/_shared/glide.tsx","content":"\"use client\";\n\nimport { useEffect, useLayoutEffect, useRef, useState, type RefObject } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport overlay from \"./overlay.module.css\";\n\n/**\n * The gliding highlight for a list whose active row is tracked in state (command menu, listbox): one plate travels to\n * the active row for the arrow keys and the pointer alike, over --pui-duration-fast. It appears in place (no travel)\n * the first time and whenever `reset` changes (a new page or a new query), so typing never sends it flying.\n * The list must be the plate's offset parent (position: relative); rows stay above it (overlay.item is z-index 1).\n */\nexport function GlidePlate({\n  list,\n  selector,\n  reset,\n  axis = \"y\",\n  className,\n}: {\n  list: RefObject<HTMLElement | null>;\n  /** Finds the active row inside `list`; null when nothing is active. */\n  selector: string | null;\n  reset?: unknown;\n  /** \"y\" for a list (the plate spans the width); \"xy\" for a row or grid of cells (it takes each cell's size). */\n  axis?: \"y\" | \"xy\";\n  className?: string;\n}) {\n  const [plate, setPlate] = useState<{ x: number; y: number; w: number; h: number; jump: boolean } | null>(null);\n  const lastReset = useRef(reset);\n  const self = useRef<HTMLSpanElement>(null);\n  // eslint-disable-next-line react-hooks/exhaustive-deps -- every render on purpose; setPlate bails out when unchanged\n  useLayoutEffect(() => {\n    // the list holds the plate, and React attaches a parent's ref only after its children's layout effects: on the\n    // first commit the list is the plate's own parent (it placed itself only when something rendered again later)\n    const host = list.current ?? self.current?.parentElement;\n    const row = selector ? host?.querySelector<HTMLElement>(selector) : null;\n    const fresh = lastReset.current !== reset;\n    lastReset.current = reset;\n    if (!row) {\n      setPlate((p) => (p && p.h ? { ...p, h: 0 } : p));\n      return;\n    }\n    const x = row.offsetLeft,\n      y = row.offsetTop,\n      w = row.offsetWidth,\n      h = row.offsetHeight;\n    setPlate((p) =>\n      p && p.x === x && p.y === y && p.w === w && p.h === h && !fresh ? p : { x, y, w, h, jump: fresh || !p?.h },\n    );\n    // every render: rows can arrive or change height without the selector changing; an unchanged place keeps the state\n  });\n  return (\n    <span\n      ref={self}\n      aria-hidden\n      data-slot=\"list-highlight\"\n      className={cn(overlay.glide, className)}\n      data-on={plate?.h ? \"\" : undefined}\n      data-jump={plate?.jump ? \"\" : undefined}\n      data-axis={axis}\n      style={\n        !plate\n          ? undefined\n          : axis === \"y\"\n            ? { transform: `translateY(${plate.y}px)`, height: plate.h || undefined }\n            : { transform: `translate(${plate.x}px, ${plate.y}px)`, width: plate.w, height: plate.h || undefined }\n      }\n    />\n  );\n}\n\n/**\n * A GlidePlate for lists whose highlight is the pointer or keyboard focus rather than state (time picker, plain option\n * lists): it follows the row under the pointer or holding focus, and fades when neither is in the list. Rows drop\n * their own hover and focus tint.\n */\nexport function FollowPlate({\n  list,\n  rows,\n  className,\n}: {\n  list: RefObject<HTMLElement | null>;\n  /** Matches a row inside `list`, e.g. \"[role=option]\". */\n  rows: string;\n  className?: string;\n}) {\n  const [hot, setHot] = useState<string | null>(null);\n  // after the commit, when the list's ref is attached (a layout effect here ran before it and never listened)\n  useEffect(() => {\n    const el = list.current;\n    if (!el) return;\n    let n = 0;\n    const mark = (target: EventTarget | null) => {\n      const row = (target as HTMLElement | null)?.closest?.<HTMLElement>(rows);\n      if (!row || !el.contains(row)) return;\n      row.dataset.glideKey ??= String(++n);\n      setHot(row.dataset.glideKey);\n    };\n    const over = (e: Event) => mark(e.target);\n    const leave = () => {\n      if (!el.contains(document.activeElement)) setHot(null);\n    };\n    const blur = (e: FocusEvent) => {\n      if (!el.contains(e.relatedTarget as Node)) setHot(null);\n    };\n    el.addEventListener(\"pointerover\", over);\n    el.addEventListener(\"focusin\", over);\n    el.addEventListener(\"pointerleave\", leave);\n    el.addEventListener(\"focusout\", blur);\n    return () => {\n      el.removeEventListener(\"pointerover\", over);\n      el.removeEventListener(\"focusin\", over);\n      el.removeEventListener(\"pointerleave\", leave);\n      el.removeEventListener(\"focusout\", blur);\n    };\n  }, [list, rows]);\n  return <GlidePlate list={list} selector={hot ? `[data-glide-key=\"${hot}\"]` : null} className={className} />;\n}\n"},{"path":"beamline/components/_shared/match.tsx","type":"registry:file","target":"src/ui/beamline/components/_shared/match.tsx","content":"/** One way to rank and mark matches for every search-as-you-type list (command menu, combobox, multi-select). */\n\n/** How well `label` (and its keywords) answers `query`: label start 100, word start 80, anywhere 60, keyword 45,\n *  every word of a several-word query found across label, keywords and description 40 (50 when one starts the label:\n *  \"billing city\" finds Billing address › City), description 30, letters in order 15, no match 0. An empty query\n *  matches everything with 1. */\nexport function rank(query: string, label: string, keywords?: string[], description?: string): number {\n  const q = query.trim().toLowerCase();\n  if (!q) return 1;\n  const l = label.toLowerCase();\n  const at = l.indexOf(q);\n  if (at === 0) return 100 - l.length / 100;\n  if (at > 0 && /[\\s\\-/(_.]/.test(l[at - 1])) return 80;\n  if (at > 0) return 60;\n  if (keywords?.some((k) => k.toLowerCase().includes(q))) return 45;\n  const words = q.split(/\\s+/);\n  if (words.length > 1) {\n    const hay = [l, ...(keywords ?? []).map((k) => k.toLowerCase()), description?.toLowerCase() ?? \"\"];\n    if (words.every((w) => hay.some((h) => h.includes(w)))) return words.some((w) => l.startsWith(w)) ? 50 : 40;\n  }\n  if (description?.toLowerCase().includes(q)) return 30;\n  let i = 0;\n  for (const ch of l) if (ch === q[i]) i++;\n  return i === q.length ? 15 : 0;\n}\n\n/** Sorts by rank (best first, original order for ties) and drops non-matches. */\nexport function filterRanked<T>(\n  items: T[],\n  query: string,\n  get: (item: T) => { label: string; keywords?: string[]; description?: string },\n): T[] {\n  if (!query.trim()) return items;\n  return items\n    .map((item, order) => {\n      const g = get(item);\n      return { item, order, s: rank(query, g.label, g.keywords, g.description) };\n    })\n    .filter((r) => r.s > 0)\n    .sort((a, b) => b.s - a.s || a.order - b.order)\n    .map((r) => r.item);\n}\n\n/** The label with the matched run emphasised. */\nexport function Highlight({ text, query }: { text: string; query: string }) {\n  const q = query.trim().toLowerCase();\n  const at = q ? text.toLowerCase().indexOf(q) : -1;\n  if (at < 0) return <>{text}</>;\n  return (\n    <>\n      {text.slice(0, at)}\n      <mark className=\"bg-transparent font-medium text-ink\">{text.slice(at, at + q.length)}</mark>\n      {text.slice(at + q.length)}\n    </>\n  );\n}\n"},{"path":"beamline/components/_shared/overlay.module.css","type":"registry:file","target":"src/ui/beamline/components/_shared/overlay.module.css","content":"@layer properties, theme, base, components, utilities;\n/* the source morph's moving parts: the clip's four sides, the radius and the offset back to the trigger */\n@property --ci-t {\n  syntax: \"<length-percentage>\";\n  inherits: false;\n  initial-value: -48px;\n}\n@property --ci-r {\n  syntax: \"<length-percentage>\";\n  inherits: false;\n  initial-value: -48px;\n}\n@property --ci-b {\n  syntax: \"<length-percentage>\";\n  inherits: false;\n  initial-value: -48px;\n}\n@property --ci-l {\n  syntax: \"<length-percentage>\";\n  inherits: false;\n  initial-value: -48px;\n}\n@property --ci-x {\n  syntax: \"<length>\";\n  inherits: false;\n  initial-value: 0px;\n}\n@property --ci-y {\n  syntax: \"<length>\";\n  inherits: false;\n  initial-value: 0px;\n}\n@layer components {\n  /* Every floating layer in the system: one surface (painted by the overlay role of the look in force), one menu-item\n   look, one scrim, one enter and exit. A layer placed against a trigger opens out of it (the source morph below); one\n   without a placement arrives in --pui-motion-in scaling from its origin. Each leaves in --pui-motion-out; Radix keeps\n   it mounted until the exit animation ends. Reduced motion makes both instant (the duration tokens become 1ms). */\n  .surface {\n    z-index: var(--pui-z-popover);\n    min-width: 8rem;\n    /* never past the screen: as wide and as tall as the room beside the trigger (Radix measures it) */\n    max-width: var(--radix-popper-available-width, none);\n    max-height: var(--radix-popper-available-height, 70vh);\n    overflow: auto;\n    border-radius: var(--pui-overlay-radius, var(--pui-radius-card));\n    color: var(--pui-ink);\n    transform-origin: var(--radix-popper-transform-origin, var(--radix-dropdown-menu-content-transform-origin, center));\n    outline: none;\n  }\n  .surface[data-state=\"open\"],\n  .surface[data-state=\"delayed-open\"],\n  .surface[data-state=\"instant-open\"] {\n    animation: pop-in var(--pui-motion-in) var(--pui-ease-default);\n  }\n  .surface[data-state=\"closed\"] {\n    animation: pop-out var(--pui-motion-out) var(--pui-ease-default) forwards;\n  }\n\n  /* The source morph (research/morphing-interfaces-2026-10-04, M3): a layer placed against a trigger does not fade in\n     beside it, it opens out of it. It starts as the trigger's own box (Radix gives its size and the side and alignment\n     it was placed on), offset back onto the trigger, and grows: the side nearest the trigger is there at once, the\n     sides across follow, the far side arrives last (~200 ms), so the rows by the trigger are readable within the\n     ruled 90 ms while the outline settles. Content fades in over those 90 ms. It closes back into the trigger in the\n     ruled 180 ms. The clip ends 48px outside the box so the layer's shadow is never cut. Reduced motion: instant. */\n  .surface[data-side] {\n    --_aw: var(--radix-popper-anchor-width, 0px);\n    --_ah: var(--radix-popper-anchor-height, 0px);\n    --_gap: 6px;\n    --from-t: -48px;\n    --from-r: -48px;\n    --from-b: -48px;\n    --from-l: -48px;\n    --from-x: 0px;\n    --from-y: 0px;\n    --d-near: calc(var(--pui-duration-standard) * 0.55);\n    --d-cross: calc(var(--pui-duration-standard) * 0.85);\n    --d-far: var(--pui-duration-standard);\n    clip-path: inset(\n      var(--ci-t) var(--ci-r) var(--ci-b) var(--ci-l) round var(--pui-overlay-radius, var(--pui-radius-card))\n    );\n    translate: var(--ci-x) var(--ci-y);\n  }\n  .surface[data-side=\"bottom\"] {\n    --from-t: 0px;\n    --from-b: calc(100% - var(--_ah));\n    --from-y: calc(-1 * (var(--_ah) + var(--_gap)));\n    --d-t: var(--d-near);\n    --d-b: var(--d-far);\n    --d-l: var(--d-cross);\n    --d-r: var(--d-cross);\n  }\n  .surface[data-side=\"top\"] {\n    --from-b: 0px;\n    --from-t: calc(100% - var(--_ah));\n    --from-y: calc(var(--_ah) + var(--_gap));\n    --d-b: var(--d-near);\n    --d-t: var(--d-far);\n    --d-l: var(--d-cross);\n    --d-r: var(--d-cross);\n  }\n  .surface[data-side=\"right\"] {\n    --from-l: 0px;\n    --from-r: calc(100% - var(--_aw));\n    --from-x: calc(-1 * (var(--_aw) + var(--_gap)));\n    --d-l: var(--d-near);\n    --d-r: var(--d-far);\n    --d-t: var(--d-cross);\n    --d-b: var(--d-cross);\n  }\n  .surface[data-side=\"left\"] {\n    --from-r: 0px;\n    --from-l: calc(100% - var(--_aw));\n    --from-x: calc(var(--_aw) + var(--_gap));\n    --d-r: var(--d-near);\n    --d-l: var(--d-far);\n    --d-t: var(--d-cross);\n    --d-b: var(--d-cross);\n  }\n  /* alignment along the trigger: which part of the layer the trigger sits over */\n  .surface:is([data-side=\"bottom\"], [data-side=\"top\"])[data-align=\"start\"] {\n    --from-l: 0px;\n    --from-r: calc(100% - var(--_aw));\n  }\n  .surface:is([data-side=\"bottom\"], [data-side=\"top\"])[data-align=\"end\"] {\n    --from-l: calc(100% - var(--_aw));\n    --from-r: 0px;\n  }\n  .surface:is([data-side=\"bottom\"], [data-side=\"top\"])[data-align=\"start\"]:dir(rtl) {\n    --from-l: calc(100% - var(--_aw));\n    --from-r: 0px;\n  }\n  .surface:is([data-side=\"bottom\"], [data-side=\"top\"])[data-align=\"end\"]:dir(rtl) {\n    --from-l: 0px;\n    --from-r: calc(100% - var(--_aw));\n  }\n  .surface:is([data-side=\"bottom\"], [data-side=\"top\"])[data-align=\"center\"] {\n    --from-l: calc((100% - var(--_aw)) / 2);\n    --from-r: calc((100% - var(--_aw)) / 2);\n  }\n  .surface:is([data-side=\"left\"], [data-side=\"right\"])[data-align=\"start\"] {\n    --from-t: 0px;\n    --from-b: calc(100% - var(--_ah));\n  }\n  .surface:is([data-side=\"left\"], [data-side=\"right\"])[data-align=\"end\"] {\n    --from-t: calc(100% - var(--_ah));\n    --from-b: 0px;\n  }\n  .surface:is([data-side=\"left\"], [data-side=\"right\"])[data-align=\"center\"] {\n    --from-t: calc((100% - var(--_ah)) / 2);\n    --from-b: calc((100% - var(--_ah)) / 2);\n  }\n  .surface[data-side]:is([data-state=\"open\"], [data-state=\"delayed-open\"], [data-state=\"instant-open\"]) {\n    animation:\n      grow-t var(--d-t) var(--pui-ease-out-strong),\n      grow-r var(--d-r) var(--pui-ease-out-strong),\n      grow-b var(--d-b) var(--pui-ease-out-strong),\n      grow-l var(--d-l) var(--pui-ease-out-strong),\n      grow-xy var(--d-cross) var(--pui-ease-out-strong),\n      settling var(--d-far) step-end;\n  }\n  .surface[data-side]:is([data-state=\"open\"], [data-state=\"delayed-open\"], [data-state=\"instant-open\"]) > * {\n    animation: content-in var(--pui-motion-in) var(--pui-ease-default) both;\n  }\n  /* one duration for every part of the exit: the layer unmounts when the first of them ends */\n  .surface[data-side][data-state=\"closed\"] {\n    animation: shrink var(--pui-motion-out) var(--pui-ease-exit) forwards;\n  }\n  .surface[data-side][data-state=\"closed\"] > * {\n    animation: content-out var(--pui-motion-in) var(--pui-ease-default) forwards;\n  }\n  @keyframes grow-t {\n    from {\n      --ci-t: var(--from-t);\n    }\n  }\n  @keyframes grow-r {\n    from {\n      --ci-r: var(--from-r);\n    }\n  }\n  @keyframes grow-b {\n    from {\n      --ci-b: var(--from-b);\n    }\n  }\n  @keyframes grow-l {\n    from {\n      --ci-l: var(--from-l);\n    }\n  }\n  @keyframes grow-xy {\n    from {\n      --ci-x: var(--from-x);\n      --ci-y: var(--from-y);\n    }\n  }\n  /* it starts over its own trigger: until it has grown, the press that opened it must not land on a row */\n  @keyframes settling {\n    0%,\n    100% {\n      pointer-events: none;\n    }\n  }\n  @keyframes shrink {\n    to {\n      --ci-t: var(--from-t);\n      --ci-r: var(--from-r);\n      --ci-b: var(--from-b);\n      --ci-l: var(--from-l);\n      --ci-x: var(--from-x);\n      --ci-y: var(--from-y);\n      opacity: 0;\n    }\n  }\n  @keyframes content-in {\n    from {\n      opacity: 0;\n    }\n  }\n  @keyframes content-out {\n    to {\n      opacity: 0;\n    }\n  }\n  .padded {\n    padding: var(--pui-pad-popover);\n  }\n  .menu {\n    padding: var(--pui-space-1);\n  }\n  @keyframes pop-in {\n    from {\n      opacity: 0;\n      transform: scale(0.97);\n    }\n  }\n  @keyframes pop-out {\n    to {\n      opacity: 0;\n      transform: scale(0.98);\n    }\n  }\n\n  /* rows (and the gliding plate) nest in the list: its radius minus its padding, never rounder than a chip; on the\n     row itself, so any list that uses .item gets it */\n  .item,\n  .glide {\n    --_item-radius: min(\n      var(--pui-radius-chip),\n      calc(var(--pui-overlay-radius, var(--pui-radius-card)) - var(--pui-space-1))\n    );\n  }\n\n  /* menu rows: shared by dropdown, context menu, menubar, select, combobox lists */\n  .item {\n    position: relative;\n    z-index: 1;\n    display: flex;\n    align-items: center;\n    gap: var(--pui-space-2);\n    min-height: var(--pui-control-height-sm);\n    padding: 0 var(--pui-space-2);\n    /* nested in the list: the overlay's radius minus its padding, never rounder than a chip */\n    border-radius: var(--_item-radius);\n    color: var(--pui-ink);\n    font-size: var(--pui-text-sm);\n    cursor: default;\n    user-select: none;\n    outline: none;\n  }\n  /* on a touch screen a row is a finger's target: the touch height (44px) from the tokens; a mouse keeps the dense row */\n  @media (pointer: coarse) {\n    .item {\n      min-height: var(--pui-control-height-touch);\n    }\n  }\n  /* a destructive row speaks in the danger ink only while it can be used: disabled wins (muted ink, no alarm for an\n     action that cannot happen) */\n  .item[data-tone=\"danger\"]:not([data-disabled]) {\n    color: var(--pui-red);\n  }\n  .item[data-disabled] {\n    color: var(--pui-ink-3);\n    pointer-events: none;\n  }\n  .item :where(svg) {\n    width: var(--pui-icon-md);\n    height: var(--pui-icon-md);\n    flex-shrink: 0;\n    color: var(--pui-ink-3);\n  }\n  .item[data-tone=\"danger\"]:not([data-disabled]) :where(svg) {\n    color: currentColor;\n  }\n  .item[data-highlighted]:not([data-glide]) {\n    background: var(--pui-hover-2);\n  }\n  /* a press is acknowledged at once, over the plate, before the menu closes or the row toggles */\n  .item:active:not([data-disabled]) {\n    background: var(--pui-hover);\n  }\n  .shortcut {\n    margin-inline-start: auto;\n    padding-inline-start: var(--pui-space-4);\n    color: var(--pui-ink-3);\n    font-size: var(--pui-text-xs);\n    letter-spacing: var(--pui-tracking-mono-caps);\n  }\n  .heading {\n    padding: var(--pui-space-1-5) var(--pui-space-2) var(--pui-space-1);\n    color: var(--pui-ink-3);\n    font-size: var(--pui-text-xs);\n    font-weight: var(--pui-weight-medium);\n  }\n  .separator {\n    height: 1px;\n    margin: var(--pui-space-1) calc(var(--pui-space-1) * -1);\n    background: var(--pui-line);\n  }\n  .check {\n    margin-inline-start: auto;\n    color: var(--pui-accent) !important;\n  }\n  /* a check arrives in its row as the value changes (at once): it grows from the middle over the arrival */\n  .check :where(svg) {\n    animation: check-in var(--pui-motion-in) var(--pui-ease-out-strong);\n  }\n  @keyframes check-in {\n    from {\n      opacity: 0;\n      scale: calc(1 - 0.5 * var(--pui-motion-travel, 1));\n    }\n  }\n  /* the gliding highlight: one plate moves between rows for the pointer and the arrow keys; it appears in place */\n  .glide {\n    position: absolute;\n    z-index: 0;\n    inset-inline: var(--pui-space-1);\n    top: 0;\n    border-radius: var(--_item-radius);\n    background: var(--pui-hover-2);\n    pointer-events: none;\n    opacity: 0;\n    transition: opacity var(--pui-motion-out) var(--pui-ease-default);\n  }\n  .glide[data-on] {\n    opacity: 1;\n    transition:\n      transform var(--pui-duration-fast) var(--pui-ease-standard),\n      height var(--pui-duration-fast) var(--pui-ease-standard),\n      opacity 0ms;\n  }\n  .glide[data-jump] {\n    transition: opacity 0ms;\n  }\n  /* a plate over a row or grid of cells takes each cell's size and travels both ways */\n  .glide[data-axis=\"xy\"] {\n    inset-inline: auto;\n    left: 0;\n  }\n  .glide[data-axis=\"xy\"][data-on] {\n    transition:\n      transform var(--pui-duration-fast) var(--pui-ease-standard),\n      width var(--pui-duration-fast) var(--pui-ease-standard),\n      height var(--pui-duration-fast) var(--pui-ease-standard),\n      opacity 0ms;\n  }\n\n  /* a layer that has begun to close is out of the way at once: for the length of its exit it takes no pointer, so a tap\n     meant for the page behind (the next row, the button the panel covered) lands there, never on the leaving ghost.\n     Focus has already gone back (Radix) and it unmounts when the exit ends. */\n  .scrim[data-state=\"closed\"],\n  .surface[data-state=\"closed\"],\n  .dialog[data-state=\"closed\"],\n  .sheet[data-state=\"closed\"] {\n    pointer-events: none;\n  }\n\n  /* modal layers */\n  .scrim {\n    position: fixed;\n    inset: 0;\n    z-index: var(--pui-z-overlay);\n    background: var(--pui-scrim);\n    backdrop-filter: blur(var(--pui-blur-overlay));\n  }\n  .scrim[data-state=\"open\"] {\n    animation: fade-in var(--pui-fade-in) var(--pui-ease-default);\n  }\n  .scrim[data-state=\"closed\"] {\n    animation: fade-out var(--pui-fade-out) var(--pui-ease-default) forwards;\n  }\n  @keyframes fade-in {\n    from {\n      opacity: 0;\n    }\n  }\n  @keyframes fade-out {\n    to {\n      opacity: 0;\n    }\n  }\n  .dialog {\n    /* the content answers to the dialog's own width (a footer's buttons share the row on a phone) */\n    container-type: inline-size;\n    position: fixed;\n    z-index: var(--pui-z-modal);\n    left: 50%;\n    top: 50%;\n    width: min(calc(100vw - var(--pui-space-8)), var(--pui-dialog-width, var(--_dialog-width, 440px)));\n    max-height: calc(100dvh - var(--pui-space-12));\n    overflow: hidden;\n    translate: -50% -50%;\n    border-radius: var(--pui-dialog-radius, var(--pui-radius-window));\n    color: var(--pui-ink);\n    outline: none;\n    /* a size chosen while open (a step that needs more room) eases the width in place, centred */\n    transition: width var(--pui-duration-standard) var(--pui-ease-standard);\n  }\n  .dialog[data-state=\"open\"] {\n    animation: dialog-in var(--pui-duration-standard) var(--pui-ease-enter);\n  }\n  .dialog[data-state=\"closed\"] {\n    animation: dialog-out var(--pui-motion-out) var(--pui-ease-default) forwards;\n  }\n  /* A dialog opened from something (use-open-from.ts) grows out of it: it starts as the opener's box, offset back onto\n     it, and grows to its own place over the considered duration while its content fades in; it closes back into the\n     opener in the ruled 180 ms. Shares the source morph's moving parts and guard with .surface. */\n  .dialog[data-open-from] {\n    clip-path: inset(\n      var(--ci-t) var(--ci-r) var(--ci-b) var(--ci-l) round var(--pui-dialog-radius, var(--pui-radius-window))\n    );\n    transform: translate(var(--ci-x), var(--ci-y));\n  }\n  .dialog[data-open-from][data-state=\"open\"] {\n    animation:\n      grow-t var(--pui-duration-considered) var(--pui-ease-out-strong),\n      grow-r var(--pui-duration-considered) var(--pui-ease-out-strong),\n      grow-b var(--pui-duration-considered) var(--pui-ease-out-strong),\n      grow-l var(--pui-duration-considered) var(--pui-ease-out-strong),\n      grow-xy calc(var(--pui-duration-considered) * 0.9) var(--pui-ease-out-strong),\n      settling var(--pui-duration-considered) step-end;\n  }\n  .dialog[data-open-from][data-state=\"open\"] > * {\n    animation: content-in var(--pui-duration-standard) var(--pui-ease-default) calc(var(--pui-duration-standard) * 0.35)\n      both;\n  }\n  .dialog[data-open-from][data-state=\"closed\"] {\n    animation: shrink var(--pui-motion-out) var(--pui-ease-exit) forwards;\n  }\n  .dialog[data-open-from][data-state=\"closed\"] > * {\n    animation: content-out var(--pui-motion-in) var(--pui-ease-default) forwards;\n  }\n  /* A sheet: a panel from an edge. It slides in from its edge over the considered duration on the drawer curve and\n     goes back out to that edge in the ruled departure (a layer closes back into where it came from). The edge is\n     logical: an end (\"right\") sheet comes from the left in a right-to-left page. Its content answers to its width. */\n  .sheet {\n    --_sx: 0px;\n    --_sy: 0px;\n    container-type: inline-size;\n    /* where a finger has swiped it toward its edge (sheet.tsx); it settles back over the departure */\n    translate: var(--_swipe, 0px) 0;\n  }\n  /* a side panel leaves sideways pans to the swipe and keeps vertical scrolling native, in its body too */\n  .sheet:is([data-side=\"left\"], [data-side=\"right\"]),\n  .sheet:is([data-side=\"left\"], [data-side=\"right\"]) [data-slot=\"sheet-body\"] {\n    touch-action: pan-y;\n  }\n  .sheet:is([data-side=\"left\"], [data-side=\"right\"]) {\n    transition:\n      var(--m-transition),\n      translate var(--pui-motion-out) var(--pui-ease-default);\n  }\n  .sheet[data-swiping] {\n    transition: none;\n  }\n  .sheet[data-side=\"right\"] {\n    --_sx: 100%;\n  }\n  .sheet[data-side=\"left\"] {\n    --_sx: -100%;\n  }\n  .sheet[data-side=\"right\"]:dir(rtl) {\n    --_sx: -100%;\n  }\n  .sheet[data-side=\"left\"]:dir(rtl) {\n    --_sx: 100%;\n  }\n  .sheet[data-side=\"top\"] {\n    --_sy: -100%;\n  }\n  .sheet[data-side=\"bottom\"] {\n    --_sy: 100%;\n  }\n  .sheet[data-state=\"open\"] {\n    animation: sheet-in var(--pui-duration-considered) var(--pui-ease-drawer) both;\n  }\n  .sheet[data-state=\"closed\"] {\n    animation: sheet-out var(--pui-motion-out) var(--pui-ease-exit) forwards;\n  }\n  @keyframes sheet-in {\n    from {\n      translate: var(--_sx) var(--_sy);\n    }\n  }\n  @keyframes sheet-out {\n    to {\n      translate: var(--_sx) var(--_sy);\n    }\n  }\n  @keyframes dialog-in {\n    from {\n      opacity: 0;\n      transform: scale(0.96);\n    }\n  }\n  @keyframes dialog-out {\n    to {\n      opacity: 0;\n      transform: scale(0.98);\n    }\n  }\n}\n\n/* Reduced motion: surfaces and dialogs fade without scaling or growing, over the fade tokens (restrained under reduced,\n   1ms under none; APPEARANCE A06). */\n@media (prefers-reduced-motion: reduce) {\n  .surface,\n  .dialog,\n  .sheet {\n    animation-name: fade-in !important;\n    animation-duration: var(--pui-fade-in) !important;\n  }\n  .surface[data-state=\"closed\"],\n  .dialog[data-state=\"closed\"],\n  .sheet[data-state=\"closed\"] {\n    animation-name: fade-out !important;\n    animation-duration: var(--pui-fade-out) !important;\n  }\n}\n@layer components {\n  :global(.pui[data-motion=\"reduced\"]) .surface,\n  :global(.pui[data-motion=\"none\"]) .surface,\n  :global(.pui[data-motion=\"reduced\"]) .dialog,\n  :global(.pui[data-motion=\"none\"]) .dialog,\n  :global(.pui[data-motion=\"reduced\"]) .sheet,\n  :global(.pui[data-motion=\"none\"]) .sheet {\n    animation-name: fade-in;\n    animation-duration: var(--pui-fade-in);\n  }\n  :global(.pui[data-motion=\"reduced\"]) .surface[data-state=\"closed\"],\n  :global(.pui[data-motion=\"none\"]) .surface[data-state=\"closed\"],\n  :global(.pui[data-motion=\"reduced\"]) .dialog[data-state=\"closed\"],\n  :global(.pui[data-motion=\"none\"]) .dialog[data-state=\"closed\"],\n  :global(.pui[data-motion=\"reduced\"]) .sheet[data-state=\"closed\"],\n  :global(.pui[data-motion=\"none\"]) .sheet[data-state=\"closed\"] {\n    animation-name: fade-out;\n    animation-duration: var(--pui-fade-out);\n  }\n}\n/* CSS-module composition only; visual rules stay in the component layer. */\n.surface {\n  composes: m-overlay from global;\n}\n.dialog {\n  composes: m-overlay from global;\n}\n"},{"path":"beamline/components/_shared/use-open-from.ts","type":"registry:file","target":"src/ui/beamline/components/_shared/use-open-from.ts","content":"\"use client\";\n\nimport { useCallback, type Ref } from \"react\";\n\n/**\n * A centred layer (dialog, alert dialog) that opens out of whatever opened it: as its content element attaches (each\n * time it opens, before the first frame and before focus moves in), the element that had focus (the button, row or\n * card that was used) is measured, and the layer starts as that box, offset back onto it, then grows to its own place\n * (overlay.module.css, `[data-open-from]`). The same values take it back there as it closes. Without a recent press\n * or Enter/Space (a shortcut, a controlled `open` from code) nothing is set and the ordinary entrance runs. Returns a ref\n * callback for the layer's content element, composed with the caller's `ref`.\n */\nexport function useOpenFrom<T extends HTMLElement>(external?: Ref<T>) {\n  watch();\n  return useCallback(\n    (el: T | null) => {\n      if (typeof external === \"function\") external(el);\n      else if (external) external.current = el;\n      if (el) measure(el);\n    },\n    [external],\n  );\n}\n\n/** What was last pressed or keyed, noted as it happened: a field inside the new layer may take focus (autoFocus)\n *  before the layer can ask what had it. */\nlet last: { el: Element; at: number } | null = null;\nlet watching = false;\nfunction watch() {\n  if (watching || typeof document === \"undefined\") return;\n  watching = true;\n  const note = (e: Event) => {\n    // a press, or Enter / Space on the focused control; a shortcut (⌘K) opens from nowhere in particular\n    if (e.type === \"keydown\" && ![\"Enter\", \" \"].includes((e as KeyboardEvent).key)) return;\n    // the control that was pressed, not the label or icon inside it\n    const target =\n      e.type === \"keydown\"\n        ? document.activeElement\n        : ((e.target as Element | null)?.closest(\n            \"button, a[href], [role=button], [role=menuitem], [role=option], [role=tab], [role=row], input, select, textarea, [tabindex]\",\n          ) ?? (e.target as Element | null));\n    if (target && target !== document.body) last = { el: target, at: performance.now() };\n  };\n  document.addEventListener(\"pointerdown\", note, true);\n  document.addEventListener(\"keydown\", note, true);\n}\n\nconst pending = new WeakSet<HTMLElement>();\nfunction measure(el: HTMLElement) {\n  // once per opening: a later call would measure the layer already moved onto its opener\n  if (el.dataset.openFrom !== undefined || pending.has(el)) return;\n  const doc = el.ownerDocument;\n  // only a recent press or activation counts as the opener; a layer opened by a shortcut or by code has none\n  const opener = (\n    last && performance.now() - last.at < 1500 && last.el.isConnected ? last.el : null\n  ) as HTMLElement | null;\n  if (!opener || opener === doc.body || el.contains(opener)) return;\n  // A modal's scroll lock lands in an effect after the ref. Measure on the next frame, after the lock and before\n  // paint. WebKit can reserve scrollbar space in the fixed containing block even when innerWidth === clientWidth\n  // (also on a touch viewport), so comparing those widths cannot tell us whether the centre will move.\n  const view = doc.defaultView;\n  if (view) {\n    pending.add(el);\n    view.requestAnimationFrame(() => {\n      pending.delete(el);\n      if (el.isConnected && el.dataset.openFrom === undefined && place(el, opener)) el.dataset.openFrom = \"\";\n    });\n  } else if (place(el, opener)) el.dataset.openFrom = \"\";\n}\n\n/** Sets the start box: the opener's box as offsets from the layer's resting box. False when either has no size. */\nfunction place(el: HTMLElement, opener: HTMLElement) {\n  const from = opener.getBoundingClientRect();\n  // the layer's own resting box: its centre as laid out (an entrance scale does not move a centre) and its size\n  // without transforms\n  const w = el.offsetWidth;\n  const h = el.offsetHeight;\n  if (!w || !h || !from.width || !from.height) return false;\n  const r = el.getBoundingClientRect();\n  const left = r.left + r.width / 2 - w / 2;\n  const top = r.top + r.height / 2 - h / 2;\n  el.style.setProperty(\"--from-x\", `${from.left - left}px`);\n  el.style.setProperty(\"--from-y\", `${from.top - top}px`);\n  el.style.setProperty(\"--from-t\", \"0px\");\n  el.style.setProperty(\"--from-l\", \"0px\");\n  el.style.setProperty(\"--from-r\", `${Math.max(0, w - from.width)}px`);\n  el.style.setProperty(\"--from-b\", `${Math.max(0, h - from.height)}px`);\n  return true;\n}\n"},{"path":"beamline/components/_shared/use-return-focus.ts","type":"registry:file","target":"src/ui/beamline/components/_shared/use-return-focus.ts","content":"\"use client\";\n\nimport { useRef } from \"react\";\nimport { useAppearanceRoot } from \"../../runtime/appearance\";\n\n/**\n * Where focus goes when a modal layer closes. Radix returns it to its own Trigger; a layer opened another way (a\n * controlled `open`, a menu item, a shortcut) would drop focus on <body>. Spread the result on the Radix content: it\n * remembers what had focus as the layer opened and returns there, unless the caller's own handler prevents it.\n * If that opener is removed, return to its surviving owning region, or the managed scope's visual root.\n */\nexport function useReturnFocus({\n  onOpenAutoFocus,\n  onCloseAutoFocus,\n}: { onOpenAutoFocus?: (event: Event) => void; onCloseAutoFocus?: (event: Event) => void } = {}) {\n  track();\n  const before = useRef<HTMLElement | null>(null);\n  const regions = useRef<HTMLElement[]>([]);\n  const scope = useAppearanceRoot();\n  return {\n    onOpenAutoFocus: (event: Event) => {\n      before.current = outside(document.activeElement, event.currentTarget as Element | null);\n      const owners: HTMLElement[] = [];\n      let ancestor = before.current;\n      while (ancestor) {\n        if (ancestor !== document.body && ancestor.matches(\"[data-return-region]\")) owners.push(ancestor);\n        ancestor = ancestor.parentElement;\n      }\n      const root = scope ?? before.current?.closest<HTMLElement>(\".pui\");\n      if (root && root !== document.body && !owners.includes(root)) owners.push(root);\n      regions.current = owners;\n      onOpenAutoFocus?.(event);\n    },\n    /** `layer` is the layer's own element, for a caller that runs this itself instead of Radix. */\n    onCloseAutoFocus: (event: Event, layer: Element | null = event.currentTarget as Element | null) => {\n      onCloseAutoFocus?.(event);\n      if (event.defaultPrevented) return;\n      // a layer whose field took focus as it mounted (autoFocus) never hears that it opened (Radix skips the mount\n      // focus): the opener is then the last element focused outside it\n      const el =\n        before.current && before.current.isConnected && !layer?.contains?.(before.current)\n          ? before.current\n          : outside(null, layer);\n      if (el && el !== document.body && el.isConnected) {\n        event.preventDefault();\n        el.focus({ preventScroll: true });\n        return;\n      }\n      const region = regions.current.find((owner) => owner.isConnected);\n      if (region) {\n        event.preventDefault();\n        // Regions need not be tab stops, but they must receive programmatic return focus.\n        if (!region.hasAttribute(\"tabindex\")) region.tabIndex = -1;\n        region.focus({ preventScroll: true });\n      }\n    },\n  };\n}\n\n/** The last elements that took focus or were pressed, newest last, noted as it happened (capture phase, once per\n *  page). Presses count too: a click on a button does not focus it everywhere (Safari), and the control that was\n *  pressed is the opener a layer should give focus back to. */\nconst trail: Element[] = [];\nlet tracking = false;\nconst PRESSABLE =\n  \"button, a[href], [role=button], [role=menuitem], [role=option], [role=tab], [tabindex], input, select, textarea\";\nfunction note(el: Element | null) {\n  if (!el || el === document.body) return;\n  if (trail.at(-1) === el) return;\n  trail.push(el);\n  if (trail.length > 24) trail.shift();\n}\nfunction track() {\n  if (tracking || typeof document === \"undefined\") return;\n  tracking = true;\n  // A lazily mounted layer may be the first focus consumer on the page. Remember its opener before child autofocus.\n  note(document.activeElement);\n  document.addEventListener(\"focusin\", (event) => note(event.target as Element | null), true);\n  document.addEventListener(\n    \"pointerdown\",\n    (event) => note((event.target as Element | null)?.closest?.(PRESSABLE) ?? null),\n    true,\n  );\n}\n\n/** `active` unless it is inside `layer`; then the most recent element focused outside the layer that still exists. */\nfunction outside(active: Element | null, layer: Element | null): HTMLElement | null {\n  if (active && active !== document.body && !layer?.contains?.(active)) return active as HTMLElement;\n  for (let i = trail.length - 1; i >= 0; i--) {\n    const el = trail[i];\n    if (el.isConnected && el !== document.body && !layer?.contains?.(el)) return el as HTMLElement;\n  }\n  return null;\n}\n"},{"path":"beamline/components/command-menu/command-menu.module.css","type":"registry:file","target":"src/ui/beamline/components/command-menu/command-menu.module.css","content":"@layer properties, theme, base, components, utilities;\n@layer components {\n  /* A nested page slides in from the side it comes from: forward from the inline end, back from the inline start.\n     Short and small, so keyboard users are never kept waiting; reduced motion makes the token durations instant. */\n  .page {\n    --_from: 14px;\n    position: relative;\n  }\n  .page:dir(rtl) {\n    --_from: -14px;\n  }\n  .page[data-dir=\"forward\"] {\n    animation: page-in calc(var(--pui-duration-considered) * 0.6) var(--pui-ease-out-strong) both;\n  }\n  .page[data-dir=\"back\"] {\n    --_from: -14px;\n    animation: page-in calc(var(--pui-duration-considered) * 0.6) var(--pui-ease-out-strong) both;\n  }\n  .page[data-dir=\"back\"]:dir(rtl) {\n    --_from: 14px;\n  }\n  /* busy: a short light runs along the field's bottom edge while results are arriving (bound to the loading state,\n     so it ends with it; transform only). Reduced motion: a still line says the same. */\n  .field[data-busy]::after {\n    content: \"\";\n    position: absolute;\n    inset-inline-start: 0;\n    bottom: -1px;\n    width: 40%;\n    height: 1px;\n    background: linear-gradient(90deg, transparent, var(--pui-accent), transparent);\n    animation: busy var(--pui-duration-flash) var(--pui-ease-in-out) infinite alternate;\n  }\n  :global(.pui:is([data-motion=\"reduced\"], [data-motion=\"none\"])) .field[data-busy]::after {\n    width: 100%;\n    opacity: 0.5;\n    animation: none;\n  }\n  /* the back chip arrives with its page */\n  .chip {\n    animation: chip-in var(--pui-duration-standard) var(--pui-ease-out-strong) both;\n  }\n}\n@keyframes page-in {\n  from {\n    opacity: 0;\n    translate: var(--_from) 0;\n  }\n}\n@keyframes chip-in {\n  from {\n    opacity: 0;\n    scale: 0.92;\n  }\n}\n@keyframes busy {\n  from {\n    translate: -100% 0;\n  }\n  to {\n    translate: 250% 0;\n  }\n}\n@media (prefers-reduced-motion: reduce) {\n  .field[data-busy]::after {\n    width: 100%;\n    opacity: 0.5;\n    animation: none;\n  }\n}\n"},{"path":"beamline/components/command-menu/command-menu.tsx","type":"registry:file","target":"src/ui/beamline/components/command-menu/command-menu.tsx","content":"\"use client\";\n\nimport { useMessages } from \"../../runtime/localization\";\nimport { english as englishMessages } from \"./messages\";\nimport type { CSSProperties, Ref } from \"react\";\n\nimport { Fragment, useEffect, useId, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport { ArrowLeft, ChevronRight, CornerDownLeft, Search } from \"lucide-react\";\nimport * as RadixDialog from \"@radix-ui/react-dialog\";\nimport { Kbd } from \"../kbd/kbd\";\nimport { SkeletonBlock } from \"../skeleton/skeleton\";\nimport { Highlight, rank } from \"../_shared/match\";\nimport { useReturnFocus } from \"../_shared/use-return-focus\";\nimport { useOpenFrom } from \"../_shared/use-open-from\";\nimport { GlidePlate } from \"../_shared/glide\";\nimport { useComposedRef } from \"../../hooks/use-composed-ref\";\nimport { useHeightMorph } from \"../../hooks/use-height-morph\";\nimport { usePortalContainer } from \"../../runtime/appearance\";\nimport { cn } from \"../../lib/utils\";\nimport { useControllable } from \"../../hooks/use-controllable\";\nimport overlay from \"../_shared/overlay.module.css\";\nimport styles from \"./command-menu.module.css\";\n\nexport interface CommandItem {\n  id: string;\n  label: string;\n  /** Items with the same group are listed under one heading, in first-seen order. */\n  group?: string;\n  icon?: ReactNode;\n  /** Shown as keycaps: \"⌘N\" is a combination, \"G A\" a sequence (G, then A). The app binds the keys. */\n  shortcut?: string;\n  /** Other words that should find it: \"invoice\" finds Billing. */\n  keywords?: string[];\n  /** A second line, muted. */\n  description?: string;\n  disabled?: boolean;\n  /** A page of further items; choosing this item opens it. */\n  children?: CommandItem[];\n  /** Runs when chosen; the palette's onSelect runs too. */\n  onSelect?: () => void;\n}\n\nexport type CommandPaletteClassNames = Partial<\n  Record<\n    | \"root\"\n    | \"field\"\n    | \"back\"\n    | \"input\"\n    | \"list\"\n    | \"empty\"\n    | \"heading\"\n    | \"option\"\n    | \"text\"\n    | \"label\"\n    | \"description\"\n    | \"meta\"\n    | \"hints\",\n    string\n  >\n>;\n\nexport interface CommandPaletteProps {\n  classNames?: CommandPaletteClassNames;\n  items: CommandItem[];\n  placeholder?: string;\n  /** A chosen item without children. */\n  onSelect?: (item: CommandItem) => void;\n  /** Escape on the first page. */\n  onClose?: () => void;\n  /** Accessible name of the search field. */\n  label?: string;\n  /** What to say when nothing matches. */\n  emptyText?: (query: string) => ReactNode;\n  /** The search text as it changes (fetch results for it; a nested page starts empty). */\n  onQueryChange?: (query: string) => void;\n  /** Results are still arriving (a search on the server): a busy line runs under the field, and while nothing has\n   *  arrived the list shows rows shaped like results instead of \"no match\". */\n  loading?: boolean;\n  style?: CSSProperties;\n  ref?: Ref<HTMLDivElement>;\n  className?: string;\n}\n\n// \"⌘N\" is a combination (one step); a space separates the steps of a sequence: \"G A\" is G, then A\nconst keysOf = (shortcut: string) =>\n  shortcut\n    .split(\" \")\n    .filter(Boolean)\n    .flatMap((part, i) => [...(i > 0 ? [\"then\"] : []), ...(/^[⌘⇧⌥⌃]/.test(part) ? [...part] : [part])]);\n\n/**\n * A search-first list of every action: type to filter, ↑ ↓ to move, Enter to run. An item with children opens a page;\n * Backspace on an empty field goes back. Inline here; CommandMenu puts it in a ⌘K dialog.\n */\nexport function CommandPalette({\n  items,\n  placeholder: placeholderProp,\n  onSelect,\n  onClose,\n  label: labelProp,\n  emptyText,\n  onQueryChange,\n  loading = false,\n  className,\n  classNames,\n  style,\n  ref,\n}: CommandPaletteProps) {\n  const i18n = useMessages(\"command-menu\", englishMessages);\n  const placeholder = placeholderProp === undefined ? i18n(\"placeholder\") : placeholderProp;\n  const label = labelProp === undefined ? i18n(\"label\") : labelProp;\n\n  const [query, setQuery] = useState(\"\");\n  const [pages, setPages] = useState<CommandItem[]>([]);\n  const [active, setActive] = useState(0);\n  const input = useRef<HTMLInputElement>(null);\n  const list = useRef<HTMLDivElement>(null);\n  const rows = useRef<HTMLDivElement>(null);\n  const root = useRef<HTMLDivElement>(null);\n  const attach = useComposedRef(root, ref);\n  // a page change slides the rows in from the side they come from and eases the palette to its new height\n  const pageMove = useRef<{ dir: \"forward\" | \"back\" } | null>(null);\n  const viaKeyboard = useRef(false);\n  const id = useId();\n  const page = pages.at(-1);\n  const q = query.trim().toLowerCase();\n\n  const groups = useMemo(() => {\n    const ranked = (page?.children ?? items)\n      .map((item, order) => ({ item, s: rank(q, item.label, item.keywords, item.description), order }))\n      .filter((r) => r.s > 0);\n    if (q) ranked.sort((a, b) => b.s - a.s || a.order - b.order);\n    const map = new Map<string, CommandItem[]>();\n    for (const { item } of ranked) {\n      const g = item.group ?? \"\";\n      map.set(g, [...(map.get(g) ?? []), item]);\n    }\n    return [...map.entries()];\n  }, [items, page, q]);\n  const flat = groups.flatMap(([, g]) => g);\n  const exposesList = loading || flat.length > 0;\n  const current = flat[Math.min(active, flat.length - 1)];\n\n  useEffect(() => {\n    setActive(\n      Math.max(\n        0,\n        flat.findIndex((i) => !i.disabled),\n      ),\n    );\n  }, [q, page]); // eslint-disable-line react-hooks/exhaustive-deps -- reset only when the list's question changes\n  useEffect(() => {\n    if (!viaKeyboard.current || !current) return;\n    list.current?.querySelector(`[data-command-id=\"${CSS.escape(current.id)}\"]`)?.scrollIntoView({ block: \"nearest\" });\n  }, [current]);\n\n  // a page change eases the menu to the new page's height in place (the shared step tray of dialogs and forms)\n  useHeightMorph(root, page, { duration: \"standard\" });\n  const turnPage = (dir: \"forward\" | \"back\") => {\n    pageMove.current = { dir };\n  };\n\n  const choose = (item: CommandItem | undefined) => {\n    if (!item || item.disabled) return;\n    if (item.children?.length) {\n      turnPage(\"forward\");\n      setPages((p) => [...p, item]);\n      setQuery(\"\");\n      onQueryChange?.(\"\");\n      input.current?.focus();\n      return;\n    }\n    item.onSelect?.();\n    onSelect?.(item);\n  };\n  const back = () => {\n    turnPage(\"back\");\n    setPages((p) => p.slice(0, -1));\n    setQuery(\"\");\n    onQueryChange?.(\"\");\n    input.current?.focus();\n  };\n  const move = (delta: number) => {\n    if (!flat.length) return;\n    viaKeyboard.current = true;\n    let next = active;\n    for (let n = 0; n < flat.length; n++) {\n      next = (next + delta + flat.length) % flat.length;\n      if (!flat[next].disabled) break;\n    }\n    setActive(next);\n  };\n\n  return (\n    <div\n      data-slot=\"command-palette\"\n      ref={attach}\n      style={style}\n      className={cn(\n        \"m-overlay @container flex max-h-[min(460px,70dvh)] flex-col overflow-hidden rounded-window text-ink\",\n        classNames?.root,\n        className,\n      )}\n      onKeyDown={(e) => {\n        if (e.key === \"ArrowDown\") {\n          e.preventDefault();\n          move(1);\n        } else if (e.key === \"ArrowUp\") {\n          e.preventDefault();\n          move(-1);\n        } else if (e.key === \"Home\" && !query) {\n          e.preventDefault();\n          viaKeyboard.current = true;\n          setActive(0);\n        } else if (e.key === \"End\" && !query) {\n          e.preventDefault();\n          viaKeyboard.current = true;\n          setActive(flat.length - 1);\n        } else if (e.key === \"Enter\" && !(e.target instanceof HTMLButtonElement)) {\n          e.preventDefault();\n          choose(current);\n        } else if (e.key === \"Backspace\" && !query && page) {\n          e.preventDefault();\n          back();\n        } else if (e.key === \"Escape\") {\n          e.preventDefault();\n          if (page) back();\n          else onClose?.();\n        }\n      }}\n    >\n      <div\n        data-slot=\"command-menu-field\"\n        data-busy={loading ? \"\" : undefined}\n        className={cn(\n          \"relative flex h-12 shrink-0 items-center gap-2 border-b border-line-soft px-3\",\n          styles.field,\n          classNames?.field,\n        )}\n      >\n        {page ? (\n          <button\n            data-slot=\"command-menu-back\"\n            type=\"button\"\n            onClick={back}\n            aria-label={i18n(\"backFrom\", { label: page.label })}\n            className={cn(\n              \"m-control flex h-7 max-w-[45%] items-center gap-1 rounded-chip px-2 text-xs text-ink-2 hover:text-ink\",\n              styles.chip,\n              classNames?.back,\n            )}\n          >\n            <ArrowLeft className=\"size-3.5 shrink-0 rtl:-scale-x-100\" />\n            <span className=\"truncate\">{page.label}</span>\n          </button>\n        ) : (\n          <Search className=\"size-4 shrink-0 text-ink-3\" />\n        )}\n        <input // organism-allow: raw-control -- the palette's own query field (it is the component)\n          data-slot=\"command-menu-input\"\n          ref={input}\n          value={query}\n          onChange={(e) => {\n            setQuery(e.target.value);\n            onQueryChange?.(e.target.value);\n          }}\n          placeholder={page ? i18n(\"search\", { value: page.label.replace(/…$/, \"\") }) : placeholder}\n          role=\"combobox\"\n          aria-expanded={exposesList}\n          aria-controls={exposesList ? `${id}-list` : undefined}\n          aria-activedescendant={current ? `${id}-${current.id}` : undefined}\n          aria-label={label}\n          autoComplete=\"off\"\n          spellCheck={false}\n          className={cn(\n            \"h-full min-w-0 flex-1 bg-transparent text-base text-ink outline-none placeholder:text-ink-3\",\n            classNames?.input,\n          )}\n        />\n      </div>\n      <div\n        data-slot=\"command-menu-list\"\n        ref={list}\n        id={`${id}-list`}\n        role={exposesList ? \"listbox\" : undefined}\n        aria-label={exposesList ? (page?.label ?? label) : undefined}\n        aria-busy={loading || undefined}\n        // keycaps in a row nest in the row's chip corner (O1 §14.1 nesting input, read by Kbd); the inset is the row's\n        // end padding, capped so a keycap keeps at least a 2px corner where the row is less round than its padding\n        className={cn(\n          \"min-h-0 flex-1 overflow-y-auto p-1.5 [--pui-nest-inset:min(var(--pui-space-2),calc(var(--pui-radius-chip)-2px))] [--pui-nest-radius:var(--pui-radius-chip)]\",\n          classNames?.list,\n        )}\n        onPointerMove={() => {\n          viaKeyboard.current = false;\n        }}\n      >\n        <div\n          ref={rows}\n          key={pages.length}\n          data-slot=\"command-menu-page\"\n          data-dir={pageMove.current?.dir}\n          className={styles.page}\n        >\n          <GlidePlate\n            list={rows}\n            selector={current ? '[data-slot=\"command-menu-option\"][data-active]' : null}\n            reset={`${pages.length}|${q}`}\n            className=\"inset-x-0\"\n          />\n          {flat.length === 0 && loading && (\n            <div data-slot=\"command-menu-loading\" aria-hidden className=\"grid gap-1 p-1\">\n              {[60, 44, 52].map((w) => (\n                <span key={w} className=\"flex h-9 items-center gap-2 px-2\">\n                  <SkeletonBlock className=\"size-4 shrink-0\" />\n                  <SkeletonBlock className=\"h-2.5\" style={{ width: `${w}%` }} />\n                </span>\n              ))}\n            </div>\n          )}\n          {flat.length === 0 && !loading && (\n            <div\n              data-slot=\"command-menu-empty\"\n              role=\"status\"\n              className={cn(\"px-3 py-8 text-center text-sm text-ink-3\", classNames?.empty)}\n            >\n              {emptyText ? emptyText(query) : <>{i18n(\"noCommandsMatch\", { query: query })}</>}\n            </div>\n          )}\n          {groups.map(([group, groupItems]) => (\n            <Fragment key={group}>\n              {group && (\n                <div\n                  data-slot=\"command-menu-heading\"\n                  role=\"presentation\"\n                  className={cn(overlay.heading, classNames?.heading)}\n                >\n                  {group}\n                </div>\n              )}\n              {groupItems.map((item) => {\n                const index = flat.indexOf(item);\n                const on = index === active;\n                return (\n                  <div\n                    data-slot=\"command-menu-option\"\n                    key={item.id}\n                    id={`${id}-${item.id}`}\n                    role=\"option\"\n                    aria-selected={on}\n                    aria-disabled={item.disabled || undefined}\n                    data-command-id={item.id}\n                    data-active={on ? \"\" : undefined}\n                    data-disabled={item.disabled ? \"\" : undefined}\n                    className={cn(overlay.item, \"min-h-9 cursor-pointer\", classNames?.option)}\n                    onPointerMove={() => {\n                      if (!on && !item.disabled) setActive(index);\n                    }}\n                    onMouseDown={(e) => e.preventDefault()}\n                    onClick={() => choose(item)}\n                  >\n                    {item.icon}\n                    <span data-slot=\"command-menu-text\" className={cn(\"grid min-w-0\", classNames?.text)}>\n                      <span\n                        data-slot=\"command-menu-label\"\n                        className={cn(\"truncate text-ink-2 [[data-active]_&]:text-ink\", classNames?.label)}\n                      >\n                        <Highlight text={item.label} query={q} />\n                      </span>\n                      {item.description && (\n                        <span\n                          data-slot=\"command-menu-description\"\n                          className={cn(\"truncate text-xs text-ink-3\", classNames?.description)}\n                        >\n                          {item.description}\n                        </span>\n                      )}\n                    </span>\n                    <span\n                      data-slot=\"command-menu-meta\"\n                      className={cn(\"ms-auto flex items-center gap-2 ps-3\", classNames?.meta)}\n                    >\n                      {item.shortcut && <Kbd keys={keysOf(item.shortcut)} size=\"sm\" className=\"@max-sm:hidden\" />}\n                      {item.children?.length ? <ChevronRight className=\"!size-3.5 rtl:rotate-180\" /> : null}\n                    </span>\n                  </div>\n                );\n              })}\n            </Fragment>\n          ))}\n        </div>\n      </div>\n      <div\n        data-slot=\"command-menu-hints\"\n        aria-hidden\n        // the first hint's keycap sits in the palette's bottom corner: it nests in the window radius\n        // keys mean nothing to a finger: on a touch screen the hints give their room to the list\n        className={cn(\n          \"flex h-9 shrink-0 items-center gap-4 border-t border-line-soft px-3 text-2xs text-ink-3 [--pui-nest-inset:var(--pui-space-2)] [--pui-nest-radius:var(--pui-radius-window)] pointer-coarse:hidden\",\n          classNames?.hints,\n        )}\n      >\n        {/* a narrow palette keeps the hints a person needs first (run, close); moving by arrows is the one guessed */}\n        <span className=\"flex items-center gap-1.5 @max-xs:hidden\">\n          <Kbd keys={[\"↑\"]} size=\"sm\" />\n          <Kbd keys={[\"↓\"]} size=\"sm\" />\n          {i18n(\"move\")}\n        </span>\n        <span className=\"flex items-center gap-1.5\">\n          <Kbd size=\"sm\">\n            <CornerDownLeft className=\"size-2.5\" />\n          </Kbd>\n          {current?.children?.length ? i18n(\"open\") : i18n(\"run\")}\n        </span>\n        <span className=\"flex items-center gap-1.5\">\n          <Kbd keys={[page ? \"⌫\" : \"esc\"]} size=\"sm\" />\n          {page ? i18n(\"previous\") : i18n(\"close\")}\n        </span>\n      </div>\n    </div>\n  );\n}\n\nexport interface CommandMenuProps extends Omit<CommandPaletteProps, \"onClose\"> {\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  /** ⌘K / Ctrl K opens and closes it. Default true. */\n  hotkey?: boolean;\n  overlayClassNames?: Partial<Record<\"root\" | \"scrim\" | \"title\", string>>;\n}\n\n/** The palette in a dialog, opened with ⌘K (Ctrl K) from anywhere. It closes after running an item. */\nexport function CommandMenu({\n  open: openProp,\n  defaultOpen = false,\n  onOpenChange,\n  hotkey = true,\n  overlayClassNames,\n  onSelect,\n  ...palette\n}: CommandMenuProps) {\n  const i18n = useMessages(\"command-menu\", englishMessages);\n\n  const [open, setOpen] = useControllable(openProp, defaultOpen, onOpenChange);\n  const container = usePortalContainer();\n  const focus = useReturnFocus();\n  const toggle = useRef(setOpen);\n  toggle.current = setOpen;\n  const openRef = useRef(open);\n  openRef.current = open;\n  useEffect(() => {\n    if (!hotkey) return;\n    const on = (e: KeyboardEvent) => {\n      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === \"k\" && !e.altKey) {\n        e.preventDefault();\n        toggle.current(!openRef.current);\n      }\n    };\n    window.addEventListener(\"keydown\", on);\n    return () => window.removeEventListener(\"keydown\", on);\n  }, [hotkey]);\n  // opened by a press (a search field, a button) it grows out of what was pressed; ⌘K opens it in place\n  const openFrom = useOpenFrom<HTMLDivElement>();\n  return (\n    <RadixDialog.Root open={open} onOpenChange={setOpen}>\n      {container !== null && (\n        <RadixDialog.Portal container={container}>\n          <RadixDialog.Overlay data-slot=\"command-menu-scrim\" className={cn(overlay.scrim, overlayClassNames?.scrim)} />\n          <RadixDialog.Content\n            ref={openFrom}\n            data-slot=\"command-menu\"\n            aria-describedby={undefined}\n            onEscapeKeyDown={(e) => e.preventDefault()}\n            {...focus}\n            className={cn(\n              overlay.dialog,\n              \"top-[14dvh] [translate:-50%_0] overflow-visible rounded-window bg-transparent shadow-none [--pui-dialog-width:600px]\",\n              overlayClassNames?.root,\n            )}\n          >\n            <RadixDialog.Title data-slot=\"command-menu-title\" className={cn(\"sr-only\", overlayClassNames?.title)}>\n              {palette.label ?? i18n(\"commandMenu\")}\n            </RadixDialog.Title>\n            <CommandPalette\n              {...palette}\n              onSelect={(item) => {\n                onSelect?.(item);\n                setOpen(false);\n              }}\n              onClose={() => setOpen(false)}\n            />\n          </RadixDialog.Content>\n        </RadixDialog.Portal>\n      )}\n    </RadixDialog.Root>\n  );\n}\n\nexport default CommandMenu;\n"},{"path":"beamline/components/command-menu/messages.ts","type":"registry:file","target":"src/ui/beamline/components/command-menu/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  placeholder: \"Search commands\",\n  label: \"Command menu\",\n  backFrom: \"Back from {label}\",\n  search: \"Search {value}\",\n  noCommandsMatch: \"No commands match “{query}”.\",\n  move: \"Move\",\n  open: \"Open\",\n  run: \"Run\",\n  previous: \"Back\",\n  close: \"Close\",\n  commandMenu: \"Command menu\",\n} satisfies MessageDefinitions;\n"},{"path":"beamline/components/kbd/defaults.ts","type":"registry:file","target":"src/ui/beamline/components/kbd/defaults.ts","content":"/** Pure allowed presentation defaults for Kbd; no component or runtime imports. */\nexport interface KbdDefaults {\n  /** Shared size setting; omit to inherit the surrounding scope or the component default. */\n  size?: \"sm\" | \"md\" | \"lg\";\n}\n"},{"path":"beamline/components/kbd/kbd.module.css","type":"registry:file","target":"src/ui/beamline/components/kbd/kbd.module.css","content":"@layer properties, theme, base, components, utilities;\n@layer components {\n  /* Keycaps: each key is a small raised control (the control role), so it reads as a key in every look. */\n  .keys {\n    display: inline-flex;\n    align-items: center;\n    gap: var(--pui-space-0-5);\n    vertical-align: middle;\n    /* a key combination is a fixed sequence: it reads Ctrl K in any page direction, isolated as one unit while the\n       page keeps its own direction around it */\n    direction: ltr;\n    unicode-bidi: isolate;\n  }\n  .cap {\n    display: inline-flex;\n    height: 22px;\n    min-width: 22px;\n    align-items: center;\n    justify-content: center;\n    padding-inline: var(--pui-space-1-5);\n    /* nesting input (O1 §14.1, as Button): a parent that holds this near its rounded corner sets --pui-nest-radius and\n       --pui-nest-inset; the corner becomes radius − inset, clamped between a floor (chip) and the part's own role radius (or --pui-nest-ceiling: a pill container sets it to the pill radius). Unset, the declaration is invalid and the own corner\n       applies. --pui-kbd-radius wins over both. */\n    --_kbd-nest: clamp(\n      calc(var(--pui-radius-chip) / 2),\n      calc(var(--pui-nest-radius) - var(--pui-nest-inset, 0px)),\n      var(--pui-nest-ceiling, var(--pui-radius-chip))\n    );\n    border-radius: var(--pui-kbd-radius, var(--_kbd-nest, calc(var(--pui-radius-chip) - 1px)));\n    color: var(--pui-ink-2);\n    font-size: var(--pui-text-2xs);\n    font-weight: var(--pui-weight-medium);\n    transition:\n      translate var(--pui-motion-out) var(--pui-ease-default),\n      color var(--pui-motion-out) var(--pui-ease-default),\n      var(--m-transition);\n  }\n  /* live: a held key presses its cap down at once (the look's own press light) and lets it up over the leave\n     duration; with every key of the combination held, the caps ink in the accent and take an accent ring */\n  .cap[data-pressed] {\n    --pui-m-press: 1;\n    translate: 0 1px;\n    color: var(--pui-ink);\n    transition-duration: 0ms;\n  }\n  .keys[data-complete] .cap {\n    color: var(--pui-accent-ink);\n    box-shadow:\n      var(--m-control-edge),\n      0 0 0 1px var(--pui-accent);\n  }\n  /* a finished step of a sequence keeps its caps inked while the next is awaited */\n  .cap[data-done] {\n    color: var(--pui-accent-ink);\n  }\n  /* \"then\" between the steps of a sequence: a word, seen and spoken, so G then A never reads as G and A held together.\n     While the next step is awaited a line under it runs out over the time it has (SEQUENCE_MS in kbd.tsx): a timer,\n     so reduced motion keeps it, as HoldToConfirm keeps its fill. */\n  .then {\n    position: relative;\n    padding-inline: var(--pui-space-0-5);\n    color: var(--pui-ink-3);\n    font-size: var(--pui-text-2xs);\n    font-weight: var(--pui-weight-normal);\n    transition: color var(--pui-motion-out) var(--pui-ease-default);\n  }\n  .then[data-waiting] {\n    color: var(--pui-ink-2);\n    transition-duration: 0ms;\n  }\n  .then[data-waiting]::after {\n    content: \"\";\n    position: absolute;\n    inset-inline: var(--pui-space-0-5);\n    bottom: -2px;\n    height: 1px;\n    background: var(--pui-accent);\n    transform-origin: left;\n    /* the same wait the sequence listener keeps (SEQUENCE_MS), passed in so the line and the timeout never drift */\n    animation: kbd-wait var(--_wait) linear forwards;\n  }\n  @keyframes kbd-wait {\n    from {\n      scale: 1 1;\n    }\n    to {\n      scale: 0 1;\n    }\n  }\n  .sm {\n    height: 18px;\n    min-width: 18px;\n    padding-inline: var(--pui-space-1);\n    font-size: var(--pui-text-2xs);\n  }\n  .lg {\n    height: 28px;\n    min-width: 28px;\n    padding-inline: var(--pui-space-2);\n    font-size: var(--pui-text-sm);\n  }\n}\n/* CSS-module composition only; visual rules stay in the component layer. */\n.cap {\n  composes: m-control from global;\n}\n"},{"path":"beamline/components/kbd/kbd.tsx","type":"registry:file","target":"src/ui/beamline/components/kbd/kbd.tsx","content":"\"use client\";\n\nimport { useMessages, type Translator } from \"../../runtime/localization\";\nimport { english as englishMessages } from \"./messages\";\nimport { useComponentDefaults } from \"../../runtime/appearance\";\nimport type { KbdDefaults } from \"./defaults\";\nexport type { KbdDefaults } from \"./defaults\";\n\nimport { Fragment, useEffect, useReducer } from \"react\";\nimport type { CSSProperties, HTMLAttributes, ReactNode, Ref } from \"react\";\nimport { cn } from \"../../lib/utils\";\nimport { useApplePlatform } from \"../../hooks/use-apple-platform\";\nimport styles from \"./kbd.module.css\";\n\nconst MAP_MAC = (i18n: Translator<typeof englishMessages>): Record<string, string> => ({\n  mod: \"⌘\",\n  cmd: \"⌘\",\n  ctrl: \"⌃\",\n  alt: \"⌥\",\n  option: \"⌥\",\n  shift: \"⇧\",\n  enter: \"↵\",\n  backspace: \"⌫\",\n  esc: i18n(\"escapeShort\"),\n  tab: \"⇥\",\n  up: \"↑\",\n  down: \"↓\",\n  left: \"←\",\n  right: \"→\",\n  space: i18n(\"space\"),\n});\nconst MAP_PC = (i18n: Translator<typeof englishMessages>): Record<string, string> => ({\n  mod: i18n(\"controlShort\"),\n  cmd: i18n(\"controlShort\"),\n  ctrl: i18n(\"controlShort\"),\n  alt: i18n(\"alt\"),\n  option: i18n(\"alt\"),\n  shift: i18n(\"shift\"),\n  enter: i18n(\"enter\"),\n  backspace: i18n(\"backspace\"),\n  esc: i18n(\"escapeShort\"),\n  tab: i18n(\"tab\"),\n  up: \"↑\",\n  down: \"↓\",\n  left: \"←\",\n  right: \"→\",\n  space: i18n(\"space\"),\n});\nconst SPOKEN = (i18n: Translator<typeof englishMessages>): Record<string, string> => ({\n  \"⌘\": i18n(\"command\"),\n  \"⌃\": i18n(\"control\"),\n  \"⌥\": i18n(\"option\"),\n  \"⇧\": i18n(\"shift\"),\n  \"↵\": i18n(\"enter\"),\n  \"⌫\": i18n(\"backspace\"),\n  \"⇥\": i18n(\"tab\"),\n  \"↑\": i18n(\"up\"),\n  \"↓\": i18n(\"down\"),\n  \"←\": i18n(\"left\"),\n  \"→\": i18n(\"right\"),\n});\n\n/** The KeyboardEvent.key a cap stands for, compared in lower case. */\nconst EVENT_KEY: Record<string, string> = {\n  ctrl: \"control\",\n  alt: \"alt\",\n  option: \"alt\",\n  shift: \"shift\",\n  enter: \"enter\",\n  backspace: \"backspace\",\n  esc: \"escape\",\n  tab: \"tab\",\n  up: \"arrowup\",\n  down: \"arrowdown\",\n  left: \"arrowleft\",\n  right: \"arrowright\",\n  space: \" \",\n};\nconst eventKey = (k: string, mac: boolean) => {\n  const name = k.toLowerCase();\n  if (name === \"mod\" || name === \"cmd\") return mac ? \"meta\" : \"control\";\n  return EVENT_KEY[name] ?? name;\n};\nconst NONE: ReadonlySet<string> = new Set();\n/** The key between the steps of a sequence: [\"g\", \"then\", \"a\"] is G, then A. */\nconst THEN = \"then\";\n/** How long a sequence waits for its next step, as apps that bind them do. */\nconst SEQUENCE_MS = 1500;\n\n/** live: the keys of the shortcut held now, the step being waited for, whether the whole shortcut is done, and how\n *  many steps have been done (a step done again, G pressed twice, gives the next one its full time again). */\ninterface Live {\n  held: ReadonlySet<string>;\n  step: number;\n  complete: boolean;\n  done: number;\n}\ntype LiveAction =\n  { type: \"down\"; key: string; steps: string[][] } | { type: \"up\"; key: string; steps: string[][] } | { type: \"reset\" };\nconst REST: Live = { held: NONE, step: 0, complete: false, done: 0 };\n\n/** A step is done by the press that completes it: every key of the step held, the last one just pressed. */\nconst advance = (live: Live, key: string, steps: string[][]): Live => {\n  const want = steps[live.step];\n  if (!want.includes(key) || !want.every((k) => live.held.has(k))) return live;\n  return live.step === steps.length - 1\n    ? { ...live, complete: true }\n    : { ...live, step: live.step + 1, done: live.done + 1 };\n};\nfunction track(live: Live, action: LiveAction): Live {\n  if (action.type === \"reset\") return live === REST ? live : REST;\n  const { key, steps } = action;\n  if (action.type === \"down\") {\n    // a key outside the shortcut breaks a sequence half-way; a combination simply waits for its own keys\n    if (!steps.some((s) => s.includes(key))) return live.step || live.complete ? { ...REST, held: live.held } : live;\n    const held = live.held.has(key) ? live.held : new Set(live.held).add(key);\n    const next = advance({ ...live, held }, key, steps);\n    if (next.step === live.step && !next.complete && live.step > 0 && !steps[live.step].includes(key))\n      // a wrong key mid-sequence starts it over, and may itself begin it\n      return advance({ ...live, held, step: 0, complete: false }, key, steps);\n    return next;\n  }\n  // with ⌘ held a Mac sends no keyup for the other keys: letting go of ⌘ lets go of all of them\n  let held = live.held;\n  if (key === \"meta\") held = NONE;\n  else if (held.has(key)) {\n    const next = new Set(held);\n    next.delete(key);\n    held = next;\n  }\n  // letting go of the last step ends the shortcut; an earlier step stays done while the next is awaited\n  if (live.complete && steps[live.step].some((k) => !held.has(k))) return { ...REST, held };\n  return held === live.held ? live : { ...live, held };\n}\n\n/** Typing a letter into a field is typing, not a shortcut. */\nconst typing = (e: KeyboardEvent) => {\n  const t = e.target as HTMLElement | null;\n  return (\n    e.key.length === 1 &&\n    !e.metaKey &&\n    !e.ctrlKey &&\n    !e.altKey &&\n    !!t &&\n    (t.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName))\n  );\n};\n\nexport interface KbdClassNames {\n  /** Classes for the root part; no override by default. */\n  root?: string;\n  /** Classes for the cap part; no override by default. */\n  cap?: string;\n  /** Classes for the symbol part; no override by default. */\n  symbol?: string;\n  /** Classes for the spoken part; no override by default. */\n  spoken?: string;\n  /** The word between the steps of a sequence. */\n  then?: string;\n}\n\nexport interface KbdProps extends HTMLAttributes<HTMLElement> {\n  /** Keys in order; held together they make a combination: `[\"mod\", \"K\"]` renders ⌘K on Mac and Ctrl K elsewhere.\n   *  `\"then\"` between keys makes a sequence: `[\"g\", \"then\", \"a\"]` is G, then A. */\n  keys?: string[];\n  /** The caps answer the keyboard: each presses down while the viewer holds its key, and the combination lights when\n   *  all are held; in a sequence each finished step stays lit while the next is awaited (1.5 s). For teaching a\n   *  shortcut where it is shown; it listens only while mounted with `live`. */\n  live?: boolean;\n  /** Platform used to name modifier keys; defaults to the viewer's detected platform. */\n  platform?: \"mac\" | \"other\";\n  /** Control size within the supported size steps. Defaults to \"md\". */\n  size?: \"sm\" | \"md\" | \"lg\";\n  /** Receives the documented root element; no ref by default. */\n  ref?: Ref<HTMLElement>;\n  /** Classes for the named visual parts; no part overrides by default. */\n  classNames?: KbdClassNames;\n}\n\n/**\n * Keyboard keys as keycaps, for combinations (⌘K) and sequences (G then A). `mod` resolves to ⌘ or Ctrl for the\n * viewer's platform; symbols are spoken as words for screen readers. Pass `children` for a single literal key.\n */\nexport function Kbd({\n  keys,\n  live = false,\n  platform,\n  size: sizeProp,\n  className,\n  classNames,\n  children,\n  ref,\n  ...props\n}: KbdProps) {\n  const i18n = useMessages(\"kbd\", englishMessages);\n\n  const scopeDefaults = useComponentDefaults<KbdDefaults>(\"kbd\", [\"size\"]);\n  const size = sizeProp ?? scopeDefaults.size ?? \"md\";\n\n  const detectedMac = useApplePlatform();\n  const mac = platform === undefined ? detectedMac : platform === \"mac\";\n  const map = mac ? MAP_MAC(i18n) : MAP_PC(i18n);\n  const spoken = SPOKEN(i18n);\n  // the steps: one for a combination, one per \"then\" for a sequence (a stray \"then\" at either end is no step)\n  const parts: string[][] = [[]];\n  for (const k of keys ?? []) {\n    if (k.toLowerCase() === THEN) parts.push([]);\n    else parts[parts.length - 1].push(k);\n  }\n  const steps = parts.filter((s) => s.length > 0);\n  // live: which of the caps' keys the viewer holds now, and how far through the sequence they are\n  const [state, dispatch] = useReducer(track, REST);\n  const wanted = live && keys ? steps.map((s) => s.map((k) => eventKey(k, mac)).join(\"\\u0000\")).join(\"\\u0001\") : \"\";\n  useEffect(() => {\n    if (!wanted) return;\n    const events = wanted.split(\"\\u0001\").map((s) => s.split(\"\\u0000\"));\n    const press = (e: KeyboardEvent) => {\n      if (e.repeat || typing(e)) return;\n      dispatch({ type: \"down\", key: e.key.toLowerCase(), steps: events });\n    };\n    const release = (e: KeyboardEvent) => dispatch({ type: \"up\", key: e.key.toLowerCase(), steps: events });\n    const clear = () => dispatch({ type: \"reset\" });\n    window.addEventListener(\"keydown\", press);\n    window.addEventListener(\"keyup\", release);\n    window.addEventListener(\"blur\", clear);\n    return () => {\n      window.removeEventListener(\"keydown\", press);\n      window.removeEventListener(\"keyup\", release);\n      window.removeEventListener(\"blur\", clear);\n      dispatch({ type: \"reset\" });\n    };\n  }, [wanted]);\n  // a sequence waits for its next step a moment, then starts over\n  const waiting = state.step > 0 && !state.complete;\n  useEffect(() => {\n    if (!waiting) return;\n    const t = window.setTimeout(() => dispatch({ type: \"reset\" }), SEQUENCE_MS);\n    return () => window.clearTimeout(t);\n  }, [waiting, state.done]);\n  const label = (k: string) => map[k.toLowerCase()] ?? (k.length === 1 ? k.toUpperCase() : k);\n  const cap = cn(styles.cap, \"font-sans\", size !== \"md\" && styles[size], classNames?.cap);\n  const face = (k: ReactNode) =>\n    // a label on a plain span is not read; the symbol is hidden and its word is spoken instead\n    typeof k === \"string\" && spoken[k] ? (\n      <>\n        <span data-slot=\"kbd-symbol\" className={classNames?.symbol} aria-hidden>\n          {k}\n        </span>\n        <span data-slot=\"kbd-spoken\" className={cn(\"sr-only\", classNames?.spoken)}>\n          {spoken[k]}\n        </span>\n      </>\n    ) : (\n      k\n    );\n  return (\n    <kbd\n      ref={ref}\n      data-slot=\"kbd\"\n      data-complete={state.complete || undefined}\n      className={cn(styles.keys, classNames?.root, className)}\n      {...props}\n    >\n      {keys ? (\n        steps.map((step, s) => (\n          <Fragment key={s}>\n            {s > 0 && (\n              <span\n                // a new wait restarts the line under it\n                key={waiting && s === state.step ? `then-${state.done}` : \"then\"}\n                data-slot=\"kbd-then\"\n                data-waiting={(waiting && s === state.step) || undefined}\n                className={cn(styles.then, classNames?.then)}\n                style={{ \"--_wait\": `${SEQUENCE_MS}ms` } as CSSProperties}\n              >\n                {i18n(\"then\")}\n              </span>\n            )}\n            {step.map((k, i) => {\n              const event = eventKey(k, mac);\n              return (\n                <span\n                  data-slot=\"kbd-cap\"\n                  key={i}\n                  data-pressed={(live && s === state.step && state.held.has(event)) || undefined}\n                  data-done={(live && s < state.step) || undefined}\n                  className={cap}\n                >\n                  {face(label(k))}\n                </span>\n              );\n            })}\n          </Fragment>\n        ))\n      ) : (\n        <span data-slot=\"kbd-cap\" className={cap}>\n          {face(children)}\n        </span>\n      )}\n    </kbd>\n  );\n}\n\nexport default Kbd;\n"},{"path":"beamline/components/kbd/messages.ts","type":"registry:file","target":"src/ui/beamline/components/kbd/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  then: \"then\",\n  escapeShort: \"Esc\",\n  space: \"Space\",\n  controlShort: \"Ctrl\",\n  alt: \"Alt\",\n  shift: \"Shift\",\n  enter: \"Enter\",\n  backspace: \"Backspace\",\n  tab: \"Tab\",\n  command: \"Command\",\n  control: \"Control\",\n  option: \"Option\",\n  up: \"Up\",\n  down: \"Down\",\n  left: \"Left\",\n  right: \"Right\",\n} satisfies MessageDefinitions;\n"},{"path":"beamline/components/skeleton/messages.ts","type":"registry:file","target":"src/ui/beamline/components/skeleton/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 content\",\n} satisfies MessageDefinitions;\n"},{"path":"beamline/components/skeleton/motion-features.ts","type":"registry:file","target":"src/ui/beamline/components/skeleton/motion-features.ts","content":"// REASON: Motion's DOM animation features (tween and presence) load after the first paint, so a Skeleton costs a\n// small part of Motion on first load; LazyMotion in skeleton.tsx renders until they arrive.\nimport { domAnimation } from \"motion/react\";\n\nexport default domAnimation;\n"},{"path":"beamline/components/skeleton/skeleton.tsx","type":"registry:file","target":"src/ui/beamline/components/skeleton/skeleton.tsx","content":"\"use client\";\n\nimport { useMessages } from \"../../runtime/localization\";\nimport { english as englishMessages } from \"./messages\";\nimport { useRef, type ComponentProps, type CSSProperties, type ReactNode, type Ref } from \"react\";\nimport { AnimatePresence, LazyMotion, m } from \"motion/react\";\nimport { useComposedRef } from \"../../hooks/use-composed-ref\";\nimport { useHeightMorph } from \"../../hooks/use-height-morph\";\nimport { useMotionTokens } from \"../../hooks/use-motion-tokens\";\nimport { useReducedMotionSafe } from \"../../hooks/use-reduced-motion-safe\";\nimport { useActive } from \"../../hooks/use-active\";\nimport { cn } from \"../../lib/utils\";\n\n// Motion's animation features arrive in a deferred chunk; the swap is instant until they land.\nconst features = () => import(\"./motion-features\").then((module) => module.default);\n\nexport interface SkeletonClassNames {\n  /** Classes for the root part; no override by default. */\n  root?: string;\n  /** Classes for the placeholder part; no override by default. */\n  placeholder?: string;\n  /** Classes for the avatar part; no override by default. */\n  avatar?: string;\n  /** Classes for the lines part; no override by default. */\n  lines?: string;\n  /** Classes for the line part; no override by default. */\n  line?: string;\n  /** Classes for the content part; no override by default. */\n  content?: string;\n}\n\nexport interface SkeletonProps {\n  /** Announced to assistive tech while loading. */\n  label?: string;\n  /** Text lines in the placeholder; the last is shorter. */\n  lines?: number;\n  /** A round avatar before the lines. */\n  avatar?: boolean;\n  /** With children: the placeholder shows while true, the children after. */\n  loading?: boolean;\n  /** The real content. */\n  children?: ReactNode;\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<HTMLDivElement>;\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?: SkeletonClassNames;\n}\n\n// The sheen (utilities.css .sheen) slides a gradient layer with transform, so the compositor moves it without repainting\n// the placeholder on every frame; it pauses with the block (play state) and stops for reduced motion (data-still). A\n// scope's own motion choice (data-motion) and the OS preference also still it in CSS, so a block or group needs no\n// reader of the look's motion values: a part that only shows a placeholder stays small.\nconst SHEEN = \"sheen\";\n\n/** One placeholder shape: give it the width, height and radius of what will be there. */\nexport function SkeletonBlock({\n  className,\n  style,\n  ref,\n}: {\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  /** Receives the documented root element; no ref by default. */\n  ref?: Ref<HTMLSpanElement>;\n}) {\n  const host = useRef<HTMLSpanElement>(null);\n  const attach = useComposedRef(host, ref);\n  const active = useActive(host);\n  const reduced = useReducedMotionSafe();\n  return (\n    <span\n      ref={attach}\n      aria-hidden\n      data-slot=\"skeleton-block\"\n      data-still={reduced ? \"\" : undefined}\n      className={cn(\"block h-3 rounded-chip\", SHEEN, className)}\n      style={{ \"--pui-sheen-play\": active ? \"running\" : \"paused\", ...style } as CSSProperties}\n    />\n  );\n}\n\n/**\n * Many placeholder shapes under one visibility and motion check: a heatmap's cells, a chart's bars. Each shape is a\n * plain element with the `sheen` class anywhere inside the group, which pauses them off screen and stills them under\n * reduced motion together, so a 500-cell placeholder is one component instead of 500, and a list's loading rows can\n * keep the real rows' structure (and so their height).\n */\nexport function SkeletonGroup({ className, style, ref, ...props }: ComponentProps<\"div\">) {\n  const host = useRef<HTMLDivElement>(null);\n  const attach = useComposedRef(host, ref);\n  const active = useActive(host);\n  const reduced = useReducedMotionSafe();\n  return (\n    <div\n      ref={attach}\n      data-slot=\"skeleton-group\"\n      className={className}\n      style={{ \"--pui-sheen-play\": active ? \"running\" : \"paused\", ...style } as CSSProperties}\n      {...props}\n      // the shapes' styles key on these, so a caller's own data-slot (a chart's loading area) keeps its name\n      data-skeleton-group=\"\"\n      data-still={reduced ? \"\" : undefined}\n    />\n  );\n}\n\n/**\n * One line of text that has not arrived, inside a SkeletonGroup: an inline box exactly one line of the surrounding text\n * style tall (a zero-width character holds the line), with the shape centred in it. Put the real text's classes on it\n * (`text-sm`, `font-figure text-2xl leading-none`) and a width, and the loaded line takes its place without a shift.\n */\nexport function SkeletonLine({\n  className,\n  style,\n}: {\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}) {\n  return (\n    <span\n      aria-hidden\n      data-slot=\"skeleton-line\"\n      className={cn(\"relative inline-block w-24 align-top\", className)}\n      style={style}\n    >\n      {\"\\u200b\"}\n      <span className=\"sheen absolute inset-x-0 top-1/2 block h-[0.7em] -translate-y-1/2 rounded-chip\" />\n    </span>\n  );\n}\n\n/** Holds the shape of content while it loads, then crossfades into it; if the content is taller or shorter than its\n *  placeholder, the height eases from one to the other, so what sits below glides instead of jumping. */\nexport function Skeleton({\n  label: labelProp,\n  lines = 3,\n  avatar = false,\n  loading = true,\n  children,\n  className,\n  classNames,\n  style,\n  ref,\n}: SkeletonProps) {\n  const i18n = useMessages(\"skeleton\", englishMessages);\n  const label = labelProp === undefined ? i18n(\"label\") : labelProp;\n\n  const host = useRef<HTMLDivElement>(null);\n  const attach = useComposedRef(host, ref);\n  const motionTokens = useMotionTokens(host);\n  const managedReduced = useReducedMotionSafe();\n  const reduced = motionTokens.reduced || managedReduced;\n  const showPlaceholder = children === undefined || loading;\n  // the swap keeps its path: the box eases between the placeholder's height and the content's\n  useHeightMorph(host, showPlaceholder, { clip: true, duration: \"standard\", reduced });\n  // A crossfade: reduced motion keeps it restrained, none removes it (APPEARANCE A06).\n  const fade = {\n    duration: motionTokens.mode === \"none\" ? 0 : reduced ? motionTokens.fade.in : motionTokens.duration.standard,\n    ease: motionTokens.ease.default,\n  };\n  return (\n    <div\n      ref={attach}\n      data-slot=\"skeleton\"\n      data-state={showPlaceholder ? \"loading\" : \"loaded\"}\n      aria-busy={showPlaceholder || undefined}\n      className={cn(\"relative\", classNames?.root, className)}\n      style={style}\n    >\n      <LazyMotion features={features}>\n        <AnimatePresence mode=\"popLayout\" initial={false}>\n          {showPlaceholder ? (\n            <m.div\n              key=\"placeholder\"\n              role=\"status\"\n              aria-label={label}\n              data-slot=\"skeleton-placeholder\"\n              className={cn(\"flex items-start gap-3\", classNames?.placeholder)}\n              exit={{ opacity: 0 }}\n              transition={fade}\n            >\n              {avatar && <SkeletonBlock className={cn(\"size-9 shrink-0 rounded-full\", classNames?.avatar)} />}\n              <div\n                data-slot=\"skeleton-lines\"\n                className={cn(\"flex min-w-0 flex-1 flex-col gap-2.5 py-1\", classNames?.lines)}\n              >\n                {Array.from({ length: Math.max(1, lines) }, (_, i) => (\n                  <SkeletonBlock\n                    key={i}\n                    className={classNames?.line}\n                    style={{ width: i === lines - 1 && lines > 1 ? \"62%\" : i === 0 ? \"88%\" : \"100%\" }}\n                  />\n                ))}\n              </div>\n            </m.div>\n          ) : (\n            <m.div\n              data-slot=\"skeleton-content\"\n              className={classNames?.content}\n              key=\"content\"\n              initial={{ opacity: 0 }}\n              animate={{ opacity: 1 }}\n              transition={fade}\n            >\n              {children}\n            </m.div>\n          )}\n        </AnimatePresence>\n      </LazyMotion>\n    </div>\n  );\n}\n\nexport default Skeleton;\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-apple-platform.ts","type":"registry:file","target":"src/ui/beamline/hooks/use-apple-platform.ts","content":"\"use client\";\n\nimport { useSyncExternalStore } from \"react\";\nimport { isApplePlatform } from \"../lib/platform\";\n\nconst never = () => () => {};\n\n/** isApplePlatform() for rendering: false on the server and in the first hydrated paint, the real answer after. */\nexport function useApplePlatform() {\n  return useSyncExternalStore(never, isApplePlatform, () => false);\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-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-height-morph.ts","type":"registry:file","target":"src/ui/beamline/hooks/use-height-morph.ts","content":"\"use client\";\n\nimport { useLayoutEffect, useRef, type RefObject } from \"react\";\nimport { useMotionTokens } from \"./use-motion-tokens\";\nimport { cssEase } from \"../lib/motion-tokens\";\n\n/**\n * The step tray: a card whose content is replaced (a sign-in step, the sign-up card, a form step) keeps its path. When\n * `key` changes, the element's height eases from what it was to what the new content needs, so the card's edge and\n * everything under it glide instead of jumping (the morph grammar's \"modes change size in place\"). The element clips\n * while it eases, with room for focus rings, unless `clip` is false (a slot whose ease only moves what is around it, like\n * the auth page re-centring its card, shows its content whole). Nothing runs at rest; reduced motion, motion \"none\" and a card off screen\n * switch at once; a change mid-way starts from where the card is. The height twin of hooks/use-width-morph; any element whose\n * content is replaced in place uses it (the auth cards, the step form, a dialog's stepped tray, the command menu's pages).\n */\nexport function useHeightMorph(\n  ref: RefObject<HTMLElement | null>,\n  key: unknown,\n  {\n    clip = true,\n    duration = \"morph\",\n    reduced: reducedOverride = false,\n  }: {\n    clip?: boolean;\n    /** The semantic duration the ease takes: a mode change morphs, a content swap (a skeleton) takes the standard. */\n    duration?: \"morph\" | \"standard\";\n    /** Also place at once when the caller knows motion is reduced by its own policy. */\n    reduced?: boolean;\n  } = {},\n) {\n  const motion = useMotionTokens(ref);\n  const last = useRef<number | null>(null);\n  const easing = useRef<Animation | null>(null);\n  // the height it last rested at, kept current when it changes on its own (an error line, a bot check) so the next ease\n  // starts where the card really is; the ease's own frames are not a resting height\n  useLayoutEffect(() => {\n    const el = ref.current;\n    if (!el || typeof ResizeObserver === \"undefined\") return;\n    const observer = new ResizeObserver(() => {\n      if (!easing.current) last.current = el.getBoundingClientRect().height;\n    });\n    observer.observe(el);\n    return () => observer.disconnect();\n  }, [ref]);\n  useLayoutEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n    const from = easing.current ? el.getBoundingClientRect().height : last.current;\n    easing.current?.cancel();\n    easing.current = null;\n    const box = el.getBoundingClientRect();\n    const to = box.height;\n    last.current = to;\n    const ms = motion.duration[duration] * 1000;\n    if (from === null || Math.abs(from - to) < 0.5 || motion.reduced || reducedOverride || ms < 2) return;\n    // off screen nobody sees the glide: the card takes its height at once and no frame is spent on it\n    if (box.bottom < 0 || box.top > innerHeight) return;\n    // the ease runs between measured heights: a keyframe to \"auto\" (even with interpolate-size, or calc-size) does not\n    // interpolate in Chromium's Web Animations, it jumps at once; at the end the element is back on its own height\n    // the clip below leaves room for a focus ring or a control's shadow at the content's edge (inert at rest); a plain\n    // length, because Chromium computes any calc() here (and so the space tokens) as 0\n    el.style.setProperty(\"overflow-clip-margin\", \"8px\");\n    const ease = motion.ease.default;\n    const overflow = clip ? \"clip\" : \"visible\";\n    const animation = el.animate(\n      [\n        { height: `${from}px`, overflow },\n        { height: `${to}px`, overflow },\n      ],\n      { duration: ms, easing: cssEase(ease, \"ease-in-out\") },\n    );\n    easing.current = animation;\n    void animation.finished.then(\n      () => {\n        if (easing.current !== animation) return;\n        easing.current = null;\n        last.current = el.getBoundingClientRect().height;\n      },\n      () => {},\n    );\n  }, [key]); // eslint-disable-line react-hooks/exhaustive-deps -- eases on a change of the step it stands for\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/platform.ts","type":"registry:file","target":"src/ui/beamline/lib/platform.ts","content":"/** Whether the person is on an Apple platform, where `mod` is ⌘ (Mac, iPhone, iPad); false where there is no browser. */\nexport const isApplePlatform = () =>\n  typeof navigator !== \"undefined\" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);\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":"Command menu is in src/ui/beamline/components/command-menu/command-menu.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/command-menu"}}