{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "markdown-viewer",
  "title": "Markdown Viewer",
  "description": "A continuous Markdown viewer forked from the Chenglou Pretext text viewer, with block virtualization and no visible page model.",
  "dependencies": [
    "@chenglou/pretext",
    "hast-util-to-jsx-runtime",
    "katex",
    "lucide-react",
    "marked@18.0.5",
    "mermaid",
    "rehype-katex",
    "rehype-raw",
    "rehype-sanitize",
    "rehype-slug",
    "remark-breaks",
    "remark-directive",
    "remark-gemoji",
    "remark-gfm",
    "remark-math",
    "remark-parse",
    "remark-rehype",
    "remark-smartypants",
    "prismjs@^1.30.0",
    "unified",
    "vfile"
  ],
  "devDependencies": [
    "@types/prismjs@^1.26.6"
  ],
  "registryDependencies": [
    "button",
    "dropdown-menu",
    "@retab/scroll-area",
    "@retab/skeleton",
    "@retab/spinner",
    "@retab/utils",
    "@retab/viewer-controls",
    "@retab/use-keyed-layout-effect",
    "@retab/use-mount-effect"
  ],
  "files": [
    {
      "path": "registry/new-york-v4/ui/markdown-viewer.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n  MarkdownGreenfieldContent,\n  type MarkdownViewerProps,\n} from \"./markdown-greenfield-content\";\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\nimport { PlainTextViewerFrame } from \"./plain-text-viewer-frame\";\nimport { TextViewerFallback } from \"./text-viewer-chrome\";\nimport type { TextViewerHandle } from \"./text-viewer-types\";\n\nexport type { MarkdownViewerProps } from \"./markdown-greenfield-content\";\nexport type {\n  TextDocumentSource,\n  TextLineRange,\n  TextViewerHandle,\n  TextViewerProps,\n} from \"./text-viewer-types\";\n\nexport type MarkdownResourceContentProps = Omit<\n  MarkdownViewerProps,\n  \"source\"\n> & {\n  resource: ViewerResource;\n};\n\nexport const MarkdownViewer = React.forwardRef<\n  TextViewerHandle,\n  MarkdownViewerProps\n>(function MarkdownViewer(props, ref) {\n  return (\n    <PlainTextViewerFrame\n      props={props}\n      forwardedRef={ref}\n      clientFallbackPolicy=\"non-inline-source\"\n      Fallback={TextViewerFallback}\n      Content={MarkdownGreenfieldContent}\n    />\n  );\n});\n\nexport const MarkdownResourceContent = React.forwardRef<\n  TextViewerHandle,\n  MarkdownResourceContentProps\n>(function MarkdownResourceContent({ resource, ...props }, ref) {\n  return (\n    <PlainTextViewerFrame\n      props={props}\n      resource={resource}\n      forwardedRef={ref}\n      clientFallbackPolicy=\"non-inline-source\"\n      Fallback={TextViewerFallback}\n      Content={MarkdownGreenfieldContent}\n    />\n  );\n});\n",
      "type": "registry:ui",
      "target": "@ui/markdown-viewer.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/plain-text-viewer-frame.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n  isResourceError,\n  isViewerFormatError,\n  isViewerStateError,\n  isViewerUnsupportedError,\n  ViewerFormatError,\n} from \"@/lib/viewer-errors\";\nimport {\n  createViewerResource,\n  viewerContentRenderKey,\n  viewerResourceRenderKey,\n  type ViewerResource,\n} from \"@/lib/viewer-resource\";\nimport type {\n  BlobViewerSource,\n  TextSource,\n  UrlViewerSource,\n} from \"@/lib/viewer-source\";\n\nimport {\n  DEFAULT_MAX_BYTES,\n  DEFAULT_MAX_LINES,\n  type TextViewerBounds,\n} from \"./plain-text-resource\";\nimport { useIsClient } from \"./use-is-client\";\nimport { ViewerErrorBoundary } from \"./viewer-error\";\n\ntype TextResourceSource = UrlViewerSource | BlobViewerSource | TextSource;\ntype ClientFallbackPolicy = \"always\" | \"non-inline-source\";\ntype ContentResetPolicy = \"content\" | \"inline-retry\";\n\nexport interface PlainTextViewerFrameProps<\n  THandle,\n  TProps extends PlainTextViewerFramePublicProps,\n> {\n  props: TProps;\n  resource?: ViewerResource;\n  forwardedRef: React.ForwardedRef<THandle>;\n  clientFallbackPolicy: ClientFallbackPolicy;\n  contentResetPolicy?: ContentResetPolicy;\n  Fallback: React.ComponentType<PlainTextViewerFallbackProps>;\n  Content: React.ComponentType<\n    TProps & {\n      resource: ViewerResource;\n      retryVersion: number;\n      forwardedRef?: React.ForwardedRef<THandle>;\n    }\n  >;\n}\n\nexport interface PlainTextViewerFramePublicProps extends TextViewerBounds {\n  source?: TextResourceSource;\n  className?: string;\n  controls?: boolean;\n  download?: boolean;\n  bare?: boolean;\n}\n\nexport interface PlainTextViewerFallbackProps {\n  className?: string;\n  controls?: boolean;\n  download?: boolean;\n  bare?: boolean;\n}\n\nexport function PlainTextViewerFrame<\n  THandle,\n  TProps extends PlainTextViewerFramePublicProps,\n>({\n  props,\n  resource: resourceProp,\n  forwardedRef,\n  clientFallbackPolicy,\n  contentResetPolicy = \"content\",\n  Fallback,\n  Content,\n}: PlainTextViewerFrameProps<THandle, TProps>) {\n  const [retryState, setRetryState] = React.useState({\n    contentKey: \"\",\n    version: 0,\n  });\n  const isClient = useIsClient();\n  const { source } = props;\n  const createdResource = React.useMemo(\n    () => (source ? createViewerResource(source) : null),\n    [source],\n  );\n  const resource = resourceProp ?? createdResource;\n  if (!resource) {\n    throw new Error(\"PlainTextViewerFrame requires a source or resource.\");\n  }\n  const contentBaseKey = plainTextViewerContentBaseKey(resource, props);\n  const retryVersion =\n    retryState.contentKey === contentBaseKey ? retryState.version : 0;\n  const resetKey = plainTextViewerResetKey(resource, props, retryVersion);\n  const contentResetKey = plainTextViewerContentResetKey(\n    contentBaseKey,\n    retryVersion,\n  );\n  const suspenseResetKey =\n    contentResetPolicy === \"inline-retry\" && resource.sourceKind === \"text\"\n      ? String(retryVersion)\n      : contentResetKey;\n\n  if (\n    !isClient &&\n    shouldRenderClientFallback(clientFallbackPolicy, resource.sourceKind)\n  ) {\n    return (\n      <Fallback\n        className={props.className}\n        controls={props.controls}\n        download={props.download}\n        bare={props.bare}\n      />\n    );\n  }\n\n  return (\n    <ViewerErrorBoundary\n      bare={props.bare}\n      className={props.className}\n      download={\n        props.controls === false || props.download === false\n          ? null\n          : plainTextViewerDownloadAction(resource)\n      }\n      format=\"text\"\n      mapError={plainTextViewerBoundaryError}\n      resetKey={resetKey}\n      sourceKind={resource.sourceKind}\n      onRetry={() =>\n        setRetryState((state) => ({\n          contentKey: contentBaseKey,\n          version: state.contentKey === contentBaseKey ? state.version + 1 : 1,\n        }))\n      }\n    >\n      <React.Suspense\n        key={suspenseResetKey}\n        fallback={\n          <Fallback\n            className={props.className}\n            controls={props.controls}\n            download={props.download}\n            bare={props.bare}\n          />\n        }\n      >\n        <Content\n          {...props}\n          forwardedRef={forwardedRef}\n          retryVersion={retryVersion}\n          resource={resource}\n        />\n      </React.Suspense>\n    </ViewerErrorBoundary>\n  );\n}\n\nfunction plainTextViewerDownloadAction(resource: ViewerResource) {\n  return resource.originalDownload;\n}\n\nfunction plainTextViewerBoundaryError(error: unknown) {\n  if (\n    isResourceError(error) ||\n    isViewerFormatError(error) ||\n    isViewerStateError(error) ||\n    isViewerUnsupportedError(error)\n  ) {\n    return error;\n  }\n\n  return new ViewerFormatError({\n    format: \"text\",\n    kind: \"render_failed\",\n    message: \"Failed to render text.\",\n    cause: error,\n  });\n}\n\nfunction plainTextViewerResetKey(\n  resource: ViewerResource,\n  props: Pick<PlainTextViewerFramePublicProps, \"maxBytes\" | \"maxLines\">,\n  retryVersion: number,\n): string {\n  const [maxBytesKey, maxLinesKey] = plainTextViewerBoundsResetKey(props);\n  return [\n    viewerResourceRenderKey(resource),\n    retryVersion,\n    maxBytesKey,\n    maxLinesKey,\n  ].join(\"\\u0000\");\n}\n\nfunction plainTextViewerContentResetKey(\n  contentBaseKey: string,\n  retryVersion: number,\n): string {\n  return [contentBaseKey, retryVersion].join(\"\\u0000\");\n}\n\nfunction plainTextViewerContentBaseKey(\n  resource: ViewerResource,\n  props: Pick<PlainTextViewerFramePublicProps, \"maxBytes\" | \"maxLines\">,\n): string {\n  const [maxBytesKey, maxLinesKey] = plainTextViewerBoundsResetKey(props);\n  return [\n    viewerContentRenderKey(resource.content),\n    maxBytesKey,\n    maxLinesKey,\n  ].join(\"\\u0000\");\n}\n\nfunction plainTextViewerBoundsResetKey(\n  props: Pick<PlainTextViewerFramePublicProps, \"maxBytes\" | \"maxLines\">,\n) {\n  return [\n    plainTextViewerBoundResetKeyPart(props.maxBytes, DEFAULT_MAX_BYTES),\n    plainTextViewerBoundResetKeyPart(props.maxLines, DEFAULT_MAX_LINES),\n  ] as const;\n}\n\nfunction plainTextViewerBoundResetKeyPart(\n  value: number | undefined,\n  defaultValue: number,\n) {\n  return String(value === undefined ? defaultValue : value);\n}\n\nfunction shouldRenderClientFallback(\n  policy: ClientFallbackPolicy,\n  sourceKind: TextResourceSource[\"kind\"],\n) {\n  return policy === \"always\" || sourceKind !== \"text\";\n}\n",
      "type": "registry:ui",
      "target": "@ui/plain-text-viewer-frame.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/text-viewer-chrome.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { AlertCircle, Check, Copy } from \"lucide-react\";\nimport { useMountEffect } from \"@/hooks/use-mount-effect\";\n\nimport { type ViewerDownloadAction } from \"@/lib/viewer-download-actions\";\n\nimport { Skeleton } from \"./skeleton\";\nimport { TextCodeViewerFrame } from \"./text-code-viewer-chrome\";\nimport type { ViewerDownloadErrorHandler } from \"./viewer-download\";\nimport {\n  ViewerControls,\n  ViewerControlButton,\n  ViewerControlsSkeleton,\n} from \"./viewer-controls\";\n\nexport type ViewerClipboardCopyStatus = \"copied\" | \"failed\" | \"idle\";\n\nexport function TextViewerFrame({\n  className,\n  bare,\n  children,\n}: {\n  className?: string;\n  bare?: boolean;\n  children: React.ReactNode;\n}) {\n  return (\n    <TextCodeViewerFrame\n      bare={bare}\n      bareClassName=\"h-full bg-background\"\n      className={className}\n      dataSlot=\"text-viewer\"\n      framedClassName=\"rounded-xl border bg-background\"\n    >\n      {children}\n    </TextCodeViewerFrame>\n  );\n}\n\nexport function TextViewerFallback({\n  className,\n  controls = true,\n  download = true,\n  bare,\n}: {\n  className?: string;\n  controls?: boolean;\n  download?: boolean;\n  bare?: boolean;\n}) {\n  return (\n    <TextViewerFrame className={className} bare={bare}>\n      {controls ? (\n        <ViewerControlsSkeleton title zoom download={download} />\n      ) : null}\n      <div\n        className=\"min-h-0 flex-1 space-y-3 overflow-hidden p-5\"\n        data-slot=\"text-body-skeleton\"\n      >\n        {Array.from({ length: 10 }, (_, index) => (\n          <Skeleton\n            key={index}\n            className=\"h-4\"\n            style={{ width: `${48 + ((index * 17) % 44)}%` }}\n          />\n        ))}\n      </div>\n    </TextViewerFrame>\n  );\n}\n\nexport function TextViewerControls({\n  wordCount,\n  fontScale,\n  copyText,\n  copyLabel = \"Copy text\",\n  downloadAction,\n  extra,\n  leading,\n  onDownloadError,\n  onZoomOut,\n  onZoomIn,\n  onResetZoom,\n}: {\n  wordCount: number;\n  fontScale: number;\n  copyText?: string;\n  copyLabel?: string;\n  downloadAction?: ViewerDownloadAction | null;\n  extra?: React.ReactNode;\n  leading?: React.ReactNode;\n  onDownloadError?: ViewerDownloadErrorHandler;\n  onZoomOut: () => void;\n  onZoomIn: () => void;\n  onResetZoom: () => void;\n}) {\n  const copyControl =\n    copyText == null ? null : (\n      <TextViewerCopyControl label={copyLabel} text={copyText} />\n    );\n\n  return (\n    <ViewerControls\n      title={leading ?? `${wordCount} word${wordCount === 1 ? \"\" : \"s\"}`}\n      zoom={{\n        scale: fontScale,\n        onZoomOut,\n        onZoomIn,\n        onFit: onResetZoom,\n        fitLabel: \"Reset zoom\",\n      }}\n      downloads={downloadAction ? [downloadAction] : undefined}\n      onDownloadError={onDownloadError}\n      extra={\n        extra == null && copyControl == null ? null : (\n          <span className=\"flex min-w-0 items-center gap-1\">\n            {extra}\n            {copyControl}\n          </span>\n        )\n      }\n    />\n  );\n}\n\nfunction TextViewerCopyControl({\n  label,\n  text,\n}: {\n  label: string;\n  text: string;\n}) {\n  const { copy, status } = useViewerClipboardCopy();\n\n  const copyText = () => {\n    copy(text);\n  };\n\n  const buttonLabel =\n    status === \"copied\"\n      ? \"Copied\"\n      : status === \"failed\"\n        ? \"Copy failed\"\n        : label;\n\n  return (\n    <ViewerControlButton label={buttonLabel} onClick={copyText} type=\"button\">\n      {status === \"copied\" ? (\n        <Check />\n      ) : status === \"failed\" ? (\n        <AlertCircle />\n      ) : (\n        <Copy />\n      )}\n    </ViewerControlButton>\n  );\n}\n\nexport function useViewerClipboardCopy({\n  resetDelay = 1200,\n}: {\n  resetDelay?: number;\n} = {}) {\n  const [status, setStatus] = React.useState<ViewerClipboardCopyStatus>(\"idle\");\n  const timeoutRef = React.useRef<number | null>(null);\n  const isMountedRef = React.useRef(true);\n  const copyAttemptRef = React.useRef(0);\n\n  useMountEffect(() => {\n    isMountedRef.current = true;\n    return () => {\n      isMountedRef.current = false;\n      clearViewerClipboardCopyReset(timeoutRef);\n    };\n  });\n\n  const scheduleReset = React.useCallback(() => {\n    clearViewerClipboardCopyReset(timeoutRef);\n    timeoutRef.current = window.setTimeout(() => {\n      timeoutRef.current = null;\n      if (isMountedRef.current) setStatus(\"idle\");\n    }, resetDelay);\n  }, [resetDelay]);\n\n  const copy = React.useCallback(\n    (text: string) => {\n      clearViewerClipboardCopyReset(timeoutRef);\n      const copyAttempt = copyAttemptRef.current + 1;\n      copyAttemptRef.current = copyAttempt;\n      const isCurrentAttempt = () =>\n        isMountedRef.current && copyAttemptRef.current === copyAttempt;\n\n      try {\n        const clipboard = navigator.clipboard;\n        const writeText = clipboard?.writeText;\n        if (typeof writeText !== \"function\") {\n          setStatus(\"failed\");\n          scheduleReset();\n          return;\n        }\n\n        void Promise.resolve(writeText.call(clipboard, text)).then(\n          () => {\n            if (!isCurrentAttempt()) return;\n            setStatus(\"copied\");\n            scheduleReset();\n          },\n          () => {\n            if (!isCurrentAttempt()) return;\n            setStatus(\"failed\");\n            scheduleReset();\n          },\n        );\n      } catch {\n        setStatus(\"failed\");\n        scheduleReset();\n      }\n    },\n    [scheduleReset],\n  );\n\n  return { copy, status };\n}\n\nfunction clearViewerClipboardCopyReset(\n  timeoutRef: React.MutableRefObject<number | null>,\n) {\n  if (timeoutRef.current === null) return;\n  window.clearTimeout(timeoutRef.current);\n  timeoutRef.current = null;\n}\n",
      "type": "registry:ui",
      "target": "@ui/text-viewer-chrome.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/text-code-viewer-chrome.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport function TextCodeViewerFrame({\n  bare,\n  bareClassName,\n  children,\n  className,\n  dataSlot,\n  framedClassName,\n}: {\n  bare?: boolean;\n  bareClassName: string;\n  children: React.ReactNode;\n  className?: string;\n  dataSlot: string;\n  framedClassName: string;\n}) {\n  return (\n    <div\n      className={cn(\n        \"flex min-h-0 flex-col overflow-hidden\",\n        bare ? bareClassName : framedClassName,\n        className,\n      )}\n      data-slot={dataSlot}\n    >\n      {children}\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/text-code-viewer-chrome.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/viewer-download.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Download } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport {\n  ViewerDownloadError,\n  type ViewerDownloadAction,\n  type ViewerDownloadPayload,\n} from \"@/lib/viewer-download-actions\";\n\nimport { Spinner } from \"@/components/ui/spinner\";\n\nimport { Button, buttonVariants } from \"./button\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"./dropdown-menu\";\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nexport interface TriggerViewerDownloadOptions {\n  signal?: AbortSignal;\n}\n\nexport async function triggerViewerDownload(\n  action: ViewerDownloadAction,\n  options?: TriggerViewerDownloadOptions,\n): Promise<void> {\n  if (action.isDisabled) {\n    throw new ViewerDownloadError({\n      actionId: action.id,\n      kind: \"disabled\",\n      message: \"This download is disabled.\",\n    });\n  }\n\n  let payload: ViewerDownloadPayload;\n  try {\n    payload = await action.getPayload(options);\n  } catch (error) {\n    if (isAbortError(error)) {\n      throw new ViewerDownloadError({\n        actionId: action.id,\n        kind: \"aborted\",\n        message: \"Download was cancelled.\",\n        cause: error,\n      });\n    }\n    throw new ViewerDownloadError({\n      actionId: action.id,\n      kind: \"payload_failed\",\n      message: \"Could not prepare this download.\",\n      cause: error,\n    });\n  }\n\n  if (payload.kind === \"none\") return;\n  if (payload.kind === \"href\") {\n    try {\n      clickDownload(payload.href, action.fileName);\n    } catch (error) {\n      throw new ViewerDownloadError({\n        actionId: action.id,\n        kind: \"unsupported\",\n        message: \"Could not start this download.\",\n        cause: error,\n      });\n    }\n    return;\n  }\n\n  try {\n    const blob =\n      payload.kind === \"blob\"\n        ? payload.blob\n        : new Blob([payload.text], {\n            type: payload.mimeType ?? \"text/plain;charset=utf-8\",\n          });\n    const url = URL.createObjectURL(blob);\n    try {\n      clickDownload(url, action.fileName);\n    } finally {\n      URL.revokeObjectURL(url);\n    }\n  } catch (error) {\n    throw new ViewerDownloadError({\n      actionId: action.id,\n      kind: \"unsupported\",\n      message: \"Could not start this download.\",\n      cause: error,\n    });\n  }\n}\n\nexport type ViewerDownloadErrorHandler = (\n  error: ViewerDownloadError,\n  action: ViewerDownloadAction,\n) => void;\n\nexport interface ViewerDownloadTrigger {\n  pendingActionId: string | null;\n  triggerDownload: (action: ViewerDownloadAction) => void;\n}\n\nexport interface ViewerDownloadTriggerOptions {\n  /** Reports non-aborted action failures; visible failure UI belongs to consumers. */\n  onError?: ViewerDownloadErrorHandler;\n  resetKey?: unknown;\n}\n\nexport interface ViewerDownloadControlProps {\n  actions: Array<ViewerDownloadAction | null | undefined>;\n  variant?: React.ComponentProps<typeof Button>[\"variant\"];\n  size?: React.ComponentProps<typeof Button>[\"size\"];\n  className?: string;\n  showLabel?: boolean;\n  /** Reports non-aborted action failures; visible failure UI belongs to consumers. */\n  onError?: ViewerDownloadErrorHandler;\n}\n\nexport interface ViewerDownloadButtonProps\n  extends Omit<ViewerDownloadControlProps, \"actions\"> {\n  action: ViewerDownloadAction | null;\n}\n\nexport interface ViewerDownloadMenuProps\n  extends Omit<ViewerDownloadControlProps, \"actions\"> {\n  actions: ViewerDownloadAction[];\n}\n\nexport function useViewerDownloadHref(\n  action: ViewerDownloadAction | null,\n): string | null {\n  const shouldCreateHref = action?.origin !== \"derived\";\n  const payload = shouldCreateHref ? getSynchronousPayload(action) : null;\n\n  return shouldCreateHref && payload?.kind === \"href\" ? payload.href : null;\n}\n\nexport function useViewerDownloadTrigger({\n  onError,\n  resetKey = \"\",\n}: ViewerDownloadTriggerOptions = {}): ViewerDownloadTrigger {\n  const [pendingActionId, setPendingActionId] = React.useState<string | null>(\n    null,\n  );\n  const abortControllerRef = React.useRef<AbortController | null>(null);\n\n  useKeyedMountEffect(joinEffectKey([resetKey]), () => {\n    return () => {\n      abortControllerRef.current?.abort();\n      abortControllerRef.current = null;\n    };\n  });\n\n  const triggerDownload = React.useCallback(\n    (action: ViewerDownloadAction) => {\n      abortControllerRef.current?.abort();\n      const abortController = new AbortController();\n      abortControllerRef.current = abortController;\n      setPendingActionId(action.id);\n      void triggerViewerDownload(action, { signal: abortController.signal })\n        .catch((error) => {\n          reportDownloadError(error, action, onError);\n        })\n        .finally(() => {\n          if (abortControllerRef.current === abortController) {\n            abortControllerRef.current = null;\n            setPendingActionId(null);\n          }\n        });\n    },\n    [onError],\n  );\n\n  return { pendingActionId, triggerDownload };\n}\n\nexport function ViewerDownloadControl({\n  actions,\n  variant = \"ghost\",\n  size = \"iconSm\",\n  className = \"size-7\",\n  showLabel = false,\n  onError,\n}: ViewerDownloadControlProps) {\n  const enabledActions = actions.filter(\n    (action): action is ViewerDownloadAction => Boolean(action),\n  );\n\n  if (enabledActions.length <= 1) {\n    return (\n      <ViewerDownloadButton\n        action={enabledActions[0] ?? null}\n        variant={variant}\n        size={size}\n        className={className}\n        showLabel={showLabel}\n        onError={onError}\n      />\n    );\n  }\n\n  return (\n    <ViewerDownloadMenu\n      actions={enabledActions}\n      variant={variant}\n      size={size}\n      className={className}\n      showLabel={showLabel}\n      onError={onError}\n    />\n  );\n}\n\nexport function ViewerDownloadButton({\n  action,\n  variant = \"ghost\",\n  size = \"iconSm\",\n  className = \"size-7\",\n  showLabel = false,\n  onError,\n}: ViewerDownloadButtonProps) {\n  const href = useViewerDownloadHref(action);\n  const label = action?.label ?? \"Download\";\n  const disabled = !action || action.isDisabled;\n  const hasDownloadHref = Boolean(href);\n  const { pendingActionId, triggerDownload } = useViewerDownloadTrigger({\n    onError,\n    resetKey: action,\n  });\n  const isPending = Boolean(action && pendingActionId === action.id);\n\n  const handleClick = React.useCallback(() => {\n    if (!action || hasDownloadHref) return;\n    triggerDownload(action);\n  }, [action, hasDownloadHref, triggerDownload]);\n\n  if (href) {\n    return (\n      <a\n        href={href}\n        download={action?.fileName}\n        className={cn(buttonVariants({ variant, size }), className)}\n        aria-label={label}\n        title={label}\n        data-slot=\"button\"\n      >\n        <Download className={showLabel ? \"mr-1.5 size-4\" : undefined} />\n        {showLabel ? label : null}\n      </a>\n    );\n  }\n\n  return (\n    <Button\n      variant={variant}\n      size={size}\n      className={className}\n      aria-label={label}\n      title={label}\n      disabled={disabled || isPending}\n      onClick={handleClick}\n    >\n      {isPending ? (\n        <Spinner className=\"size-4 animate-spin\" />\n      ) : (\n        <Download className={showLabel ? \"mr-1.5 size-4\" : undefined} />\n      )}\n      {showLabel ? label : null}\n    </Button>\n  );\n}\n\nexport function ViewerDownloadMenu({\n  actions,\n  variant = \"ghost\",\n  size = \"iconSm\",\n  className = \"size-7\",\n  showLabel = false,\n  onError,\n}: ViewerDownloadMenuProps) {\n  const actionSetKey = actions.map((action) => action.id).join(\"\\u0000\");\n  const label = \"Download\";\n  const { pendingActionId, triggerDownload } = useViewerDownloadTrigger({\n    onError,\n    resetKey: actionSetKey,\n  });\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button\n          variant={variant}\n          size={size}\n          className={className}\n          aria-label={label}\n          title={label}\n          disabled={pendingActionId != null}\n        >\n          {pendingActionId != null ? (\n            <Spinner className=\"size-4 animate-spin\" />\n          ) : (\n            <Download className={showLabel ? \"mr-1.5 size-4\" : undefined} />\n          )}\n          {showLabel ? label : null}\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent align=\"end\">\n        {actions.map((action) => (\n          <DropdownMenuItem\n            key={action.id}\n            disabled={action.isDisabled || pendingActionId != null}\n            onClick={() => triggerDownload(action)}\n          >\n            <Download />\n            <span>{action.label}</span>\n          </DropdownMenuItem>\n        ))}\n      </DropdownMenuContent>\n    </DropdownMenu>\n  );\n}\n\nfunction clickDownload(href: string, fileName: string) {\n  const anchor = document.createElement(\"a\");\n  anchor.href = href;\n  anchor.download = fileName;\n  anchor.rel = \"noreferrer\";\n  document.body.appendChild(anchor);\n  anchor.click();\n  anchor.remove();\n}\n\nfunction getSynchronousPayload(\n  action: ViewerDownloadAction | null,\n): ViewerDownloadPayload | null {\n  if (!action || action.isDisabled) return null;\n  const payload = action.getPayload();\n  return payload instanceof Promise ? null : payload;\n}\n\nfunction reportDownloadError(\n  error: unknown,\n  action: ViewerDownloadAction,\n  onError: ViewerDownloadErrorHandler | undefined,\n) {\n  if (!(error instanceof ViewerDownloadError)) return;\n  if (error.kind === \"aborted\") return;\n  onError?.(error, action);\n}\n\nfunction isAbortError(error: unknown) {\n  return error instanceof DOMException && error.name === \"AbortError\";\n}\n",
      "type": "registry:ui",
      "target": "@ui/viewer-download.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/viewer-error.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { RotateCcw } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\nimport type { ViewerDownloadAction } from \"@/lib/viewer-download-actions\";\nimport {\n  toViewerErrorInfo,\n  type ViewerErrorContext,\n  type ViewerFormat,\n} from \"@/lib/viewer-errors\";\n\nimport { Button } from \"./button\";\nimport { ViewerDownloadButton } from \"./viewer-download\";\n\nexport interface ViewerErrorStateProps extends ViewerErrorContext {\n  error: unknown;\n  download?: ViewerDownloadAction | null;\n  className?: string;\n  bare?: boolean;\n  variant?: \"card\" | \"document\" | \"inline\";\n  onRetry?: () => void;\n}\n\nexport function ViewerErrorState({\n  error,\n  format,\n  sourceKind,\n  canDownload,\n  retry,\n  download,\n  className,\n  bare = false,\n  variant = \"card\",\n  onRetry,\n}: ViewerErrorStateProps) {\n  const info = toViewerErrorInfo(error, {\n    format,\n    sourceKind,\n    canDownload: canDownload ?? Boolean(download && !download.isDisabled),\n    retry,\n  });\n  const showRetry = info.isRetryable && onRetry;\n  const showDownload =\n    info.isDownloadUseful && download != null && !download.isDisabled;\n\n  return (\n    <div\n      className={cn(errorStateClassName({ bare, variant }), className)}\n      data-error-domain={info.domain}\n      data-error-format={info.format}\n      data-error-kind={info.kind}\n      data-error-message={info.message}\n      data-slot=\"viewer-error\"\n      role=\"alert\"\n    >\n      <p>{info.userMessage}</p>\n      {showRetry || showDownload ? (\n        <div className=\"flex items-center gap-2\">\n          {showRetry ? (\n            <Button variant=\"outline\" size=\"sm\" onClick={onRetry}>\n              <RotateCcw className=\"mr-1.5 size-4\" />\n              Retry\n            </Button>\n          ) : null}\n          {showDownload ? (\n            <ViewerDownloadButton\n              action={download}\n              variant={showRetry ? \"ghost\" : \"outline\"}\n              size=\"sm\"\n              className=\"\"\n              showLabel\n            />\n          ) : null}\n        </div>\n      ) : null}\n    </div>\n  );\n}\n\nexport interface ViewerErrorBoundaryProps extends ViewerErrorContext {\n  children: React.ReactNode;\n  resetKey?: unknown;\n  download?: ViewerDownloadAction | null;\n  className?: string;\n  bare?: boolean;\n  variant?: \"card\" | \"document\" | \"inline\";\n  mapError?: (error: unknown) => unknown;\n  onRetry?: (error: unknown) => void;\n  onCaughtError?: (error: unknown, errorInfo: React.ErrorInfo) => void;\n}\n\nexport class ViewerErrorBoundary extends React.Component<\n  ViewerErrorBoundaryProps,\n  { error: unknown | null; retryKey: number }\n> {\n  state: Readonly<{ error: unknown | null; retryKey: number }> = {\n    error: null,\n    retryKey: 0,\n  };\n\n  componentDidUpdate(previousProps: ViewerErrorBoundaryProps) {\n    if (\n      previousProps.resetKey !== this.props.resetKey &&\n      this.state.error != null\n    ) {\n      this.setState({ error: null });\n    }\n  }\n\n  static getDerivedStateFromError(error: unknown) {\n    return { error };\n  }\n\n  componentDidCatch(error: unknown, errorInfo: React.ErrorInfo) {\n    this.props.onCaughtError?.(error, errorInfo);\n  }\n\n  render() {\n    if (this.state.error != null) {\n      const error = this.props.mapError\n        ? this.props.mapError(this.state.error)\n        : this.state.error;\n\n      return (\n        <ViewerErrorState\n          error={error}\n          format={this.props.format}\n          sourceKind={this.props.sourceKind}\n          canDownload={this.props.canDownload}\n          retry={this.props.retry}\n          download={this.props.download}\n          className={this.props.className}\n          bare={this.props.bare}\n          variant={this.props.variant}\n          onRetry={() => {\n            this.setState((state) => ({\n              error: null,\n              retryKey: state.retryKey + 1,\n            }));\n            this.props.onRetry?.(this.state.error);\n          }}\n        />\n      );\n    }\n\n    return (\n      <React.Fragment key={this.state.retryKey}>\n        {this.props.children}\n      </React.Fragment>\n    );\n  }\n}\n\nfunction errorStateClassName({\n  bare,\n  variant,\n}: {\n  bare: boolean;\n  variant: \"card\" | \"document\" | \"inline\";\n}) {\n  if (variant === \"inline\") {\n    return \"flex h-24 flex-col items-center justify-center gap-3 px-3 text-center text-xs text-muted-foreground\";\n  }\n  return cn(\n    \"flex min-h-64 flex-col items-center justify-center gap-3 p-6 text-center text-sm text-muted-foreground\",\n    bare ? \"bg-muted/20\" : \"rounded-xl border bg-muted/30\",\n    variant === \"document\" && \"min-h-full\",\n  );\n}\n\nexport type { ViewerFormat };\n",
      "type": "registry:ui",
      "target": "@ui/viewer-error.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/text-viewer-layout.ts",
      "content": "\"use client\";\n\nimport {\n  layoutNextLineRange,\n  materializeLineRange,\n  measureLineStats,\n  measureNaturalWidth,\n  prepareWithSegments,\n  type LayoutCursor,\n  type LayoutLine,\n  type PreparedTextWithSegments,\n} from \"@chenglou/pretext\";\nimport {\n  layoutNextRichInlineLineRange,\n  materializeRichInlineLineRange,\n  measureRichInlineStats,\n  prepareRichInline,\n  type PreparedRichInline,\n  type RichInlineCursor,\n  type RichInlineLine,\n} from \"@chenglou/pretext/rich-inline\";\nimport { marked, type Token, type Tokens } from \"marked\";\n\nimport { splitTextLines } from \"./text-viewer-resource\";\n\nexport type TextViewerMode = \"markdown\" | \"text\";\n\nexport interface TextStyleConfig {\n  fontEpoch?: number;\n  fontScale: number;\n}\n\nexport interface PreparedTextDocument {\n  blocks: PreparedTextBlock[];\n  mode: TextViewerMode;\n  sourceLineCount: number;\n  wordCount: number;\n}\n\nexport type PreparedTextBlock =\n  | PreparedInlineTextBlock\n  | PreparedCodeTextBlock\n  | PreparedImageTextBlock\n  | PreparedRuleTextBlock\n  | PreparedTableTextBlock;\n\nexport interface PreparedTextBlockBase {\n  contentLeft: number;\n  listDepth: number;\n  marginTop: number;\n  markerClassName: string | null;\n  markerLeft: number | null;\n  markerText: string | null;\n  quoteDepth: number;\n  quoteRailLefts: number[];\n  sourceEndLine: number;\n  sourceStartLine: number;\n}\n\nexport interface PreparedInlineTextBlock extends PreparedTextBlockBase {\n  classNames: string[];\n  fallbackText: string;\n  flow: PreparedRichInline | null;\n  fonts: string[];\n  headingId: string | null;\n  hrefs: Array<string | null>;\n  kind: \"inline\";\n  lineHeight: number;\n  texts: string[];\n  titles: Array<string | null>;\n  variant: InlineVariant;\n}\n\nexport interface PreparedCodeTextBlock extends PreparedTextBlockBase {\n  fallbackText: string;\n  font: string;\n  kind: \"code\";\n  language: string | null;\n  lineHeight: number;\n  prepared: PreparedTextWithSegments | null;\n}\n\nexport interface PreparedImageTextBlock extends PreparedTextBlockBase {\n  alt: string;\n  href: string | null;\n  kind: \"image\";\n  src: string | null;\n  title: string | null;\n}\n\nexport interface PreparedRuleTextBlock extends PreparedTextBlockBase {\n  height: number;\n  kind: \"rule\";\n}\n\nexport interface PreparedTableTextBlock extends PreparedTextBlockBase {\n  alignments: TableColumnAlignment[];\n  columnWidths: number[];\n  header: PreparedTableCell[];\n  kind: \"table\";\n  rowSourceStartLines: number[];\n  rows: PreparedTableCell[][];\n}\n\nexport interface PreparedTableCell {\n  className: string;\n  href: string | null;\n  text: string;\n  title: string | null;\n}\n\nexport interface TextDocumentFrame {\n  frames: TextBlockFrame[];\n  totalHeight: number;\n  width: number;\n}\n\nexport type TextBlockFrame =\n  | InlineTextBlockFrame\n  | CodeTextBlockFrame\n  | ImageTextBlockFrame\n  | RuleTextBlockFrame\n  | TableTextBlockFrame;\n\ninterface TextBlockFrameBase {\n  blockIndex: number;\n  bottom: number;\n  contentLeft: number;\n  height: number;\n  listDepth: number;\n  markerClassName: string | null;\n  markerLeft: number | null;\n  markerText: string | null;\n  quoteDepth: number;\n  quoteRailLefts: number[];\n  sourceEndLine: number;\n  sourceStartLine: number;\n  scale: number;\n  top: number;\n}\n\nexport interface InlineTextBlockFrame extends TextBlockFrameBase {\n  kind: \"inline\";\n  lineCount: number;\n  lineHeight: number;\n  usedWidth: number;\n}\n\nexport interface CodeTextBlockFrame extends TextBlockFrameBase {\n  kind: \"code\";\n  language: string | null;\n  lineCount: number;\n  lineHeight: number;\n  width: number;\n}\n\nexport interface ImageTextBlockFrame extends TextBlockFrameBase {\n  alt: string;\n  imageHeight: number;\n  imageWidth: number;\n  kind: \"image\";\n}\n\nexport interface RuleTextBlockFrame extends TextBlockFrameBase {\n  kind: \"rule\";\n  width: number;\n}\n\nexport interface TableTextBlockFrame extends TextBlockFrameBase {\n  columnWidths: number[];\n  headerHeight: number;\n  kind: \"table\";\n  rowHeights: number[];\n  rowOffsets: number[];\n  rowCount: number;\n  rowSourceStartLines: number[];\n  tableWidth: number;\n}\n\nexport interface InlineFragmentLayout {\n  className: string;\n  font: string;\n  href: string | null;\n  leadingGap: number;\n  text: string;\n  title: string | null;\n}\n\nexport interface InlineLineLayout {\n  fragments: InlineFragmentLayout[];\n  lineIndex: number;\n  top: number;\n  width: number;\n}\n\nexport interface CodeLineLayout {\n  lineIndex: number;\n  line: LayoutLine;\n  top: number;\n}\n\nexport interface TextLineWindow {\n  firstLine: number;\n  lastLine: number;\n}\n\nexport type TableColumnAlignment = \"center\" | \"left\" | \"right\";\n\nexport interface TableRowWindow {\n  afterHeight: number;\n  beforeHeight: number;\n  endIndex: number;\n  startIndex: number;\n}\n\ntype InlineVariant = \"body\" | \"heading-1\" | \"heading-2\";\n\ntype MarkState = {\n  bold: boolean;\n  italic: boolean;\n  strike: boolean;\n  href: string | null;\n  title: string | null;\n};\n\ntype ParseContext = {\n  listDepth: number;\n  quoteDepth: number;\n};\n\ntype HeadingIdRegistry = Map<string, number>;\n\ntype InlinePiece = {\n  breakMode: \"never\" | \"normal\";\n  className: string;\n  extraWidth: number;\n  font: string;\n  href: string | null;\n  text: string;\n  title: string | null;\n};\n\nconst BODY_FONT_PX = 15;\nconst BODY_LINE_PX = 24;\nconst HEADING_ONE_FONT_PX = 22;\nconst HEADING_ONE_LINE_PX = 32;\nconst HEADING_TWO_FONT_PX = 18;\nconst HEADING_TWO_LINE_PX = 28;\nconst CODE_FONT_PX = 13;\nconst CODE_LINE_PX = 20;\nconst MARKER_FONT_PX = 12;\nconst CHIP_FONT_PX = 12;\nconst INLINE_CODE_EXTRA_WIDTH = 12;\nconst IMAGE_EXTRA_WIDTH = 16;\nconst CODE_BLOCK_PADDING_X = 12;\nconst CODE_BLOCK_PADDING_Y = 10;\nconst DOCUMENT_PADDING_Y = 16;\nconst BLOCK_GAP = 12;\nconst RICH_BLOCK_GAP = 6;\nconst HARD_BREAK_GAP = 4;\nconst LIST_ITEM_GAP = 4;\nconst LIST_MARKER_GAP = 10;\nconst LIST_NESTING_INDENT = 20;\nconst BLOCKQUOTE_INDENT = 18;\nconst RAIL_OFFSET = 5;\nconst RULE_HEIGHT = 20;\nconst IMAGE_BLOCK_HEIGHT = 220;\nconst IMAGE_BLOCK_MAX_WIDTH = 720;\nconst IMAGE_BLOCK_MIN_WIDTH = 220;\nconst IMAGE_PLACEHOLDER_HEIGHT = 72;\nconst TABLE_CELL_FONT_PX = 13;\nconst TABLE_CELL_PADDING_X = 14;\nconst TABLE_COLUMN_MIN_WIDTH = 72;\nconst TABLE_COLUMN_MAX_WIDTH = 320;\nconst TABLE_HEADER_HEIGHT = 38;\nconst TABLE_ROW_MIN_HEIGHT = 34;\nconst TABLE_ROW_LINE_HEIGHT = 20;\nconst TABLE_ROW_OVERSCAN = 4;\nconst HARD_WRAPPED_LINE_MIN_LENGTH = 52;\nconst HARD_WRAPPED_RUN_AVERAGE_LENGTH = 64;\nconst SANS_FAMILY = \"Arial, Helvetica, sans-serif\";\nconst SERIF_FAMILY = \"Georgia, Times New Roman, serif\";\nconst MONO_FAMILY = '\"SF Mono\", Menlo, Monaco, Consolas, monospace';\nconst EMPTY_MARK_STATE: MarkState = {\n  bold: false,\n  href: null,\n  italic: false,\n  strike: false,\n  title: null,\n};\nconst PREPARED_TEXT_DOCUMENT_CACHE_LIMIT = 32;\nconst PREPARED_TEXT_DOCUMENT_CACHE_VERSION = \"prepared-text-v1\";\nconst TEXT_DOCUMENT_FRAME_CACHE_LIMIT = 12;\nconst LINE_RANGE_WIDTH_CACHE_LIMIT = 8;\nconst LINE_RANGE_MATERIALIZED_LINE_CACHE_LIMIT = 1024;\nconst LINE_RANGE_CHECKPOINT_CACHE_LIMIT = 128;\nconst LINE_RANGE_CHECKPOINT_INTERVAL = 64;\nconst markerWidthCache = new Map<string, number>();\nconst preparedTextDocumentCache = new Map<string, PreparedTextDocument>();\nconst preparedTextLineSourceIds = new WeakMap<readonly string[], number>();\nconst textDocumentFrameCache = new WeakMap<\n  PreparedTextDocument,\n  Map<string, TextDocumentFrame>\n>();\nconst inlineLineRangeCaches = new WeakMap<\n  PreparedInlineTextBlock,\n  Map<string, RichInlineLineRangeCache>\n>();\nconst codeLineRangeCaches = new WeakMap<\n  PreparedCodeTextBlock,\n  Map<string, CodeLineRangeCache>\n>();\nlet nextPreparedTextLineSourceId = 1;\n\ntype RichInlineLineRangeCache = {\n  checkpoints: Array<LineRangeCheckpoint<RichInlineCursor>>;\n  lines: Map<number, RichInlineLine>;\n};\n\ntype CodeLineRangeCache = {\n  checkpoints: Array<LineRangeCheckpoint<LayoutCursor>>;\n  lines: Map<number, LayoutLine>;\n};\n\ntype LineRangeCheckpoint<Cursor> = {\n  cursor: Cursor;\n  lineIndex: number;\n};\n\nexport function resolveTextViewerMode({\n  fileName,\n  mimeType,\n}: {\n  fileName: string;\n  mimeType?: string;\n}): TextViewerMode {\n  const lowerName = fileName.toLowerCase();\n  const lowerMime = mimeType?.toLowerCase().split(\";\")[0].trim();\n  return lowerName.endsWith(\".md\") ||\n    lowerName.endsWith(\".markdown\") ||\n    lowerMime === \"text/markdown\"\n    ? \"markdown\"\n    : \"text\";\n}\n\nexport function createPreparedTextDocument({\n  lines,\n  mode,\n  text,\n  style,\n}: {\n  lines?: readonly string[];\n  mode: TextViewerMode;\n  text: string;\n  style: TextStyleConfig;\n}): PreparedTextDocument {\n  const sourceLines = mode === \"text\" ? lines : undefined;\n  const cacheKey = preparedTextDocumentCacheKey({\n    lines: sourceLines,\n    mode,\n    text,\n    style,\n  });\n  const cached = preparedTextDocumentCache.get(cacheKey);\n  if (cached) {\n    preparedTextDocumentCache.delete(cacheKey);\n    preparedTextDocumentCache.set(cacheKey, cached);\n    return cached;\n  }\n\n  const document = createUncachedPreparedTextDocument({\n    lines: sourceLines,\n    mode,\n    text,\n    style,\n  });\n  preparedTextDocumentCache.set(cacheKey, document);\n  trimPreparedTextDocumentCache();\n  return document;\n}\n\nexport function clearPreparedTextDocumentCacheForTests() {\n  preparedTextDocumentCache.clear();\n}\n\nfunction createUncachedPreparedTextDocument({\n  lines,\n  mode,\n  text,\n  style,\n}: {\n  lines?: readonly string[];\n  mode: TextViewerMode;\n  text: string;\n  style: TextStyleConfig;\n}): PreparedTextDocument {\n  const sourceLines = mode === \"text\" ? lines : undefined;\n  const sourceLineCount = sourceLines?.length ?? splitTextLines(text).length;\n  const blocks =\n    mode === \"markdown\"\n      ? parseMarkdownBlocks(text, style)\n      : buildPlainTextBlocks(sourceLines ?? splitTextLines(text), style);\n\n  return {\n    blocks,\n    mode,\n    sourceLineCount,\n    wordCount: sourceLines\n      ? countTextLineWords(sourceLines)\n      : countTextWords(text),\n  };\n}\n\nfunction preparedTextDocumentCacheKey({\n  lines,\n  mode,\n  text,\n  style,\n}: {\n  lines?: readonly string[];\n  mode: TextViewerMode;\n  text: string;\n  style: TextStyleConfig;\n}) {\n  const sourceKey = lines\n    ? [\"lines\", preparedTextLineSourceId(lines), lines.length]\n    : [\"text\", text.length, hashTextForPreparedDocument(text)];\n  return [\n    PREPARED_TEXT_DOCUMENT_CACHE_VERSION,\n    mode,\n    style.fontEpoch ?? 0,\n    safeScale(style.fontScale),\n    ...sourceKey,\n  ].join(\"\\u0000\");\n}\n\nfunction preparedTextLineSourceId(lines: readonly string[]) {\n  const cached = preparedTextLineSourceIds.get(lines);\n  if (cached !== undefined) return cached;\n\n  const id = nextPreparedTextLineSourceId;\n  nextPreparedTextLineSourceId += 1;\n  preparedTextLineSourceIds.set(lines, id);\n  return id;\n}\n\nfunction trimPreparedTextDocumentCache() {\n  while (preparedTextDocumentCache.size > PREPARED_TEXT_DOCUMENT_CACHE_LIMIT) {\n    const firstKey = preparedTextDocumentCache.keys().next().value;\n    if (firstKey === undefined) return;\n    preparedTextDocumentCache.delete(firstKey);\n  }\n}\n\nfunction getTextDocumentFrameCache(document: PreparedTextDocument) {\n  let cache = textDocumentFrameCache.get(document);\n  if (!cache) {\n    cache = new Map();\n    textDocumentFrameCache.set(document, cache);\n  }\n  return cache;\n}\n\nfunction textDocumentFrameCacheKey({\n  contentWidth,\n  fontScale,\n}: {\n  contentWidth: number;\n  fontScale: number;\n}) {\n  return [contentWidth, fontScale].join(\"\\u0000\");\n}\n\nfunction getRichInlineLineRangeCache(\n  block: PreparedInlineTextBlock,\n  lineWidth: number,\n) {\n  let blockCache = inlineLineRangeCaches.get(block);\n  if (!blockCache) {\n    blockCache = new Map();\n    inlineLineRangeCaches.set(block, blockCache);\n  }\n\n  const cacheKey = textLineWidthCacheKey(lineWidth);\n  let cache = blockCache.get(cacheKey);\n  if (cache) {\n    setBoundedCacheEntry(\n      blockCache,\n      cacheKey,\n      cache,\n      LINE_RANGE_WIDTH_CACHE_LIMIT,\n    );\n    return cache;\n  }\n\n  cache = {\n    checkpoints: [\n      {\n        cursor: { graphemeIndex: 0, itemIndex: 0, segmentIndex: 0 },\n        lineIndex: 0,\n      },\n    ],\n    lines: new Map(),\n  };\n  setBoundedCacheEntry(\n    blockCache,\n    cacheKey,\n    cache,\n    LINE_RANGE_WIDTH_CACHE_LIMIT,\n  );\n  return cache;\n}\n\nfunction getCodeLineRangeCache(\n  block: PreparedCodeTextBlock,\n  innerWidth: number,\n) {\n  let blockCache = codeLineRangeCaches.get(block);\n  if (!blockCache) {\n    blockCache = new Map();\n    codeLineRangeCaches.set(block, blockCache);\n  }\n\n  const cacheKey = textLineWidthCacheKey(innerWidth);\n  let cache = blockCache.get(cacheKey);\n  if (cache) {\n    setBoundedCacheEntry(\n      blockCache,\n      cacheKey,\n      cache,\n      LINE_RANGE_WIDTH_CACHE_LIMIT,\n    );\n    return cache;\n  }\n\n  cache = {\n    checkpoints: [\n      {\n        cursor: { graphemeIndex: 0, segmentIndex: 0 },\n        lineIndex: 0,\n      },\n    ],\n    lines: new Map(),\n  };\n  setBoundedCacheEntry(\n    blockCache,\n    cacheKey,\n    cache,\n    LINE_RANGE_WIDTH_CACHE_LIMIT,\n  );\n  return cache;\n}\n\nfunction textLineWidthCacheKey(width: number) {\n  return String(width);\n}\n\nfunction setBoundedCacheEntry<K, V>(\n  cache: Map<K, V>,\n  key: K,\n  value: V,\n  limit: number,\n) {\n  cache.delete(key);\n  cache.set(key, value);\n  while (cache.size > limit) {\n    const firstKey = cache.keys().next().value;\n    if (firstKey === undefined) return;\n    cache.delete(firstKey);\n  }\n}\n\nfunction touchCacheEntry<K, V>(cache: Map<K, V>, key: K, value: V) {\n  cache.delete(key);\n  cache.set(key, value);\n}\n\nfunction trimMaterializedLineCache<Line>(cache: Map<number, Line>) {\n  while (cache.size > LINE_RANGE_MATERIALIZED_LINE_CACHE_LIMIT) {\n    const firstKey = cache.keys().next().value;\n    if (firstKey === undefined) return;\n    cache.delete(firstKey);\n  }\n}\n\nfunction nearestLineRangeCheckpoint<Cursor>(\n  checkpoints: readonly LineRangeCheckpoint<Cursor>[],\n  lineIndex: number,\n) {\n  let low = 0;\n  let high = checkpoints.length - 1;\n  let match = checkpoints[0]!;\n\n  while (low <= high) {\n    const middle = Math.floor((low + high) / 2);\n    const checkpoint = checkpoints[middle]!;\n    if (checkpoint.lineIndex <= lineIndex) {\n      match = checkpoint;\n      low = middle + 1;\n    } else {\n      high = middle - 1;\n    }\n  }\n\n  return match;\n}\n\nfunction maybeStoreLineRangeCheckpoint<Cursor>(\n  checkpoints: Array<LineRangeCheckpoint<Cursor>>,\n  lineIndex: number,\n  cursor: Cursor,\n  force = false,\n) {\n  if (\n    !force &&\n    (lineIndex === 0 || lineIndex % LINE_RANGE_CHECKPOINT_INTERVAL !== 0)\n  ) {\n    return;\n  }\n\n  let low = 0;\n  let high = checkpoints.length - 1;\n  while (low <= high) {\n    const middle = Math.floor((low + high) / 2);\n    const checkpoint = checkpoints[middle]!;\n    if (checkpoint.lineIndex === lineIndex) {\n      checkpoints[middle] = { cursor, lineIndex };\n      return;\n    }\n    if (checkpoint.lineIndex < lineIndex) {\n      low = middle + 1;\n    } else {\n      high = middle - 1;\n    }\n  }\n  checkpoints.splice(low, 0, { cursor, lineIndex });\n  while (checkpoints.length > LINE_RANGE_CHECKPOINT_CACHE_LIMIT) {\n    checkpoints.splice(checkpoints.length > 1 ? 1 : 0, 1);\n  }\n}\n\nfunction cloneLayoutCursor(cursor: LayoutCursor): LayoutCursor {\n  return {\n    graphemeIndex: cursor.graphemeIndex,\n    segmentIndex: cursor.segmentIndex,\n  };\n}\n\nfunction cloneRichInlineCursor(cursor: RichInlineCursor): RichInlineCursor {\n  return {\n    graphemeIndex: cursor.graphemeIndex,\n    itemIndex: cursor.itemIndex,\n    segmentIndex: cursor.segmentIndex,\n  };\n}\n\nexport function layoutTextDocument({\n  contentWidth,\n  document,\n  fontScale = 1,\n}: {\n  contentWidth: number;\n  document: PreparedTextDocument;\n  fontScale?: number;\n}): TextDocumentFrame {\n  const safeContentWidth = safeWidth(contentWidth);\n  const safeFontScale = safeScale(fontScale);\n  const cacheKey = textDocumentFrameCacheKey({\n    contentWidth: safeContentWidth,\n    fontScale: safeFontScale,\n  });\n  const cachedFrame = getTextDocumentFrameCache(document).get(cacheKey);\n  if (cachedFrame) return cachedFrame;\n\n  const frames: TextBlockFrame[] = [];\n  let y = DOCUMENT_PADDING_Y;\n\n  for (let index = 0; index < document.blocks.length; index++) {\n    const block = document.blocks[index]!;\n    y += block.marginTop;\n    const frame = layoutTextBlock({\n      block,\n      blockIndex: index,\n      contentWidth: safeContentWidth,\n      scale: safeFontScale,\n      top: y,\n    });\n    frames.push(frame);\n    y = frame.bottom;\n  }\n\n  const frame = {\n    frames,\n    totalHeight: y + DOCUMENT_PADDING_Y,\n    width: safeContentWidth,\n  };\n  setBoundedCacheEntry(\n    getTextDocumentFrameCache(document),\n    cacheKey,\n    frame,\n    TEXT_DOCUMENT_FRAME_CACHE_LIMIT,\n  );\n  return frame;\n}\n\nexport function materializeInlineVisibleLines({\n  block,\n  frame,\n  lineWindow,\n  maxWidth,\n  viewportBottom,\n  viewportTop,\n}: {\n  block: PreparedInlineTextBlock;\n  frame: InlineTextBlockFrame;\n  lineWindow?: TextLineWindow | null;\n  maxWidth: number;\n  viewportBottom: number;\n  viewportTop: number;\n}): InlineLineLayout[] {\n  const window =\n    lineWindow ??\n    getInlineVisibleLineWindow({ frame, viewportBottom, viewportTop });\n  if (!window) return [];\n\n  if (!block.flow) {\n    return [\n      {\n        fragments: fallbackInlineFragments(block),\n        lineIndex: 0,\n        top: 0,\n        width: frame.usedWidth,\n      },\n    ];\n  }\n\n  const lineWidth = safeWidth((maxWidth - frame.contentLeft) / frame.scale);\n  return getRichInlineMaterializedLineWindow({\n    block,\n    lineWidth,\n    window,\n  }).map(({ line, lineIndex }) => ({\n    fragments: richInlineFragments(block, line),\n    lineIndex,\n    top: lineIndex * frame.lineHeight,\n    width: line.width * frame.scale,\n  }));\n}\n\nexport function materializeCodeVisibleLines({\n  block,\n  contentWidth,\n  frame,\n  lineWindow,\n  viewportBottom,\n  viewportTop,\n}: {\n  block: PreparedCodeTextBlock;\n  contentWidth: number;\n  frame: CodeTextBlockFrame;\n  lineWindow?: TextLineWindow | null;\n  viewportBottom: number;\n  viewportTop: number;\n}): CodeLineLayout[] {\n  const window =\n    lineWindow ??\n    getCodeVisibleLineWindow({ frame, viewportBottom, viewportTop });\n  if (!window) return [];\n\n  if (!block.prepared) {\n    return [\n      {\n        lineIndex: 0,\n        line: {\n          end: { graphemeIndex: 0, segmentIndex: 0 },\n          start: { graphemeIndex: 0, segmentIndex: 0 },\n          text: block.fallbackText,\n          width: frame.width,\n        },\n        top: CODE_BLOCK_PADDING_Y,\n      },\n    ];\n  }\n\n  const boxWidth = safeWidth(contentWidth - frame.contentLeft);\n  const innerWidth = safeWidth(\n    (boxWidth - CODE_BLOCK_PADDING_X * 2) / frame.scale,\n  );\n  return getCodeMaterializedLineWindow({\n    block,\n    innerWidth,\n    window,\n  }).map(({ line, lineIndex }) => ({\n    lineIndex,\n    line,\n    top: CODE_BLOCK_PADDING_Y + lineIndex * frame.lineHeight,\n  }));\n}\n\nfunction getRichInlineMaterializedLineWindow({\n  block,\n  lineWidth,\n  window,\n}: {\n  block: PreparedInlineTextBlock;\n  lineWidth: number;\n  window: TextLineWindow;\n}) {\n  if (!block.flow) return [];\n\n  const cache = getRichInlineLineRangeCache(block, lineWidth);\n  const materializedLines = new Map<number, RichInlineLine>();\n  let firstMissing = Number.POSITIVE_INFINITY;\n  let lastMissing = -1;\n\n  for (\n    let lineIndex = window.firstLine;\n    lineIndex <= window.lastLine;\n    lineIndex++\n  ) {\n    const cachedLine = cache.lines.get(lineIndex);\n    if (cachedLine) {\n      touchCacheEntry(cache.lines, lineIndex, cachedLine);\n      materializedLines.set(lineIndex, cachedLine);\n      continue;\n    }\n\n    firstMissing = Math.min(firstMissing, lineIndex);\n    lastMissing = lineIndex;\n  }\n\n  if (lastMissing >= firstMissing) {\n    fillRichInlineMaterializedLineCache({\n      cache,\n      flow: block.flow,\n      lineWidth,\n      materializedLines,\n      window: {\n        firstLine: firstMissing,\n        lastLine: lastMissing,\n      },\n    });\n  }\n\n  const lines: Array<{ line: RichInlineLine; lineIndex: number }> = [];\n  for (\n    let lineIndex = window.firstLine;\n    lineIndex <= window.lastLine;\n    lineIndex++\n  ) {\n    const line = materializedLines.get(lineIndex);\n    if (line) lines.push({ line, lineIndex });\n  }\n\n  trimMaterializedLineCache(cache.lines);\n  return lines;\n}\n\nfunction fillRichInlineMaterializedLineCache({\n  cache,\n  flow,\n  lineWidth,\n  materializedLines,\n  window,\n}: {\n  cache: RichInlineLineRangeCache;\n  flow: PreparedRichInline;\n  lineWidth: number;\n  materializedLines: Map<number, RichInlineLine>;\n  window: TextLineWindow;\n}) {\n  const checkpoint = nearestLineRangeCheckpoint(\n    cache.checkpoints,\n    window.firstLine,\n  );\n  let cursor = cloneRichInlineCursor(checkpoint.cursor);\n\n  for (\n    let lineIndex = checkpoint.lineIndex;\n    lineIndex <= window.lastLine;\n    lineIndex++\n  ) {\n    const cachedLine = cache.lines.get(lineIndex);\n    if (cachedLine) {\n      cursor = cloneRichInlineCursor(cachedLine.end);\n      if (lineIndex >= window.firstLine) {\n        touchCacheEntry(cache.lines, lineIndex, cachedLine);\n        materializedLines.set(lineIndex, cachedLine);\n      }\n      maybeStoreLineRangeCheckpoint(\n        cache.checkpoints,\n        lineIndex + 1,\n        cloneRichInlineCursor(cachedLine.end),\n        true,\n      );\n      continue;\n    }\n\n    const range = layoutNextRichInlineLineRange(flow, lineWidth, cursor);\n    if (!range) return;\n\n    const nextCursor = cloneRichInlineCursor(range.end);\n    const nextLineIndex = lineIndex + 1;\n    if (lineIndex >= window.firstLine) {\n      const line = materializeRichInlineLineRange(flow, range);\n      cache.lines.set(lineIndex, line);\n      materializedLines.set(lineIndex, line);\n      maybeStoreLineRangeCheckpoint(\n        cache.checkpoints,\n        lineIndex,\n        cloneRichInlineCursor(cursor),\n        true,\n      );\n    }\n    maybeStoreLineRangeCheckpoint(\n      cache.checkpoints,\n      nextLineIndex,\n      nextCursor,\n      lineIndex >= window.firstLine,\n    );\n    cursor = nextCursor;\n  }\n}\n\nfunction getCodeMaterializedLineWindow({\n  block,\n  innerWidth,\n  window,\n}: {\n  block: PreparedCodeTextBlock;\n  innerWidth: number;\n  window: TextLineWindow;\n}) {\n  if (!block.prepared) return [];\n\n  const cache = getCodeLineRangeCache(block, innerWidth);\n  const materializedLines = new Map<number, LayoutLine>();\n  let firstMissing = Number.POSITIVE_INFINITY;\n  let lastMissing = -1;\n\n  for (\n    let lineIndex = window.firstLine;\n    lineIndex <= window.lastLine;\n    lineIndex++\n  ) {\n    const cachedLine = cache.lines.get(lineIndex);\n    if (cachedLine) {\n      touchCacheEntry(cache.lines, lineIndex, cachedLine);\n      materializedLines.set(lineIndex, cachedLine);\n      continue;\n    }\n\n    firstMissing = Math.min(firstMissing, lineIndex);\n    lastMissing = lineIndex;\n  }\n\n  if (lastMissing >= firstMissing) {\n    fillCodeMaterializedLineCache({\n      cache,\n      innerWidth,\n      materializedLines,\n      prepared: block.prepared,\n      window: {\n        firstLine: firstMissing,\n        lastLine: lastMissing,\n      },\n    });\n  }\n\n  const lines: Array<{ line: LayoutLine; lineIndex: number }> = [];\n  for (\n    let lineIndex = window.firstLine;\n    lineIndex <= window.lastLine;\n    lineIndex++\n  ) {\n    const line = materializedLines.get(lineIndex);\n    if (line) lines.push({ line, lineIndex });\n  }\n\n  trimMaterializedLineCache(cache.lines);\n  return lines;\n}\n\nfunction fillCodeMaterializedLineCache({\n  cache,\n  innerWidth,\n  materializedLines,\n  prepared,\n  window,\n}: {\n  cache: CodeLineRangeCache;\n  innerWidth: number;\n  materializedLines: Map<number, LayoutLine>;\n  prepared: PreparedTextWithSegments;\n  window: TextLineWindow;\n}) {\n  const checkpoint = nearestLineRangeCheckpoint(\n    cache.checkpoints,\n    window.firstLine,\n  );\n  let cursor = cloneLayoutCursor(checkpoint.cursor);\n\n  for (\n    let lineIndex = checkpoint.lineIndex;\n    lineIndex <= window.lastLine;\n    lineIndex++\n  ) {\n    const cachedLine = cache.lines.get(lineIndex);\n    if (cachedLine) {\n      cursor = cloneLayoutCursor(cachedLine.end);\n      if (lineIndex >= window.firstLine) {\n        touchCacheEntry(cache.lines, lineIndex, cachedLine);\n        materializedLines.set(lineIndex, cachedLine);\n      }\n      maybeStoreLineRangeCheckpoint(\n        cache.checkpoints,\n        lineIndex + 1,\n        cloneLayoutCursor(cachedLine.end),\n        true,\n      );\n      continue;\n    }\n\n    const range = layoutNextLineRange(prepared, cursor, innerWidth);\n    if (!range) return;\n\n    const nextCursor = cloneLayoutCursor(range.end);\n    const nextLineIndex = lineIndex + 1;\n    if (lineIndex >= window.firstLine) {\n      const line = materializeLineRange(prepared, range);\n      cache.lines.set(lineIndex, line);\n      materializedLines.set(lineIndex, line);\n      maybeStoreLineRangeCheckpoint(\n        cache.checkpoints,\n        lineIndex,\n        cloneLayoutCursor(cursor),\n        true,\n      );\n    }\n    maybeStoreLineRangeCheckpoint(\n      cache.checkpoints,\n      nextLineIndex,\n      nextCursor,\n      lineIndex >= window.firstLine,\n    );\n    cursor = nextCursor;\n  }\n}\n\nexport function getInlineVisibleLineWindow({\n  frame,\n  viewportBottom,\n  viewportTop,\n}: {\n  frame: InlineTextBlockFrame;\n  viewportBottom: number;\n  viewportTop: number;\n}): TextLineWindow | null {\n  return getVisibleLineWindow({\n    lineCount: frame.lineCount,\n    lineHeight: frame.lineHeight,\n    originTop: frame.top,\n    viewportBottom,\n    viewportTop,\n  });\n}\n\nexport function getCodeVisibleLineWindow({\n  frame,\n  viewportBottom,\n  viewportTop,\n}: {\n  frame: CodeTextBlockFrame;\n  viewportBottom: number;\n  viewportTop: number;\n}): TextLineWindow | null {\n  return getVisibleLineWindow({\n    lineCount: frame.lineCount,\n    lineHeight: frame.lineHeight,\n    originTop: frame.top + CODE_BLOCK_PADDING_Y,\n    viewportBottom,\n    viewportTop,\n  });\n}\n\nfunction getVisibleLineWindow({\n  lineCount,\n  lineHeight,\n  originTop,\n  viewportBottom,\n  viewportTop,\n}: {\n  lineCount: number;\n  lineHeight: number;\n  originTop: number;\n  viewportBottom: number;\n  viewportTop: number;\n}): TextLineWindow | null {\n  const firstLine = Math.max(\n    0,\n    Math.floor((viewportTop - originTop) / lineHeight) - 1,\n  );\n  const lastLine = Math.min(\n    lineCount - 1,\n    Math.ceil((viewportBottom - originTop) / lineHeight) + 1,\n  );\n  return lastLine < firstLine ? null : { firstLine, lastLine };\n}\n\nexport function getTableVisibleRowWindow({\n  frame,\n  viewportBottom,\n  viewportTop,\n}: {\n  frame: TableTextBlockFrame;\n  viewportBottom: number;\n  viewportTop: number;\n}): TableRowWindow {\n  const relativeTop = viewportTop - frame.top - frame.headerHeight;\n  const relativeBottom = viewportBottom - frame.top - frame.headerHeight;\n  const bodyHeight = frame.rowOffsets[frame.rowOffsets.length - 1] ?? 0;\n  const startIndex = Math.max(\n    0,\n    findTableRowAtOffset(frame.rowOffsets, relativeTop) - TABLE_ROW_OVERSCAN,\n  );\n  const endIndex = Math.min(\n    frame.rowCount,\n    findTableRowEndAtOffset(frame.rowOffsets, relativeBottom) +\n      TABLE_ROW_OVERSCAN,\n  );\n  return {\n    afterHeight: Math.max(0, bodyHeight - (frame.rowOffsets[endIndex] ?? 0)),\n    beforeHeight: frame.rowOffsets[startIndex] ?? 0,\n    endIndex,\n    startIndex,\n  };\n}\n\nexport function textFrameIntersectsLineRange({\n  frame,\n  range,\n}: {\n  frame: TextBlockFrame;\n  range: { end: number; start: number } | null;\n}) {\n  return (\n    range != null &&\n    frame.sourceStartLine <= range.end &&\n    frame.sourceEndLine >= range.start\n  );\n}\n\nexport function serializeMarkdownTableForClipboard(\n  block: PreparedTableTextBlock,\n) {\n  const rows = [block.header, ...block.rows];\n  return rows\n    .map((row) => {\n      return block.header\n        .map((_headerCell, index) => tableClipboardCell(row[index]?.text ?? \"\"))\n        .join(\"\\t\");\n    })\n    .join(\"\\n\");\n}\n\nfunction parseMarkdownBlocks(\n  markdown: string,\n  style: TextStyleConfig,\n): PreparedTextBlock[] {\n  const sourceEndLine = splitTextLines(markdown).length;\n  try {\n    const headingIds: HeadingIdRegistry = new Map();\n    const frontmatter = extractMarkdownFrontmatter(markdown);\n    if (frontmatter) {\n      const blocks: PreparedTextBlock[] = [];\n      appendBlockGroup(\n        blocks,\n        [\n          buildCodeBlock({\n            ctx: { listDepth: 0, quoteDepth: 0 },\n            language: \"yaml\",\n            sourceEndLine: frontmatter.endLine,\n            sourceStartLine: 1,\n            style,\n            text: frontmatter.text,\n          }),\n        ],\n        0,\n      );\n      appendBlockGroup(\n        blocks,\n        parseBlockTokens(marked.lexer(frontmatter.body, { gfm: true }), {\n          ctx: { listDepth: 0, quoteDepth: 0 },\n          headingIds,\n          sourceEndLine,\n          sourceStartLine: frontmatter.endLine + 1,\n          style,\n        }),\n        BLOCK_GAP,\n      );\n      return blocks;\n    }\n\n    return parseBlockTokens(marked.lexer(markdown, { gfm: true }), {\n      ctx: { listDepth: 0, quoteDepth: 0 },\n      headingIds,\n      sourceEndLine,\n      sourceStartLine: 1,\n      style,\n    });\n  } catch {\n    return buildPlainTextBlocks(splitTextLines(markdown), style);\n  }\n}\n\nfunction parseBlockTokens(\n  tokens: readonly Token[],\n  {\n    ctx,\n    headingIds,\n    sourceEndLine,\n    sourceStartLine,\n    style,\n  }: {\n    ctx: ParseContext;\n    headingIds: HeadingIdRegistry;\n    sourceEndLine: number;\n    sourceStartLine: number;\n    style: TextStyleConfig;\n  },\n): PreparedTextBlock[] {\n  const blocks: PreparedTextBlock[] = [];\n  let cursorLine = sourceStartLine;\n\n  for (const token of tokens) {\n    // Advance by the number of line breaks the token's raw spans. `marked`\n    // concatenates token raws to reconstruct the source, so this keeps the\n    // cursor aligned even when a block's trailing newline is absorbed into the\n    // following `space` token (a \"\\n\\n\" gap is two breaks, i.e. one blank line).\n    const breaks = countSourceLineBreaks(token.raw);\n    const tokenStartLine = Math.min(cursorLine, sourceEndLine);\n    const tokenEndLine = Math.min(\n      sourceEndLine,\n      Math.max(\n        tokenStartLine,\n        cursorLine + breaks - (endsWithSourceLineBreak(token.raw) ? 1 : 0),\n      ),\n    );\n    cursorLine += breaks;\n\n    switch (token.type) {\n      case \"space\":\n      case \"def\":\n        continue;\n\n      case \"paragraph\":\n        if (isStandaloneImageParagraph(token)) {\n          appendBlockGroup(\n            blocks,\n            [\n              buildImageBlock({\n                ctx,\n                sourceEndLine: tokenEndLine,\n                sourceStartLine: tokenStartLine,\n                token: token.tokens[0] as Tokens.Image,\n              }),\n            ],\n            BLOCK_GAP,\n          );\n          continue;\n        }\n        appendBlockGroup(\n          blocks,\n          buildInlineBlocks({\n            ctx,\n            lines: collectInlinePieceLines(token.tokens ?? [], \"body\", style),\n            sourceEndLine: tokenEndLine,\n            sourceStartLine: tokenStartLine,\n            style,\n            variant: \"body\",\n          }),\n          BLOCK_GAP,\n        );\n        continue;\n\n      case \"heading\": {\n        const variant = headingVariant(token.depth);\n        const lines = collectInlinePieceLines(\n          token.tokens ?? [],\n          variant,\n          style,\n        );\n        appendBlockGroup(\n          blocks,\n          buildInlineBlocks({\n            ctx,\n            headingId: createMarkdownHeadingId(lines, headingIds),\n            lines,\n            sourceEndLine: tokenEndLine,\n            sourceStartLine: tokenStartLine,\n            style,\n            variant,\n          }),\n          BLOCK_GAP + 4,\n        );\n        continue;\n      }\n\n      case \"code\":\n        appendBlockGroup(\n          blocks,\n          [\n            buildCodeBlock({\n              ctx,\n              language: sanitizeMarkdownLanguage(token.lang),\n              sourceEndLine: tokenEndLine,\n              sourceStartLine: tokenStartLine,\n              style,\n              text: token.text,\n            }),\n          ],\n          RICH_BLOCK_GAP,\n        );\n        continue;\n\n      case \"list\":\n        appendBlockGroup(\n          blocks,\n          buildListBlocks({\n            ctx,\n            headingIds,\n            sourceEndLine: tokenEndLine,\n            sourceStartLine: tokenStartLine,\n            style,\n            token: token as Tokens.List,\n          }),\n          BLOCK_GAP,\n        );\n        continue;\n\n      case \"blockquote\":\n        appendBlockGroup(\n          blocks,\n          parseBlockTokens(token.tokens ?? [], {\n            ctx: {\n              listDepth: ctx.listDepth,\n              quoteDepth: ctx.quoteDepth + 1,\n            },\n            headingIds,\n            sourceEndLine: tokenEndLine,\n            sourceStartLine: tokenStartLine,\n            style,\n          }),\n          RICH_BLOCK_GAP,\n        );\n        continue;\n\n      case \"hr\":\n        appendBlockGroup(\n          blocks,\n          [\n            buildRuleBlock({\n              ctx,\n              sourceEndLine: tokenEndLine,\n              sourceStartLine: tokenStartLine,\n            }),\n          ],\n          BLOCK_GAP + 2,\n        );\n        continue;\n\n      case \"table\":\n        appendBlockGroup(\n          blocks,\n          [\n            buildTableBlock({\n              ctx,\n              sourceEndLine: tokenEndLine,\n              sourceStartLine: tokenStartLine,\n              token: token as Tokens.Table,\n            }),\n          ],\n          RICH_BLOCK_GAP,\n        );\n        continue;\n\n      case \"html\": {\n        const htmlText = token.text.trim().length > 0 ? token.text : token.raw;\n        if (token.block || (\"pre\" in token && token.pre === true)) {\n          appendBlockGroup(\n            blocks,\n            [\n              buildCodeBlock({\n                ctx,\n                language: null,\n                sourceEndLine: tokenEndLine,\n                sourceStartLine: tokenStartLine,\n                style,\n                text: htmlText,\n              }),\n            ],\n            RICH_BLOCK_GAP,\n          );\n        } else {\n          appendPlainTextFallback({\n            blocks,\n            ctx,\n            sourceEndLine: tokenEndLine,\n            sourceStartLine: tokenStartLine,\n            style,\n            text: htmlText,\n          });\n        }\n        continue;\n      }\n\n      case \"text\":\n        if (Array.isArray(token.tokens) && token.tokens.length > 0) {\n          appendBlockGroup(\n            blocks,\n            buildInlineBlocks({\n              ctx,\n              lines: collectInlinePieceLines(token.tokens, \"body\", style),\n              sourceEndLine: tokenEndLine,\n              sourceStartLine: tokenStartLine,\n              style,\n              variant: \"body\",\n            }),\n            BLOCK_GAP,\n          );\n        } else {\n          appendPlainTextFallback({\n            blocks,\n            ctx,\n            sourceEndLine: tokenEndLine,\n            sourceStartLine: tokenStartLine,\n            style,\n            text: token.text,\n          });\n        }\n        continue;\n\n      default: {\n        const fallbackText = fallbackTextForToken(token);\n        if (fallbackText) {\n          appendPlainTextFallback({\n            blocks,\n            ctx,\n            sourceEndLine: tokenEndLine,\n            sourceStartLine: tokenStartLine,\n            style,\n            text: fallbackText,\n          });\n        }\n      }\n    }\n  }\n\n  return blocks;\n}\n\nfunction buildPlainTextBlocks(\n  lines: readonly string[],\n  style: TextStyleConfig,\n): PreparedTextBlock[] {\n  const blocks: PreparedTextBlock[] = [];\n  let run: string[] = [];\n  let runStartLine = 1;\n\n  function appendLine(line: string, sourceLine: number) {\n    blocks.push(buildPlainTextLineBlock(line, sourceLine, style));\n  }\n\n  function flushRun(endLine: number) {\n    if (run.length === 0) return;\n    if (shouldJoinPlainTextRun(run)) {\n      blocks.push(\n        buildInlineBlock({\n          ctx: { listDepth: 0, quoteDepth: 0 },\n          pieces: [\n            createTextPiece(\n              joinPlainTextRun(run),\n              EMPTY_MARK_STATE,\n              \"body\",\n              style,\n            )!,\n          ],\n          sourceEndLine: endLine,\n          sourceStartLine: runStartLine,\n          style,\n          variant: \"body\",\n        }),\n      );\n    } else {\n      for (let index = 0; index < run.length; index++) {\n        appendLine(run[index]!, runStartLine + index);\n      }\n    }\n    run = [];\n  }\n\n  for (let index = 0; index < lines.length; index++) {\n    const line = lines[index]!;\n    const sourceLine = index + 1;\n    if (line.trim() === \"\") {\n      flushRun(sourceLine - 1);\n      appendLine(\" \", sourceLine);\n      runStartLine = sourceLine + 1;\n      continue;\n    }\n    if (run.length === 0) runStartLine = sourceLine;\n    run.push(line);\n  }\n\n  flushRun(lines.length);\n  return blocks;\n}\n\nfunction buildPlainTextLineBlock(\n  line: string,\n  sourceLine: number,\n  style: TextStyleConfig,\n): PreparedInlineTextBlock {\n  return buildInlineBlock({\n    ctx: { listDepth: 0, quoteDepth: 0 },\n    pieces: [createTextPiece(line || \" \", EMPTY_MARK_STATE, \"body\", style)!],\n    sourceEndLine: sourceLine,\n    sourceStartLine: sourceLine,\n    style,\n    variant: \"body\",\n  });\n}\n\nfunction shouldJoinPlainTextRun(lines: readonly string[]) {\n  if (lines.length < 2) return false;\n\n  const trimmed = lines.map((line) => line.trim()).filter(Boolean);\n  if (\n    trimmed.length < 2 ||\n    countRecordLikeLines(trimmed) > trimmed.length / 3\n  ) {\n    return false;\n  }\n\n  const lengths = trimmed.map((line) => line.length);\n  const average =\n    lengths.reduce((total, length) => total + length, 0) / lengths.length;\n  if (average < HARD_WRAPPED_RUN_AVERAGE_LENGTH) return false;\n\n  const internal = lengths.slice(0, -1);\n  const shortInternalCount = internal.filter(\n    (length) => length < HARD_WRAPPED_LINE_MIN_LENGTH,\n  ).length;\n  return shortInternalCount <= Math.floor(internal.length * 0.15);\n}\n\nfunction countRecordLikeLines(lines: readonly string[]) {\n  return lines.reduce((count, line) => {\n    return count + (isRecordLikePlainTextLine(line) ? 1 : 0);\n  }, 0);\n}\n\nfunction isRecordLikePlainTextLine(line: string) {\n  return (\n    /^\\d{4}-\\d{2}-\\d{2}(?:[T\\s]|\\b)/.test(line) ||\n    /^\\d{1,6}[:.)\\]]\\s/.test(line) ||\n    /^[-*+]\\s/.test(line) ||\n    /^(?:TRACE|DEBUG|INFO|WARN|ERROR|FATAL)\\b/.test(line) ||\n    /\\b(?:TRACE|DEBUG|INFO|WARN|ERROR|FATAL)\\b/.test(line.slice(0, 80)) ||\n    /^[{[]/.test(line)\n  );\n}\n\nfunction joinPlainTextRun(lines: readonly string[]) {\n  return lines.map((line) => line.trim()).join(\" \");\n}\n\nfunction appendPlainTextFallback({\n  blocks,\n  ctx,\n  sourceEndLine,\n  sourceStartLine,\n  style,\n  text,\n}: {\n  blocks: PreparedTextBlock[];\n  ctx: ParseContext;\n  sourceEndLine: number;\n  sourceStartLine: number;\n  style: TextStyleConfig;\n  text: string;\n}) {\n  appendBlockGroup(\n    blocks,\n    buildInlineBlocks({\n      ctx,\n      lines: [\n        [createTextPiece(text, EMPTY_MARK_STATE, \"body\", style)].filter(\n          Boolean,\n        ) as InlinePiece[],\n      ],\n      sourceEndLine,\n      sourceStartLine,\n      style,\n      variant: \"body\",\n    }),\n    BLOCK_GAP,\n  );\n}\n\nfunction buildListBlocks({\n  ctx,\n  headingIds,\n  sourceEndLine,\n  sourceStartLine,\n  style,\n  token,\n}: {\n  ctx: ParseContext;\n  headingIds: HeadingIdRegistry;\n  sourceEndLine: number;\n  sourceStartLine: number;\n  style: TextStyleConfig;\n  token: Tokens.List;\n}): PreparedTextBlock[] {\n  const blocks: PreparedTextBlock[] = [];\n  const itemCtx = {\n    listDepth: ctx.listDepth + 1,\n    quoteDepth: ctx.quoteDepth,\n  };\n\n  let itemCursorLine = sourceStartLine;\n  for (let index = 0; index < token.items.length; index++) {\n    const item = token.items[index]!;\n    const breaks = countSourceLineBreaks(item.raw);\n    const itemStartLine = Math.min(sourceEndLine, itemCursorLine);\n    const itemEndLine = Math.min(\n      sourceEndLine,\n      Math.max(\n        itemStartLine,\n        itemCursorLine + breaks - (endsWithSourceLineBreak(item.raw) ? 1 : 0),\n      ),\n    );\n    itemCursorLine += breaks;\n\n    let itemBlocks = parseBlockTokens(item.tokens, {\n      ctx: itemCtx,\n      headingIds,\n      sourceEndLine: itemEndLine,\n      sourceStartLine: itemStartLine,\n      style,\n    });\n    if (itemBlocks.length === 0) {\n      itemBlocks = buildInlineBlocks({\n        ctx: itemCtx,\n        lines: [\n          [createTextPiece(item.text, EMPTY_MARK_STATE, \"body\", style)].filter(\n            Boolean,\n          ) as InlinePiece[],\n        ],\n        sourceEndLine: itemEndLine,\n        sourceStartLine: itemStartLine,\n        style,\n        variant: \"body\",\n      });\n    }\n\n    decorateListItemBlocks(\n      itemBlocks,\n      resolveListMarkerText(token, item, index),\n      resolveListMarkerClassName(token, item),\n      style,\n    );\n    appendBlockGroup(blocks, itemBlocks, LIST_ITEM_GAP);\n  }\n\n  return blocks;\n}\n\nfunction decorateListItemBlocks(\n  blocks: PreparedTextBlock[],\n  markerText: string,\n  markerClassName: string,\n  style: TextStyleConfig,\n) {\n  if (blocks.length === 0) return;\n\n  const markerArea = measureMarkerWidth(markerText, style) + LIST_MARKER_GAP;\n  for (let index = 0; index < blocks.length; index++) {\n    blocks[index] = shiftBlock(blocks[index]!, markerArea);\n  }\n\n  const firstBlock = blocks[0]!;\n  blocks[0] = {\n    ...firstBlock,\n    markerClassName,\n    markerLeft: firstBlock.contentLeft - markerArea,\n    markerText,\n  } satisfies PreparedTextBlock;\n}\n\nfunction buildInlineBlocks({\n  ctx,\n  headingId = null,\n  lines,\n  sourceEndLine,\n  sourceStartLine,\n  style,\n  variant,\n}: {\n  ctx: ParseContext;\n  headingId?: string | null;\n  lines: InlinePiece[][];\n  sourceEndLine: number;\n  sourceStartLine: number;\n  style: TextStyleConfig;\n  variant: InlineVariant;\n}) {\n  const blocks: PreparedTextBlock[] = [];\n  for (const pieces of lines) {\n    if (pieces.length === 0) continue;\n    const isFirstBlock = blocks.length === 0;\n    blocks.push({\n      ...buildInlineBlock({\n        ctx,\n        pieces,\n        sourceEndLine,\n        sourceStartLine,\n        style,\n        variant,\n      }),\n      headingId: isFirstBlock ? headingId : null,\n      marginTop: isFirstBlock ? 0 : HARD_BREAK_GAP,\n    });\n  }\n  return blocks;\n}\n\nfunction buildInlineBlock({\n  ctx,\n  pieces,\n  sourceEndLine,\n  sourceStartLine,\n  style,\n  variant,\n}: {\n  ctx: ParseContext;\n  pieces: InlinePiece[];\n  sourceEndLine: number;\n  sourceStartLine: number;\n  style: TextStyleConfig;\n  variant: InlineVariant;\n}): PreparedInlineTextBlock {\n  return {\n    ...createBlockBase(ctx, sourceStartLine, sourceEndLine),\n    classNames: pieces.map((piece) => piece.className),\n    fallbackText: pieces.map((piece) => piece.text).join(\"\"),\n    flow: prepareRichInlineSafe(pieces),\n    fonts: pieces.map((piece) => piece.font),\n    headingId: null,\n    hrefs: pieces.map((piece) => piece.href),\n    kind: \"inline\",\n    lineHeight: lineHeightForVariant(variant, style),\n    texts: pieces.map((piece) => piece.text),\n    titles: pieces.map((piece) => piece.title),\n    variant,\n  };\n}\n\nfunction buildCodeBlock({\n  ctx,\n  language,\n  sourceEndLine,\n  sourceStartLine,\n  style,\n  text,\n}: {\n  ctx: ParseContext;\n  language: string | null;\n  sourceEndLine: number;\n  sourceStartLine: number;\n  style: TextStyleConfig;\n  text: string;\n}): PreparedCodeTextBlock {\n  const code = stripSingleTrailingNewline(text);\n  const font = codeFont(style);\n  return {\n    ...createBlockBase(ctx, sourceStartLine, sourceEndLine),\n    fallbackText: code,\n    font,\n    kind: \"code\",\n    language,\n    lineHeight: CODE_LINE_PX * style.fontScale,\n    prepared: prepareWithSegmentsSafe(code || \" \", font, {\n      whiteSpace: \"pre-wrap\",\n    }),\n  };\n}\n\nfunction buildImageBlock({\n  ctx,\n  sourceEndLine,\n  sourceStartLine,\n  token,\n}: {\n  ctx: ParseContext;\n  sourceEndLine: number;\n  sourceStartLine: number;\n  token: Tokens.Image;\n}): PreparedImageTextBlock {\n  const alt = token.text?.trim() || token.href || \"Markdown image\";\n  return {\n    ...createBlockBase(ctx, sourceStartLine, sourceEndLine),\n    alt,\n    href: parseMarkdownHref(token.href),\n    kind: \"image\",\n    src: parseMarkdownImageSrc(token.href),\n    title: sanitizeMarkdownTitle(token.title),\n  };\n}\n\nfunction buildRuleBlock({\n  ctx,\n  sourceEndLine,\n  sourceStartLine,\n}: {\n  ctx: ParseContext;\n  sourceEndLine: number;\n  sourceStartLine: number;\n}): PreparedRuleTextBlock {\n  return {\n    ...createBlockBase(ctx, sourceStartLine, sourceEndLine),\n    height: RULE_HEIGHT,\n    kind: \"rule\",\n  };\n}\n\nfunction buildTableBlock({\n  ctx,\n  sourceEndLine,\n  sourceStartLine,\n  token,\n}: {\n  ctx: ParseContext;\n  sourceEndLine: number;\n  sourceStartLine: number;\n  token: Tokens.Table;\n}): PreparedTableTextBlock {\n  const header = token.header.map((cell) => tableCellFromTokens(cell.tokens));\n  const rows = token.rows.map((row) =>\n    row.map((cell) => tableCellFromTokens(cell.tokens)),\n  );\n  const rowSourceStartLines = token.rows.map(\n    (_row, index) => sourceStartLine + 2 + index,\n  );\n  const alignments = token.align.map((alignment) =>\n    tableColumnAlignment(alignment),\n  );\n  return {\n    ...createBlockBase(ctx, sourceStartLine, sourceEndLine),\n    alignments,\n    columnWidths: measureTableColumnWidths(header, rows),\n    header,\n    kind: \"table\",\n    rowSourceStartLines,\n    rows,\n  };\n}\n\nfunction createBlockBase(\n  ctx: ParseContext,\n  sourceStartLine: number,\n  sourceEndLine: number,\n): PreparedTextBlockBase {\n  const listIndent = Math.max(0, ctx.listDepth - 1) * LIST_NESTING_INDENT;\n  const contentLeft = listIndent + ctx.quoteDepth * BLOCKQUOTE_INDENT;\n  const quoteRailLefts = Array.from({ length: ctx.quoteDepth }, (_, depth) => {\n    return listIndent + depth * BLOCKQUOTE_INDENT + RAIL_OFFSET;\n  });\n\n  return {\n    contentLeft,\n    listDepth: ctx.listDepth,\n    marginTop: 0,\n    markerClassName: null,\n    markerLeft: null,\n    markerText: null,\n    quoteDepth: ctx.quoteDepth,\n    quoteRailLefts,\n    sourceEndLine,\n    sourceStartLine,\n  };\n}\n\nfunction collectInlinePieceLines(\n  tokens: readonly Token[],\n  variant: InlineVariant,\n  style: TextStyleConfig,\n): InlinePiece[][] {\n  const lines: InlinePiece[][] = [[]];\n\n  function currentLine() {\n    return lines[lines.length - 1]!;\n  }\n\n  function pushLineBreak() {\n    lines.push([]);\n  }\n\n  function pushPiece(piece: InlinePiece | null) {\n    if (!piece) return;\n    const line = currentLine();\n    const previous = line[line.length - 1];\n    if (previous && canMergeInlinePieces(previous, piece)) {\n      previous.text += piece.text;\n      return;\n    }\n    line.push(piece);\n  }\n\n  function walk(tokenList: readonly Token[], marks: MarkState) {\n    for (const token of tokenList) {\n      switch (token.type) {\n        case \"text\":\n          if (Array.isArray(token.tokens) && token.tokens.length > 0) {\n            walk(token.tokens, marks);\n          } else {\n            pushPiece(createTextPiece(token.text, marks, variant, style));\n          }\n          continue;\n        case \"escape\":\n          pushPiece(createTextPiece(token.text, marks, variant, style));\n          continue;\n        case \"strong\":\n          walk(token.tokens ?? [], { ...marks, bold: true });\n          continue;\n        case \"em\":\n          walk(token.tokens ?? [], { ...marks, italic: true });\n          continue;\n        case \"del\":\n          walk(token.tokens ?? [], { ...marks, strike: true });\n          continue;\n        case \"codespan\":\n          pushPiece(createCodePiece(token.text, style));\n          continue;\n        case \"link\":\n          walk(token.tokens ?? [], {\n            ...marks,\n            href: parseMarkdownHref(token.href),\n            title: sanitizeMarkdownTitle(token.title),\n          });\n          continue;\n        case \"image\":\n          pushPiece(createImagePiece(token.text || token.href, style));\n          continue;\n        case \"br\":\n          pushLineBreak();\n          continue;\n        case \"checkbox\":\n          pushPiece(\n            createTextPiece(\n              token.checked ? \"[x] \" : \"[ ] \",\n              marks,\n              variant,\n              style,\n            ),\n          );\n          continue;\n        case \"html\":\n          pushPiece(createTextPiece(token.text, marks, variant, style));\n          continue;\n        default: {\n          const fallback = fallbackTextForToken(token);\n          if (fallback) {\n            pushPiece(createTextPiece(fallback, marks, variant, style));\n          }\n        }\n      }\n    }\n  }\n\n  walk(tokens, EMPTY_MARK_STATE);\n  while (lines.length > 0 && lines[lines.length - 1]!.length === 0) {\n    lines.pop();\n  }\n  return lines;\n}\n\nfunction createTextPiece(\n  text: string,\n  marks: MarkState,\n  variant: InlineVariant,\n  style: TextStyleConfig,\n): InlinePiece | null {\n  if (!text) return null;\n  return {\n    breakMode: \"normal\",\n    className: inlineClassName(variant, marks),\n    extraWidth: 0,\n    font: inlineFont(variant, marks, style),\n    href: marks.href,\n    text,\n    title: marks.title,\n  };\n}\n\nfunction createCodePiece(\n  text: string,\n  style: TextStyleConfig,\n): InlinePiece | null {\n  if (!text) return null;\n  return {\n    breakMode: \"normal\",\n    className: \"rounded bg-muted px-1.5 py-0.5 font-mono text-[0.92em]\",\n    extraWidth: INLINE_CODE_EXTRA_WIDTH,\n    font: codeInlineFont(style),\n    href: null,\n    text,\n    title: null,\n  };\n}\n\nfunction codeFont(style: TextStyleConfig) {\n  return `500 ${CODE_FONT_PX * style.fontScale}px ${MONO_FAMILY}`;\n}\n\nfunction codeInlineFont(style: TextStyleConfig) {\n  return `600 ${CODE_FONT_PX * style.fontScale}px ${MONO_FAMILY}`;\n}\n\nfunction createImagePiece(text: string, style: TextStyleConfig): InlinePiece {\n  return {\n    breakMode: \"never\",\n    className:\n      \"inline-flex min-h-5 items-center rounded-full bg-muted px-2 text-xs font-medium text-muted-foreground\",\n    extraWidth: IMAGE_EXTRA_WIDTH,\n    font: `600 ${CHIP_FONT_PX * style.fontScale}px ${SANS_FAMILY}`,\n    href: null,\n    text: text || \"image\",\n    title: null,\n  };\n}\n\nfunction layoutTextBlock({\n  block,\n  blockIndex,\n  contentWidth,\n  scale,\n  top,\n}: {\n  block: PreparedTextBlock;\n  blockIndex: number;\n  contentWidth: number;\n  scale: number;\n  top: number;\n}): TextBlockFrame {\n  switch (block.kind) {\n    case \"inline\": {\n      const lineWidth = safeWidth((contentWidth - block.contentLeft) / scale);\n      const stats = block.flow\n        ? measureRichInlineStatsSafe(block.flow, lineWidth, block.fallbackText)\n        : estimateInlineStats(block.fallbackText, lineWidth);\n      const lineCount = Math.max(1, stats.lineCount);\n      const lineHeight = block.lineHeight * scale;\n      const height = lineCount * lineHeight;\n      return {\n        ...frameBase(block, blockIndex, top, height, scale),\n        kind: \"inline\",\n        lineCount,\n        lineHeight,\n        usedWidth: stats.maxLineWidth * scale,\n      };\n    }\n\n    case \"code\": {\n      const boxWidth = safeWidth(contentWidth - block.contentLeft);\n      const innerWidth = safeWidth(\n        (boxWidth - CODE_BLOCK_PADDING_X * 2) / scale,\n      );\n      const stats = block.prepared\n        ? measureLineStatsSafe(block.prepared, innerWidth, block.fallbackText)\n        : estimateInlineStats(block.fallbackText, innerWidth);\n      const lineCount = Math.max(1, stats.lineCount);\n      const width = Math.min(\n        boxWidth,\n        Math.max(1, stats.maxLineWidth * scale + CODE_BLOCK_PADDING_X * 2),\n      );\n      const lineHeight = block.lineHeight * scale;\n      const height = lineCount * lineHeight + CODE_BLOCK_PADDING_Y * 2;\n      return {\n        ...frameBase(block, blockIndex, top, height, scale),\n        kind: \"code\",\n        language: block.language,\n        lineCount,\n        lineHeight,\n        width,\n      };\n    }\n\n    case \"image\": {\n      const availableWidth = safeWidth(contentWidth - block.contentLeft);\n      const imageWidth = Math.min(\n        IMAGE_BLOCK_MAX_WIDTH,\n        Math.max(IMAGE_BLOCK_MIN_WIDTH, availableWidth),\n      );\n      const imageHeight = block.src\n        ? IMAGE_BLOCK_HEIGHT\n        : IMAGE_PLACEHOLDER_HEIGHT;\n      return {\n        ...frameBase(block, blockIndex, top, imageHeight, scale),\n        alt: block.alt,\n        imageHeight,\n        imageWidth,\n        kind: \"image\",\n      };\n    }\n\n    case \"rule\": {\n      return {\n        ...frameBase(block, blockIndex, top, block.height, scale),\n        kind: \"rule\",\n        width: safeWidth(contentWidth - block.contentLeft),\n      };\n    }\n\n    case \"table\": {\n      const availableWidth = safeWidth(contentWidth - block.contentLeft);\n      const intrinsicWidth = block.columnWidths.reduce(\n        (total, width) => total + width,\n        0,\n      );\n      const tableWidth = Math.max(availableWidth, intrinsicWidth);\n      const rowHeights = measureTableRowHeights(block.rows, block.columnWidths);\n      const rowOffsets = buildRowOffsets(rowHeights);\n      const bodyHeight = rowOffsets[rowOffsets.length - 1] ?? 0;\n      const height = TABLE_HEADER_HEIGHT + bodyHeight + 2;\n      return {\n        ...frameBase(block, blockIndex, top, height, scale),\n        columnWidths: block.columnWidths,\n        headerHeight: TABLE_HEADER_HEIGHT,\n        kind: \"table\",\n        rowHeights,\n        rowOffsets,\n        rowCount: block.rows.length,\n        rowSourceStartLines: block.rowSourceStartLines,\n        tableWidth,\n      };\n    }\n  }\n}\n\nfunction frameBase(\n  block: PreparedTextBlock,\n  blockIndex: number,\n  top: number,\n  height: number,\n  scale: number,\n): TextBlockFrameBase {\n  return {\n    blockIndex,\n    bottom: top + height,\n    contentLeft: block.contentLeft,\n    height,\n    listDepth: block.listDepth,\n    markerClassName: block.markerClassName,\n    markerLeft: block.markerLeft,\n    markerText: block.markerText,\n    quoteDepth: block.quoteDepth,\n    quoteRailLefts: block.quoteRailLefts,\n    scale,\n    sourceEndLine: block.sourceEndLine,\n    sourceStartLine: block.sourceStartLine,\n    top,\n  };\n}\n\nfunction richInlineFragments(\n  block: PreparedInlineTextBlock,\n  line: RichInlineLine,\n): InlineFragmentLayout[] {\n  return line.fragments.map((fragment) => ({\n    className: block.classNames[fragment.itemIndex] ?? \"\",\n    font:\n      block.fonts[fragment.itemIndex] ??\n      inlineFont(\"body\", EMPTY_MARK_STATE, { fontScale: 1 }),\n    href: block.hrefs[fragment.itemIndex] ?? null,\n    leadingGap: fragment.gapBefore,\n    text: fragment.text,\n    title: block.titles[fragment.itemIndex] ?? null,\n  }));\n}\n\nfunction fallbackInlineFragments(\n  block: PreparedInlineTextBlock,\n): InlineFragmentLayout[] {\n  const fragments = block.texts.map((text, index) => ({\n    className: block.classNames[index] ?? \"\",\n    font:\n      block.fonts[index] ??\n      inlineFont(block.variant, EMPTY_MARK_STATE, { fontScale: 1 }),\n    href: block.hrefs[index] ?? null,\n    leadingGap: 0,\n    text,\n    title: block.titles[index] ?? null,\n  }));\n\n  return fragments.length > 0\n    ? fragments\n    : [\n        {\n          className: inlineClassName(block.variant, EMPTY_MARK_STATE),\n          font: inlineFont(block.variant, EMPTY_MARK_STATE, { fontScale: 1 }),\n          href: null,\n          leadingGap: 0,\n          text: block.fallbackText || \" \",\n          title: null,\n        },\n      ];\n}\n\nfunction prepareRichInlineSafe(pieces: InlinePiece[]) {\n  try {\n    return prepareRichInline(\n      pieces.map((piece) => ({\n        break: piece.breakMode,\n        extraWidth: piece.extraWidth,\n        font: piece.font,\n        text: piece.text,\n      })),\n    );\n  } catch {\n    return null;\n  }\n}\n\nfunction prepareWithSegmentsSafe(\n  text: string,\n  font: string,\n  options?: Parameters<typeof prepareWithSegments>[2],\n) {\n  try {\n    return prepareWithSegments(text, font, options);\n  } catch {\n    return null;\n  }\n}\n\nfunction measureRichInlineStatsSafe(\n  flow: PreparedRichInline,\n  width: number,\n  fallbackText: string,\n) {\n  try {\n    return measureRichInlineStats(flow, width);\n  } catch {\n    return estimateInlineStats(fallbackText, width);\n  }\n}\n\nfunction measureLineStatsSafe(\n  prepared: PreparedTextWithSegments,\n  width: number,\n  fallbackText: string,\n) {\n  try {\n    return measureLineStats(prepared, width);\n  } catch {\n    return estimateInlineStats(fallbackText, width);\n  }\n}\n\nfunction estimateInlineStats(text: string, width: number) {\n  const columns = Math.max(1, Math.floor(width / 8));\n  const lines = splitTextLines(text || \" \");\n  const lineCount = lines.reduce(\n    (sum, line) => sum + Math.max(1, Math.ceil((line || \" \").length / columns)),\n    0,\n  );\n  const maxLineWidth = Math.min(\n    width,\n    Math.max(...lines.map((line) => (line || \" \").length * 8), 1),\n  );\n  return { lineCount, maxLineWidth };\n}\n\nfunction measureMarkerWidth(text: string, style: TextStyleConfig) {\n  const font = `600 ${MARKER_FONT_PX * style.fontScale}px ${MONO_FAMILY}`;\n  const cacheKey = `${font}\\u0000${text}`;\n  const cached = markerWidthCache.get(cacheKey);\n  if (cached != null) return cached;\n\n  let width = Math.max(8, text.length * MARKER_FONT_PX * style.fontScale);\n  const prepared = prepareWithSegmentsSafe(text, font);\n  if (prepared) {\n    try {\n      width = measureNaturalWidth(prepared);\n    } catch {\n      // keep estimate\n    }\n  }\n  markerWidthCache.set(cacheKey, width);\n  return width;\n}\n\nfunction appendBlockGroup(\n  target: PreparedTextBlock[],\n  group: PreparedTextBlock[],\n  firstMargin: number,\n) {\n  if (group.length === 0) return;\n\n  for (let index = 0; index < group.length; index++) {\n    const block = group[index]!;\n    target.push({\n      ...block,\n      marginTop:\n        index === 0 ? (target.length === 0 ? 0 : firstMargin) : block.marginTop,\n    } satisfies PreparedTextBlock);\n  }\n}\n\nfunction extractMarkdownFrontmatter(markdown: string) {\n  const lines = splitTextLines(markdown);\n  if (lines[0]?.trim() !== \"---\") return null;\n\n  for (let index = 1; index < lines.length; index++) {\n    if (lines[index]!.trim() !== \"---\") continue;\n    if (index === 1) return null;\n\n    return {\n      body: lines.slice(index + 1).join(\"\\n\"),\n      endLine: index + 1,\n      text: lines.slice(1, index).join(\"\\n\"),\n    };\n  }\n\n  return null;\n}\n\nfunction createMarkdownHeadingId(\n  lines: readonly InlinePiece[][],\n  headingIds: HeadingIdRegistry,\n) {\n  const text = lines\n    .flatMap((line) => line.map((piece) => piece.text))\n    .join(\" \");\n  const base = slugifyMarkdownHeading(text) || \"section\";\n  const count = headingIds.get(base) ?? 0;\n  headingIds.set(base, count + 1);\n  return count === 0 ? base : `${base}-${count}`;\n}\n\nfunction slugifyMarkdownHeading(text: string) {\n  return text\n    .trim()\n    .toLowerCase()\n    .normalize(\"NFKD\")\n    .replace(/[^\\p{Letter}\\p{Number}\\s-]/gu, \"\")\n    .replace(/\\s+/g, \"-\")\n    .replace(/-+/g, \"-\")\n    .replace(/^-|-$/g, \"\");\n}\n\nfunction shiftBlock(\n  block: PreparedTextBlock,\n  delta: number,\n): PreparedTextBlock {\n  return {\n    ...block,\n    contentLeft: block.contentLeft + delta,\n  } satisfies PreparedTextBlock;\n}\n\nfunction resolveListMarkerText(\n  list: Tokens.List,\n  item: Tokens.ListItem,\n  index: number,\n) {\n  if (item.task) return item.checked ? \"☑\" : \"☐\";\n  if (list.ordered) {\n    const start = typeof list.start === \"number\" ? list.start : 1;\n    return `${start + index}.`;\n  }\n  return \"•\";\n}\n\nfunction resolveListMarkerClassName(list: Tokens.List, item: Tokens.ListItem) {\n  if (item.task) return \"text-muted-foreground\";\n  return list.ordered ? \"text-muted-foreground\" : \"text-muted-foreground\";\n}\n\nfunction isStandaloneImageParagraph(\n  token: Token,\n): token is Tokens.Paragraph & { tokens: [Tokens.Image] } {\n  return (\n    token.type === \"paragraph\" &&\n    Array.isArray(token.tokens) &&\n    token.tokens.length === 1 &&\n    token.tokens[0]?.type === \"image\"\n  );\n}\n\nfunction parseMarkdownHref(href: string | null | undefined) {\n  return sanitizeMarkdownUrl(href, {\n    allowedAbsoluteProtocols: new Set([\"http:\", \"https:\", \"mailto:\"]),\n    allowRelative: true,\n  });\n}\n\nfunction parseMarkdownImageSrc(src: string | null | undefined) {\n  return sanitizeMarkdownUrl(src, {\n    allowedAbsoluteProtocols: new Set([\"http:\", \"https:\", \"blob:\"]),\n    allowRelative: true,\n  });\n}\n\nfunction sanitizeMarkdownTitle(title: string | null | undefined) {\n  if (!title) return null;\n  return title.replace(/[\\u0000-\\u001f\\u007f]/g, \"\").trim() || null;\n}\n\nfunction sanitizeMarkdownLanguage(language: string | null | undefined) {\n  if (!language) return null;\n  return (\n    language\n      .replace(/[\\u0000-\\u001f\\u007f]/g, \"\")\n      .trim()\n      .slice(0, 40) || null\n  );\n}\n\nfunction sanitizeMarkdownUrl(\n  value: string | null | undefined,\n  {\n    allowedAbsoluteProtocols,\n    allowRelative,\n  }: {\n    allowedAbsoluteProtocols: ReadonlySet<string>;\n    allowRelative: boolean;\n  },\n) {\n  if (!value) return null;\n  const trimmed = value.trim();\n  if (!trimmed || /[\\u0000-\\u001f\\u007f]/.test(trimmed)) return null;\n  if (trimmed.startsWith(\"#\")) return trimmed;\n  if (allowRelative && /^\\.{0,2}\\//.test(trimmed)) return trimmed;\n  if (allowRelative && /^\\//.test(trimmed)) return trimmed;\n\n  try {\n    const url = new URL(trimmed);\n    return allowedAbsoluteProtocols.has(url.protocol) ? url.href : null;\n  } catch {\n    return null;\n  }\n}\n\nfunction fallbackTextForToken(token: Token) {\n  if (\"text\" in token && typeof token.text === \"string\") return token.text;\n  return token.raw ?? \"\";\n}\n\nfunction countSourceLineBreaks(raw: string | null | undefined) {\n  if (!raw) return 0;\n  const matches = raw.match(/\\r\\n|[\\n\\r\\u2028\\u2029]/g);\n  return matches ? matches.length : 0;\n}\n\nfunction endsWithSourceLineBreak(raw: string | null | undefined) {\n  return raw ? /(?:\\r\\n|[\\n\\r\\u2028\\u2029])$/.test(raw) : false;\n}\n\nfunction tableCellFromTokens(tokens: readonly Token[]): PreparedTableCell {\n  const { className, href, text, title } = inlineTokensToTableCell(tokens);\n  return {\n    className,\n    href,\n    text: text || \" \",\n    title,\n  };\n}\n\nfunction inlineTokensToTableCell(tokens: readonly Token[]): PreparedTableCell {\n  let className = \"\";\n  let href: string | null = null;\n  let text = \"\";\n  let title: string | null = null;\n  for (const token of tokens) {\n    switch (token.type) {\n      case \"strong\":\n        text += inlineTokensToTableCell(token.tokens ?? []).text;\n        className = cnClassNames(className, \"font-semibold\");\n        break;\n      case \"em\":\n        text += inlineTokensToTableCell(token.tokens ?? []).text;\n        className = cnClassNames(className, \"italic\");\n        break;\n      case \"del\":\n        text += inlineTokensToTableCell(token.tokens ?? []).text;\n        className = cnClassNames(className, \"line-through\");\n        break;\n      case \"link\": {\n        const child = inlineTokensToTableCell(token.tokens ?? []);\n        text += child.text;\n        href = href ?? parseMarkdownHref(token.href);\n        title = title ?? sanitizeMarkdownTitle(token.title);\n        className = cnClassNames(className, \"text-primary underline\");\n        break;\n      }\n      case \"codespan\":\n        text += token.text;\n        className = cnClassNames(className, \"font-mono\");\n        break;\n      case \"escape\":\n      case \"text\":\n      case \"html\":\n        text += token.text;\n        break;\n      case \"br\":\n        text += \" \";\n        break;\n      case \"image\":\n        text += token.text || token.href;\n        break;\n      default:\n        text += fallbackTextForToken(token);\n    }\n  }\n  return { className, href, text, title };\n}\n\nfunction measureTableColumnWidths(\n  header: readonly PreparedTableCell[],\n  rows: readonly PreparedTableCell[][],\n) {\n  return header.map((cell, columnIndex) => {\n    const columnTexts = [\n      cell.text,\n      ...rows.map((row) => row[columnIndex]?.text ?? \"\"),\n    ];\n    const maxLength = Math.max(\n      1,\n      ...columnTexts.map((text) => Math.min(48, text.length)),\n    );\n    return Math.min(\n      TABLE_COLUMN_MAX_WIDTH,\n      Math.max(\n        TABLE_COLUMN_MIN_WIDTH,\n        maxLength * TABLE_CELL_FONT_PX * 0.58 + TABLE_CELL_PADDING_X * 2,\n      ),\n    );\n  });\n}\n\nfunction measureTableRowHeights(\n  rows: readonly PreparedTableCell[][],\n  columnWidths: readonly number[],\n) {\n  return rows.map((row) => {\n    const lineCount = Math.max(\n      1,\n      ...row.map((cell, index) =>\n        measureTableCellLineCount(cell.text, columnWidths[index] ?? 0),\n      ),\n    );\n    return Math.max(\n      TABLE_ROW_MIN_HEIGHT,\n      lineCount * TABLE_ROW_LINE_HEIGHT + 12,\n    );\n  });\n}\n\nfunction measureTableCellLineCount(text: string, columnWidth: number) {\n  const innerWidth = Math.max(1, columnWidth - TABLE_CELL_PADDING_X * 2);\n  const charsPerLine = Math.max(\n    1,\n    Math.floor(innerWidth / (TABLE_CELL_FONT_PX * 0.58)),\n  );\n  return splitTextLines(text || \" \").reduce((count, line) => {\n    return count + Math.max(1, Math.ceil((line || \" \").length / charsPerLine));\n  }, 0);\n}\n\nfunction buildRowOffsets(rowHeights: readonly number[]) {\n  const offsets = [0];\n  for (const height of rowHeights) {\n    offsets.push(offsets[offsets.length - 1]! + height);\n  }\n  return offsets;\n}\n\nfunction findTableRowAtOffset(rowOffsets: readonly number[], offset: number) {\n  if (offset <= 0) return 0;\n  const rowCount = Math.max(0, rowOffsets.length - 1);\n  let low = 0;\n  let high = rowCount;\n  while (low < high) {\n    const mid = Math.floor((low + high) / 2);\n    if ((rowOffsets[mid + 1] ?? 0) <= offset) low = mid + 1;\n    else high = mid;\n  }\n  return low;\n}\n\nfunction findTableRowEndAtOffset(\n  rowOffsets: readonly number[],\n  offset: number,\n) {\n  if (offset <= 0) return 0;\n  const rowCount = Math.max(0, rowOffsets.length - 1);\n  let low = 0;\n  let high = rowCount;\n  while (low < high) {\n    const mid = Math.floor((low + high) / 2);\n    if ((rowOffsets[mid] ?? 0) < offset) low = mid + 1;\n    else high = mid;\n  }\n  return low;\n}\n\nfunction tableColumnAlignment(\n  alignment: \"center\" | \"left\" | \"right\" | null,\n): TableColumnAlignment {\n  if (alignment === \"center\" || alignment === \"right\") return alignment;\n  return \"left\";\n}\n\nfunction tableClipboardCell(text: string) {\n  return text.replace(/[\\t\\r\\n]+/g, \" \").trim();\n}\n\nfunction cnClassNames(...classes: Array<string | null | undefined>) {\n  return classes.filter(Boolean).join(\" \");\n}\n\nfunction headingVariant(depth: number): InlineVariant {\n  if (depth <= 1) return \"heading-1\";\n  if (depth === 2) return \"heading-2\";\n  return \"body\";\n}\n\nfunction lineHeightForVariant(variant: InlineVariant, style: TextStyleConfig) {\n  switch (variant) {\n    case \"heading-1\":\n      return HEADING_ONE_LINE_PX * style.fontScale;\n    case \"heading-2\":\n      return HEADING_TWO_LINE_PX * style.fontScale;\n    case \"body\":\n      return BODY_LINE_PX * style.fontScale;\n  }\n}\n\nfunction inlineFont(\n  variant: InlineVariant,\n  marks: MarkState,\n  style: TextStyleConfig,\n) {\n  const italic = marks.italic ? \"italic \" : \"\";\n  switch (variant) {\n    case \"heading-1\":\n      return `${italic}${marks.bold ? 800 : 700} ${HEADING_ONE_FONT_PX * style.fontScale}px ${SERIF_FAMILY}`;\n    case \"heading-2\":\n      return `${italic}${marks.bold ? 800 : 700} ${HEADING_TWO_FONT_PX * style.fontScale}px ${SERIF_FAMILY}`;\n    case \"body\":\n      return `${italic}${marks.bold ? 700 : marks.href ? 500 : 400} ${BODY_FONT_PX * style.fontScale}px ${SANS_FAMILY}`;\n  }\n}\n\nfunction inlineClassName(variant: InlineVariant, marks: MarkState) {\n  const classes = [\n    \"inline-block wrap-break-word whitespace-pre-wrap whitespace-pre align-baseline leading-none\",\n  ];\n  if (variant === \"heading-1\")\n    classes.push(\"font-serif text-[1.45em] font-semibold\");\n  if (variant === \"heading-2\")\n    classes.push(\"font-serif text-[1.18em] font-semibold\");\n  if (marks.bold) classes.push(\"font-semibold\");\n  if (marks.italic) classes.push(\"italic\");\n  if (marks.strike) classes.push(\"line-through\");\n  if (marks.href) classes.push(\"text-primary underline underline-offset-2\");\n  return classes.join(\" \");\n}\n\nfunction canMergeInlinePieces(a: InlinePiece, b: InlinePiece) {\n  return (\n    a.breakMode === b.breakMode &&\n    a.className === b.className &&\n    a.extraWidth === b.extraWidth &&\n    a.font === b.font &&\n    a.href === b.href &&\n    a.title === b.title\n  );\n}\n\nfunction stripSingleTrailingNewline(text: string) {\n  return text.endsWith(\"\\n\") ? text.slice(0, -1) : text;\n}\n\nfunction countTextWords(text: string) {\n  const matches = text.trim().match(/\\S+/g);\n  return matches?.length ?? 0;\n}\n\nfunction countTextLineWords(lines: readonly string[]) {\n  return lines.reduce((count, line) => count + countTextWords(line), 0);\n}\n\nfunction hashTextForPreparedDocument(text: string) {\n  let hash = 2166136261;\n  for (let index = 0; index < text.length; index += 1) {\n    hash ^= text.charCodeAt(index);\n    hash = Math.imul(hash, 16777619);\n  }\n  return (hash >>> 0).toString(36);\n}\n\nfunction safeWidth(width: number) {\n  return Number.isFinite(width) && width > 0 ? width : 1;\n}\n\nfunction safeScale(scale: number) {\n  return Number.isFinite(scale) && scale > 0 ? scale : 1;\n}\n",
      "type": "registry:ui",
      "target": "@ui/text-viewer-layout.ts"
    },
    {
      "path": "registry/new-york-v4/ui/text-viewer-ranges.ts",
      "content": "export interface TextLineRange {\n  start: number;\n  end: number;\n}\n\nexport interface NormalizedTextLineRange extends TextLineRange {\n  readonly normalized: true;\n}\n\nconst clamp = (value: number, min: number, max: number) =>\n  Math.min(max, Math.max(min, value));\n\nexport function normalizeTextLineRange(\n  range: TextLineRange | null | undefined,\n  lineCount: number,\n): NormalizedTextLineRange | null {\n  if (\n    !range ||\n    !Number.isFinite(range.start) ||\n    !Number.isFinite(range.end) ||\n    !Number.isFinite(lineCount) ||\n    lineCount <= 0\n  ) {\n    return null;\n  }\n\n  const maxLine = Math.floor(lineCount);\n  const rawStart = Math.trunc(range.start);\n  const rawEnd = Math.trunc(range.end);\n  const start = Math.min(rawStart, rawEnd);\n  const end = Math.max(rawStart, rawEnd);\n\n  if (end < 1 || start > maxLine) return null;\n\n  return {\n    start: clamp(start, 1, maxLine),\n    end: clamp(end, 1, maxLine),\n    normalized: true,\n  };\n}\n\nexport function isLineInRange(\n  lineNumber: number,\n  range: NormalizedTextLineRange | null,\n) {\n  return range != null && lineNumber >= range.start && lineNumber <= range.end;\n}\n",
      "type": "registry:ui",
      "target": "@ui/text-viewer-ranges.ts"
    },
    {
      "path": "registry/new-york-v4/ui/line-ranges.ts",
      "content": "export * from \"./text-viewer-ranges\";\n",
      "type": "registry:ui",
      "target": "@ui/line-ranges.ts"
    },
    {
      "path": "registry/new-york-v4/ui/text-viewer-resource.ts",
      "content": "import {\n  isResourceError,\n  isViewerFormatError,\n  ViewerFormatError,\n  type ViewerFormatErrorMapperOptions,\n} from \"@/lib/viewer-errors\";\nimport type {\n  ViewerContentIdentity,\n  ViewerContentPayload,\n  ViewerContentText,\n} from \"@/lib/viewer-resource\";\n\nexport const DEFAULT_MAX_BYTES = 1_000_000;\nexport const DEFAULT_MAX_LINES = 10_000;\nexport const MAX_TEXT_RESOURCE_CACHE_ENTRIES = 64;\nexport const TEXT_LINE_DETACHMENT_SOURCE_MIN_LENGTH = 64 * 1024;\nexport const TEXT_LINE_DETACHMENT_MAX_LINE_LENGTH = 16 * 1024;\n\nexport interface TextViewerBounds {\n  maxBytes?: number;\n  maxLines?: number;\n}\n\nexport type TextViewerTooLargeReason = \"bytes\" | \"lines\";\nexport type TextViewerBoundName = \"maxBytes\" | \"maxLines\";\n\nexport class TextViewerTooLargeError extends ViewerFormatError {\n  readonly reason: TextViewerTooLargeReason;\n\n  constructor(reason: TextViewerTooLargeReason) {\n    super({\n      format: \"text\",\n      kind: \"bounds\",\n      message: `Text file exceeds ${reason} limit`,\n    });\n    this.name = \"TextViewerTooLargeError\";\n    this.reason = reason;\n  }\n}\n\nexport class TextViewerInvalidBoundsError extends ViewerFormatError {\n  readonly boundName: TextViewerBoundName;\n\n  constructor(boundName: TextViewerBoundName) {\n    super({\n      format: \"text\",\n      kind: \"bounds\",\n      message: `${boundName} must be a positive integer`,\n    });\n    this.name = \"TextViewerInvalidBoundsError\";\n    this.boundName = boundName;\n  }\n}\n\nexport function toTextFormatError(\n  error: unknown,\n  options: ViewerFormatErrorMapperOptions = {\n    kind: \"load_failed\",\n    message: \"Failed to load text.\",\n  },\n): ViewerFormatError {\n  if (isViewerFormatError(error)) return error;\n  return new ViewerFormatError({\n    format: \"text\",\n    kind: options.kind,\n    message: options.message,\n    cause: error,\n  });\n}\n\nexport interface PreparedTextDocument {\n  text: string;\n  lines: readonly string[];\n  lineCount: number;\n}\n\ninterface TextResource {\n  promise: Promise<PreparedTextDocument>;\n  status: \"pending\" | \"resolved\" | \"rejected\";\n  value?: PreparedTextDocument;\n  error?: unknown;\n}\n\nconst textResourceCache = new Map<string, TextResource>();\n\nexport type TextViewerContent = ViewerContentIdentity &\n  ViewerContentPayload &\n  ViewerContentText;\n\nfunction textViewerResourceKey({\n  content,\n  retryVersion,\n  bounds,\n}: {\n  content: ViewerContentIdentity;\n  retryVersion: number;\n  bounds: Required<TextViewerBounds>;\n}) {\n  return `${content.key}\\0${retryVersion}\\0${bounds.maxBytes}\\0${bounds.maxLines}`;\n}\n\nexport function clearTextViewerResourceCacheForTests() {\n  textResourceCache.clear();\n}\n\nexport function resolvedTextViewerBounds({\n  maxBytes = DEFAULT_MAX_BYTES,\n  maxLines = DEFAULT_MAX_LINES,\n}: TextViewerBounds = {}): Required<TextViewerBounds> {\n  return {\n    maxBytes: resolveTextViewerBound(maxBytes, \"maxBytes\"),\n    maxLines: resolveTextViewerBound(maxLines, \"maxLines\"),\n  };\n}\n\nexport function assertTextWithinBounds(\n  text: string,\n  bounds: Required<TextViewerBounds>,\n) {\n  prepareTextDocument(text, bounds);\n}\n\n// Split into stable source lines. The prose viewer may wrap each source line\n// into several visual lines, but highlighting and scroll APIs stay source-line\n// based.\nexport function splitTextLines(text: string) {\n  return splitTextLinesForDocument(text).lines;\n}\n\nexport function shouldDetachTextLine({\n  lineLength,\n  sourceLength,\n}: {\n  lineLength: number;\n  sourceLength: number;\n}) {\n  return (\n    sourceLength >= TEXT_LINE_DETACHMENT_SOURCE_MIN_LENGTH &&\n    lineLength > 0 &&\n    lineLength <= TEXT_LINE_DETACHMENT_MAX_LINE_LENGTH\n  );\n}\n\nexport function detachTextLine(line: string) {\n  return line.length === 0 ? line : ` ${line}`.slice(1);\n}\n\nexport function readTextResource({\n  content,\n  retryVersion,\n  bounds,\n}: {\n  content: TextViewerContent;\n  retryVersion: number;\n  bounds: Required<TextViewerBounds>;\n}) {\n  return readTextDocument({ content, retryVersion, bounds }).text;\n}\n\nexport function readTextDocument({\n  content,\n  retryVersion,\n  bounds,\n}: {\n  content: TextViewerContent;\n  retryVersion: number;\n  bounds: Required<TextViewerBounds>;\n}): PreparedTextDocument {\n  const resourceKey = textViewerResourceKey({ content, retryVersion, bounds });\n  const inlineText = inlineTextResource(content);\n  if (inlineText != null) {\n    return getInlineTextDocument({\n      bounds,\n      resourceKey,\n      text: inlineText,\n    });\n  }\n\n  const textResource = getTextResource({ content, resourceKey, bounds });\n\n  if (textResource.status === \"resolved\") {\n    return textResource.value ?? emptyPreparedTextDocument(bounds);\n  }\n  if (textResource.status === \"rejected\") throw textResource.error;\n\n  throw textResource.promise;\n}\n\nexport function prepareTextDocument(\n  text: string,\n  bounds: Required<TextViewerBounds>,\n  options: { isByteLengthChecked?: boolean } = {},\n): PreparedTextDocument {\n  if (\n    !options.isByteLengthChecked &&\n    new TextEncoder().encode(text).byteLength > bounds.maxBytes\n  ) {\n    throw new TextViewerTooLargeError(\"bytes\");\n  }\n\n  const lines = splitTextLinesForDocument(text, {\n    maxLines: bounds.maxLines,\n  }).lines;\n\n  return {\n    lineCount: lines.length,\n    lines,\n    text,\n  };\n}\n\nfunction inlineTextResource(content: ViewerContentPayload) {\n  return content.payload.kind === \"text\" ? content.payload.text : null;\n}\n\nfunction splitTextLinesForDocument(\n  text: string,\n  options: { maxLines?: number } = {},\n) {\n  const maxLines = options.maxLines ?? Number.POSITIVE_INFINITY;\n  const lines: string[] = [];\n  let lineStart = 0;\n\n  for (let index = 0; index < text.length; index += 1) {\n    const breakLength = textLineBreakLength(text, index);\n    if (breakLength === 0) continue;\n\n    appendTextLine(lines, text, lineStart, index, maxLines);\n    index += breakLength - 1;\n    lineStart = index + 1;\n  }\n\n  appendTextLine(lines, text, lineStart, text.length, maxLines);\n  return { lines };\n}\n\nfunction appendTextLine(\n  lines: string[],\n  text: string,\n  start: number,\n  end: number,\n  maxLines: number,\n) {\n  if (lines.length >= maxLines) {\n    throw new TextViewerTooLargeError(\"lines\");\n  }\n\n  const line = text.slice(start, end);\n  lines.push(\n    shouldDetachTextLine({\n      lineLength: end - start,\n      sourceLength: text.length,\n    })\n      ? detachTextLine(line)\n      : line,\n  );\n}\n\nfunction textLineBreakLength(text: string, index: number) {\n  const code = text.charCodeAt(index);\n  if (code === 0x0d) {\n    return text.charCodeAt(index + 1) === 0x0a ? 2 : 1;\n  }\n  return code === 0x0a || code === 0x2028 || code === 0x2029 ? 1 : 0;\n}\n\nfunction getInlineTextDocument({\n  bounds,\n  resourceKey,\n  text,\n}: {\n  bounds: Required<TextViewerBounds>;\n  resourceKey: string;\n  text: string;\n}) {\n  const cached = textResourceCache.get(resourceKey);\n  if (cached?.status === \"resolved\" && cached.value) return cached.value;\n\n  const document = prepareTextDocument(text, bounds);\n  textResourceCache.set(resourceKey, {\n    promise: Promise.resolve(document),\n    status: \"resolved\",\n    value: document,\n  });\n  trimTextResourceCache();\n  return document;\n}\n\nfunction getTextResource({\n  content,\n  resourceKey,\n  bounds,\n}: {\n  content: TextViewerContent;\n  resourceKey: string;\n  bounds: Required<TextViewerBounds>;\n}) {\n  let textResource = textResourceCache.get(resourceKey);\n  if (!textResource) {\n    const nextResource: TextResource = {\n      status: \"pending\",\n      promise: readBoundedTextResource(content, bounds).then((text) =>\n        prepareTextDocument(text, bounds, { isByteLengthChecked: true }),\n      ),\n    };\n    nextResource.promise.then(\n      (value) => {\n        nextResource.status = \"resolved\";\n        nextResource.value = value;\n      },\n      (error) => {\n        nextResource.status = \"rejected\";\n        nextResource.error = error;\n      },\n    );\n    textResource = nextResource;\n    textResourceCache.set(resourceKey, textResource);\n    trimTextResourceCache();\n  }\n  return textResource;\n}\n\nasync function readBoundedTextResource(\n  content: ViewerContentText,\n  bounds: Required<TextViewerBounds>,\n) {\n  try {\n    return await content.readText(bounds);\n  } catch (error) {\n    if (isResourceError(error)) throw error;\n    throw toTextFormatError(error);\n  }\n}\n\nfunction emptyPreparedTextDocument(\n  bounds: Required<TextViewerBounds>,\n): PreparedTextDocument {\n  return prepareTextDocument(\"\", bounds);\n}\n\nfunction resolveTextViewerBound(value: number, boundName: TextViewerBoundName) {\n  if (!Number.isSafeInteger(value) || value < 1) {\n    throw new TextViewerInvalidBoundsError(boundName);\n  }\n  return value;\n}\n\nfunction trimTextResourceCache() {\n  while (textResourceCache.size > MAX_TEXT_RESOURCE_CACHE_ENTRIES) {\n    const firstKey = textResourceCache.keys().next().value;\n    if (firstKey === undefined) return;\n    textResourceCache.delete(firstKey);\n  }\n}\n",
      "type": "registry:ui",
      "target": "@ui/text-viewer-resource.ts"
    },
    {
      "path": "registry/new-york-v4/ui/plain-text-resource.ts",
      "content": "export * from \"./text-viewer-resource\";\n",
      "type": "registry:ui",
      "target": "@ui/plain-text-resource.ts"
    },
    {
      "path": "registry/new-york-v4/ui/text-viewer-scale.ts",
      "content": "export const TEXT_VIEWER_MIN_SCALE = 0.1;\nexport const TEXT_VIEWER_MAX_SCALE = 5;\nexport const TEXT_VIEWER_BLOCK_PADDING = 8;\n\nexport function clampTextViewerScale(value: number) {\n  return Math.min(\n    TEXT_VIEWER_MAX_SCALE,\n    Math.max(TEXT_VIEWER_MIN_SCALE, value),\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/text-viewer-scale.ts"
    },
    {
      "path": "registry/new-york-v4/ui/text-viewer-types.ts",
      "content": "import type {\n  BlobViewerSource,\n  TextSource,\n  UrlViewerSource,\n} from \"@/lib/viewer-source\";\n\nimport type { TextViewerMode } from \"./text-viewer-layout\";\nimport type { TextLineRange } from \"./text-viewer-ranges\";\nimport type { TextViewerBounds } from \"./text-viewer-resource\";\n\nexport type { TextLineRange };\n\nexport interface TextViewerHandle {\n  scrollToLineRange: (range: TextLineRange, options?: ScrollToOptions) => void;\n  getViewportElement: () => HTMLDivElement | null;\n}\n\nexport type TextDocumentSource =\n  | UrlViewerSource\n  | BlobViewerSource\n  | TextSource;\n\nexport interface TextViewerProps extends TextViewerBounds {\n  source: TextDocumentSource;\n  className?: string;\n  controls?: boolean;\n  download?: boolean;\n  /** 1-based inclusive line range to highlight, or null. */\n  highlight?: TextLineRange | null;\n  /** Drop the outer border/rounded/background so the viewer fills its container. */\n  bare?: boolean;\n  /** Explicitly select text or markdown when the caller has already classified the source. */\n  mode?: TextViewerMode;\n}\n",
      "type": "registry:ui",
      "target": "@ui/text-viewer-types.ts"
    },
    {
      "path": "registry/new-york-v4/lib/viewer-source.ts",
      "content": "export type FileCategory =\n  | \"pdf\"\n  | \"docx\"\n  | \"xlsx\"\n  | \"pptx\"\n  | \"csv\"\n  | \"image\"\n  | \"markdown\"\n  | \"html\"\n  | \"email\"\n  | \"text\"\n  | \"unsupported\";\n\nexport type ViewerSource = UrlViewerSource | TextSource | BlobViewerSource;\n\nexport interface UrlViewerSource {\n  kind: \"url\";\n  url: string;\n  fileName?: string;\n  mimeType?: string;\n  downloadUrl?: string;\n  identityKey?: string;\n}\n\nexport interface TextSource {\n  kind: \"text\";\n  text: string;\n  fileName?: string;\n  mimeType?: string;\n  identityKey?: string;\n}\n\nexport interface BlobViewerSource {\n  kind: \"blob\";\n  blob: Blob;\n  identityKey: string;\n  fileName?: string;\n  mimeType?: string;\n  downloadUrl?: string;\n}\n\nexport interface ViewerDescriptor {\n  source: ViewerSource;\n  category: FileCategory;\n  identityKey: string;\n  displayName: string;\n  fileName: string;\n  mimeType?: string;\n}\n\nconst EXTENSION_CATEGORY: Record<string, FileCategory> = {\n  pdf: \"pdf\",\n  docx: \"docx\",\n  xlsx: \"xlsx\",\n  xls: \"xlsx\",\n  xlsm: \"xlsx\",\n  pptx: \"pptx\",\n  csv: \"csv\",\n  tsv: \"csv\",\n  png: \"image\",\n  jpg: \"image\",\n  jpeg: \"image\",\n  gif: \"image\",\n  webp: \"image\",\n  avif: \"image\",\n  bmp: \"image\",\n  svg: \"image\",\n  ico: \"image\",\n  tif: \"image\",\n  tiff: \"image\",\n  md: \"markdown\",\n  markdown: \"markdown\",\n  mdx: \"text\",\n  html: \"html\",\n  htm: \"html\",\n  eml: \"email\",\n  txt: \"text\",\n  text: \"text\",\n  log: \"text\",\n  json: \"text\",\n  jsonl: \"text\",\n  json5: \"text\",\n  ndjson: \"text\",\n  xml: \"text\",\n  yaml: \"text\",\n  yml: \"text\",\n  toml: \"text\",\n  ini: \"text\",\n  env: \"text\",\n  js: \"text\",\n  mjs: \"text\",\n  cjs: \"text\",\n  jsx: \"text\",\n  ts: \"text\",\n  tsx: \"text\",\n  css: \"text\",\n  scss: \"text\",\n  less: \"text\",\n  py: \"text\",\n  rb: \"text\",\n  go: \"text\",\n  rs: \"text\",\n  java: \"text\",\n  kt: \"text\",\n  c: \"text\",\n  h: \"text\",\n  cpp: \"text\",\n  cc: \"text\",\n  cs: \"text\",\n  php: \"text\",\n  sh: \"text\",\n  bash: \"text\",\n  zsh: \"text\",\n  sql: \"text\",\n  graphql: \"text\",\n  proto: \"text\",\n  lua: \"text\",\n  r: \"text\",\n  swift: \"text\",\n  scala: \"text\",\n  pl: \"text\",\n  vue: \"text\",\n  svelte: \"text\",\n};\n\nexport function extensionOf(name: string): string | null {\n  const clean = name.split(/[?#]/)[0];\n  const base = clean.split(\"/\").pop() ?? clean;\n  const dot = base.lastIndexOf(\".\");\n  return dot > 0 ? base.slice(dot + 1).toLowerCase() : null;\n}\n\nexport function extractName(url: string): string {\n  const clean = url.split(/[?#]/)[0];\n  return clean.split(\"/\").pop() || \"file\";\n}\n\nexport function detectCategory(\n  fileName: string,\n  mimeType?: string,\n): FileCategory {\n  const ext = extensionOf(fileName);\n  if (ext && EXTENSION_CATEGORY[ext]) return EXTENSION_CATEGORY[ext];\n  if (mimeType) {\n    const fromMime = categoryFromMime(mimeType);\n    if (fromMime) return fromMime;\n  }\n  return \"unsupported\";\n}\n\nexport function resolveViewerDescriptor({\n  source,\n  category,\n}: {\n  source: ViewerSource;\n  category?: FileCategory;\n}): ViewerDescriptor {\n  const resolvedMimeType =\n    source.mimeType ??\n    (source.kind === \"blob\" && source.blob.type ? source.blob.type : undefined);\n  const displayName = source.fileName ?? defaultDisplayName(source);\n  const fileName = source.fileName ?? defaultFileName(source);\n  const resolvedCategory =\n    category ?? detectCategory(displayName, resolvedMimeType);\n\n  return {\n    source,\n    category: resolvedCategory,\n    identityKey: source.identityKey ?? defaultIdentityKey(source),\n    displayName,\n    fileName,\n    mimeType: resolvedMimeType,\n  };\n}\n\nfunction categoryFromMime(mimeType: string): FileCategory | null {\n  const mime = mimeType.toLowerCase().split(\";\")[0].trim();\n  if (mime === \"application/pdf\") return \"pdf\";\n  if (mime.includes(\"wordprocessingml\")) return \"docx\";\n  if (mime.includes(\"spreadsheet\") || mime.includes(\"ms-excel\")) return \"xlsx\";\n  if (mime.includes(\"presentation\") || mime.includes(\"ms-powerpoint\")) {\n    return \"pptx\";\n  }\n  if (mime === \"text/csv\" || mime === \"text/tab-separated-values\") return \"csv\";\n  if (mime === \"text/markdown\") return \"markdown\";\n  if (mime === \"text/html\") return \"html\";\n  if (mime === \"message/rfc822\" || mime === \"message/global\") {\n    return \"email\";\n  }\n  if (mime.startsWith(\"image/\")) return \"image\";\n  if (mime === \"application/json\" || mime === \"application/xml\") return \"text\";\n  if (mime.startsWith(\"text/\")) return \"text\";\n  return null;\n}\n\nfunction defaultDisplayName(source: ViewerSource) {\n  if (source.kind === \"url\") return source.url;\n  if (source.kind === \"text\") return \"text.txt\";\n  return \"file\";\n}\n\nfunction defaultFileName(source: ViewerSource) {\n  if (source.kind === \"url\") return extractName(source.url);\n  if (source.kind === \"text\") return \"text.txt\";\n  return \"file\";\n}\n\nfunction defaultIdentityKey(source: ViewerSource) {\n  if (source.kind === \"url\") return `url:${source.url}`;\n  if (source.kind === \"text\") return textPayloadIdentityKey(source.text);\n  return source.identityKey;\n}\n\nexport function textPayloadIdentityKey(text: string) {\n  return textPayloadKey(text);\n}\n\nexport function textPayloadKey(text: string) {\n  return `text:${text.length}:${hashString(text)}`;\n}\n\nfunction hashString(text: string) {\n  let hash = 0x811c9dc5;\n  for (let index = 0; index < text.length; index += 1) {\n    hash ^= text.charCodeAt(index);\n    hash = Math.imul(hash, 0x01000193);\n  }\n  return (hash >>> 0).toString(36);\n}\n",
      "type": "registry:lib",
      "target": "@lib/viewer-source.ts"
    },
    {
      "path": "registry/new-york-v4/lib/viewer-resource.ts",
      "content": "import {\n  createBlobDownloadAction,\n  createHrefDownloadAction,\n  createTextDownloadAction,\n  type ViewerDownloadAction,\n} from \"@/lib/viewer-download-actions\";\nimport {\n  isAbortError,\n  ResourceError,\n  type ResourceTooLargeReason,\n} from \"@/lib/viewer-errors\";\nimport {\n  resolveViewerDescriptor,\n  textPayloadKey,\n  type BlobViewerSource,\n  type FileCategory,\n  type TextSource,\n  type UrlViewerSource,\n  type ViewerDescriptor,\n  type ViewerSource,\n} from \"@/lib/viewer-source\";\n\nexport interface ResourceReadOptions {\n  cache?: RequestCache;\n  signal?: AbortSignal;\n}\n\nexport interface TextReadOptions extends ResourceReadOptions {\n  maxBytes?: number;\n  maxLines?: number;\n}\n\nexport interface ByteRange {\n  start: number;\n  end: number;\n}\n\nexport interface ByteRangeResult {\n  buffer: ArrayBuffer;\n  contentRange?: {\n    start: number;\n    end: number;\n    total: number | null;\n  };\n  isComplete: boolean;\n}\n\nexport interface ViewerResourceKeys {\n  readonly load: string;\n  readonly presentation: string;\n  readonly resource: string;\n}\n\nexport type ViewerResourcePayload =\n  | { kind: \"url\"; url: string }\n  | { kind: \"blob\"; blob: Blob }\n  | { kind: \"text\"; text: string };\n\nexport interface ViewerResourceContent {\n  readonly key: string;\n  readonly sourceKind: ViewerSource[\"kind\"];\n  readonly directUrl: string | null;\n  readonly mimeType?: string;\n  readonly payload: ViewerResourcePayload;\n  readBlob(options?: ResourceReadOptions): Promise<Blob>;\n  readBytes(options?: ResourceReadOptions): Promise<ArrayBuffer>;\n  readText(options?: TextReadOptions): Promise<string>;\n  readStream(\n    options?: ResourceReadOptions,\n  ): Promise<ReadableStream<Uint8Array>>;\n  readRange(\n    range: ByteRange,\n    options?: ResourceReadOptions,\n  ): Promise<ByteRangeResult>;\n}\n\nexport type ViewerContentIdentity = Pick<\n  ViewerResourceContent,\n  \"key\" | \"sourceKind\"\n>;\n\nexport type ViewerContentDirectUrl = ViewerContentIdentity &\n  Pick<ViewerResourceContent, \"directUrl\">;\n\nexport type ViewerContentPayload = ViewerContentIdentity &\n  Pick<ViewerResourceContent, \"payload\">;\n\nexport type ViewerContentMime = ViewerContentIdentity &\n  Pick<ViewerResourceContent, \"mimeType\">;\n\nexport type ViewerContentBlob = ViewerContentIdentity &\n  Pick<ViewerResourceContent, \"readBlob\">;\n\nexport type ViewerContentBytes = ViewerContentIdentity &\n  Pick<ViewerResourceContent, \"readBytes\">;\n\nexport type ViewerContentText = ViewerContentIdentity &\n  Pick<ViewerResourceContent, \"readText\">;\n\nexport type ViewerContentStream = ViewerContentIdentity &\n  Pick<ViewerResourceContent, \"readStream\">;\n\nexport type ViewerContentRange = ViewerContentIdentity &\n  Pick<ViewerResourceContent, \"readRange\">;\n\nexport interface ViewerResource {\n  readonly descriptor: ViewerDescriptor;\n  readonly sourceKind: ViewerSource[\"kind\"];\n  readonly keys: ViewerResourceKeys;\n  readonly identityKey: string;\n  readonly fileName: string;\n  readonly mimeType?: string;\n  readonly content: ViewerResourceContent;\n  readonly originalDownload: ViewerDownloadAction;\n}\n\nconst URL_RESOURCE_REGISTRY_MAX = 128;\nconst TEXT_RESOURCE_REGISTRY_MAX = 64;\n// LF, CR, CRLF, LINE SEPARATOR (U+2028), and PARAGRAPH SEPARATOR (U+2029) — the\n// ECMAScript LineTerminator set, matching what a browser breaks on in a\n// `white-space: pre` block. Kept in sync with text-viewer-resource's splitter.\nconst TEXT_LINE_BREAK_PATTERN = /\\r\\n|[\\n\\r\\u2028\\u2029]/g;\n\nconst urlViewerResourceRegistry = new Map<string, ViewerResource>();\nconst urlViewerResourceContentRegistry = new Map<\n  string,\n  ViewerResourceContent\n>();\nconst textViewerResourceRegistry = new Map<string, ViewerResource>();\nconst textViewerResourceContentRegistry = new Map<\n  string,\n  ViewerResourceContent\n>();\nlet blobViewerResourceRegistry = new WeakMap<\n  Blob,\n  Map<string, ViewerResource>\n>();\nlet blobViewerResourceContentRegistry = new WeakMap<\n  Blob,\n  Map<string, ViewerResourceContent>\n>();\nconst blobObjectKeys = new WeakMap<Blob, string>();\nlet nextBlobObjectKey = 0;\n\nexport function createViewerResource(\n  source: ViewerSource,\n  category?: FileCategory,\n): ViewerResource {\n  const descriptor = resolveViewerDescriptor({ source, category });\n  const keys = viewerResourceKeys(source, descriptor);\n\n  if (source.kind === \"url\") {\n    return internUrlResource(source, descriptor, keys);\n  }\n  if (source.kind === \"blob\") {\n    return internBlobResource(source, descriptor, keys);\n  }\n  return internTextResource(source, descriptor, keys);\n}\n\nexport function clearViewerResourceRegistryForTests() {\n  urlViewerResourceRegistry.clear();\n  urlViewerResourceContentRegistry.clear();\n  textViewerResourceRegistry.clear();\n  textViewerResourceContentRegistry.clear();\n  blobViewerResourceRegistry = new WeakMap();\n  blobViewerResourceContentRegistry = new WeakMap();\n}\n\nfunction internUrlResource(\n  source: UrlViewerSource,\n  descriptor: ViewerDescriptor,\n  keys: ViewerResourceKeys,\n): ViewerResource {\n  const cached = urlViewerResourceRegistry.get(keys.resource);\n  if (cached) return cached;\n\n  const resource = createUrlResource(source, descriptor, keys);\n  urlViewerResourceRegistry.set(keys.resource, resource);\n  pruneUrlResourceRegistry();\n  return resource;\n}\n\nfunction internBlobResource(\n  source: BlobViewerSource,\n  descriptor: ViewerDescriptor,\n  keys: ViewerResourceKeys,\n): ViewerResource {\n  let resources = blobViewerResourceRegistry.get(source.blob);\n  if (!resources) {\n    resources = new Map();\n    blobViewerResourceRegistry.set(source.blob, resources);\n  }\n\n  const cached = resources.get(keys.resource);\n  if (cached) return cached;\n\n  const resource = createBlobResource(source, descriptor, keys);\n  resources.set(keys.resource, resource);\n  return resource;\n}\n\nfunction internTextResource(\n  source: TextSource,\n  descriptor: ViewerDescriptor,\n  keys: ViewerResourceKeys,\n): ViewerResource {\n  const cached = textViewerResourceRegistry.get(keys.resource);\n  if (cached) return cached;\n\n  const resource = createTextResource(source, descriptor, keys);\n  textViewerResourceRegistry.set(keys.resource, resource);\n  pruneTextResourceRegistry();\n  return resource;\n}\n\nfunction pruneUrlResourceRegistry() {\n  while (urlViewerResourceRegistry.size > URL_RESOURCE_REGISTRY_MAX) {\n    const firstKey = urlViewerResourceRegistry.keys().next().value;\n    if (!firstKey) return;\n    urlViewerResourceRegistry.delete(firstKey);\n  }\n}\n\nfunction pruneUrlResourceContentRegistry() {\n  while (urlViewerResourceContentRegistry.size > URL_RESOURCE_REGISTRY_MAX) {\n    const firstKey = urlViewerResourceContentRegistry.keys().next().value;\n    if (!firstKey) return;\n    urlViewerResourceContentRegistry.delete(firstKey);\n  }\n}\n\nfunction pruneTextResourceRegistry() {\n  while (textViewerResourceRegistry.size > TEXT_RESOURCE_REGISTRY_MAX) {\n    const firstKey = textViewerResourceRegistry.keys().next().value;\n    if (!firstKey) return;\n    textViewerResourceRegistry.delete(firstKey);\n  }\n}\n\nfunction pruneTextResourceContentRegistry() {\n  while (textViewerResourceContentRegistry.size > TEXT_RESOURCE_REGISTRY_MAX) {\n    const firstKey = textViewerResourceContentRegistry.keys().next().value;\n    if (!firstKey) return;\n    textViewerResourceContentRegistry.delete(firstKey);\n  }\n}\n\nfunction viewerResourceKeys(\n  source: ViewerSource,\n  descriptor: ViewerDescriptor,\n): ViewerResourceKeys {\n  const load = viewerResourceLoadKey(source, descriptor);\n  const presentation = viewerResourcePresentationKey(source, descriptor);\n  return {\n    load,\n    presentation,\n    resource: [load, presentation].join(\"\\u0000\"),\n  };\n}\n\nfunction viewerResourceLoadKey(\n  source: ViewerSource,\n  descriptor: ViewerDescriptor,\n) {\n  return [\n    source.kind,\n    source.identityKey ?? \"\",\n    sourceMimeType(source) ?? \"\",\n    directLoadCacheKey(source),\n    payloadCacheKey(source, descriptor),\n  ].join(\"\\u0000\");\n}\n\nfunction viewerResourcePresentationKey(\n  source: ViewerSource,\n  descriptor: ViewerDescriptor,\n) {\n  return [\n    descriptor.category,\n    descriptor.displayName,\n    descriptor.fileName,\n    descriptor.mimeType ?? \"\",\n    downloadCacheKey(source),\n  ].join(\"\\u0000\");\n}\n\nfunction directLoadCacheKey(source: ViewerSource) {\n  return source.kind === \"url\" ? source.url : \"\";\n}\n\nfunction downloadCacheKey(source: ViewerSource) {\n  if (source.kind === \"text\") return \"\";\n  return source.downloadUrl ?? \"\";\n}\n\nfunction payloadCacheKey(source: ViewerSource, descriptor: ViewerDescriptor) {\n  if (source.kind === \"url\") return \"\";\n  if (source.kind === \"blob\") return blobObjectKey(source.blob);\n  return source.identityKey ? \"\" : descriptor.identityKey;\n}\n\nexport function viewerResourceRenderKey(resource: ViewerResource): string {\n  const load = [\n    resource.sourceKind,\n    resource.identityKey,\n    resource.mimeType ?? resource.content.mimeType ?? \"\",\n    resource.content.directUrl ?? \"\",\n    viewerContentRenderKey(resource.content),\n  ].join(\"\\u0000\");\n\n  return [load, resource.keys.presentation].join(\"\\u0000\");\n}\n\nexport function viewerContentRenderKey(content: ViewerResourceContent): string {\n  if (content.payload.kind === \"text\")\n    return textPayloadKey(content.payload.text);\n  return content.key;\n}\n\nfunction sourceMimeType(source: ViewerSource) {\n  if (source.kind === \"blob\") return source.mimeType ?? source.blob.type;\n  return source.mimeType;\n}\n\nfunction blobObjectKey(blob: Blob) {\n  let key = blobObjectKeys.get(blob);\n  if (!key) {\n    nextBlobObjectKey += 1;\n    key = `blob-object:${nextBlobObjectKey}`;\n    blobObjectKeys.set(blob, key);\n  }\n  return key;\n}\n\nexport function blobSource(\n  bytes: Blob | ArrayBuffer | Uint8Array,\n  metadata: {\n    identityKey: string;\n    fileName?: string;\n    mimeType?: string;\n    downloadUrl?: string;\n  },\n): BlobViewerSource {\n  const blob =\n    bytes instanceof Blob\n      ? bytes\n      : new Blob(\n          [bytes instanceof ArrayBuffer ? bytes : new Uint8Array(bytes)],\n          {\n            type: metadata.mimeType ?? \"\",\n          },\n        );\n  return {\n    kind: \"blob\",\n    blob,\n    fileName: metadata.fileName,\n    mimeType: metadata.mimeType ?? blob.type,\n    downloadUrl: metadata.downloadUrl,\n    identityKey: metadata.identityKey,\n  };\n}\n\nfunction createUrlResource(\n  source: UrlViewerSource,\n  descriptor: ViewerDescriptor,\n  keys: ViewerResourceKeys,\n): ViewerResource {\n  const content = internUrlResourceContent(source, keys);\n  const originalDownload = createHrefDownloadAction({\n    id: \"download-original\",\n    label: \"Download\",\n    href: source.downloadUrl ?? source.url,\n    fileName: descriptor.fileName,\n  });\n\n  return resourceBase(source, descriptor, keys, { content, originalDownload });\n}\n\nfunction internUrlResourceContent(\n  source: UrlViewerSource,\n  keys: ViewerResourceKeys,\n): ViewerResourceContent {\n  const cached = urlViewerResourceContentRegistry.get(keys.load);\n  if (cached) return cached;\n\n  const content = resourceContentBase(source, keys, {\n    directUrl: source.url,\n    payload: { kind: \"url\", url: source.url },\n    readBlob: async ({ cache, signal } = {}) => {\n      const response = await fetchResource(\n        source.url,\n        cache ? { cache, signal } : { signal },\n      );\n      validateFullContentResponse(response);\n      return readResponseBlob(response);\n    },\n    readBytes: async ({ cache, signal } = {}) => {\n      const response = await fetchResource(\n        source.url,\n        cache ? { cache, signal } : { signal },\n      );\n      validateFullContentResponse(response);\n      return readResponseArrayBuffer(response);\n    },\n    readText: async ({ cache, signal, maxBytes, maxLines } = {}) => {\n      const response = await fetchResource(\n        source.url,\n        cache ? { cache, signal } : { signal },\n      );\n      return readBoundedResponseText(response, { maxBytes, maxLines });\n    },\n    readStream: async ({ cache, signal } = {}) => {\n      const response = await fetchResource(\n        source.url,\n        cache ? { cache, signal } : { signal },\n      );\n      validateFullContentResponse(response);\n      if (!response.body) {\n        if (response.status === 204 || response.status === 205) {\n          return emptyByteStream();\n        }\n        throw new ResourceError({\n          kind: \"unsupported_capability\",\n          message: \"This response cannot be streamed.\",\n        });\n      }\n      return response.body;\n    },\n    readRange: async (range, { cache, signal } = {}) => {\n      validateByteRange(range);\n      const { start, end } = range;\n      const init = {\n        signal,\n        headers: { Range: `bytes=${start}-${end}` },\n      };\n      const response = await fetchResource(\n        source.url,\n        cache ? { ...init, cache } : init,\n      );\n      const buffer = await readResponseArrayBuffer(response);\n      const contentRange = parseContentRange(\n        response.headers.get(\"content-range\"),\n      );\n      validateUrlRangeResponse({\n        bufferLength: buffer.byteLength,\n        contentRange,\n        range,\n        status: response.status,\n      });\n      return {\n        buffer,\n        contentRange,\n        isComplete: isByteRangeComplete({\n          bufferLength: buffer.byteLength,\n          contentRange,\n          requestedLength: end - start + 1,\n          status: response.status,\n        }),\n      };\n    },\n  });\n  urlViewerResourceContentRegistry.set(keys.load, content);\n  pruneUrlResourceContentRegistry();\n  return content;\n}\n\nfunction emptyByteStream() {\n  return new ReadableStream<Uint8Array>({\n    start(controller) {\n      controller.close();\n    },\n  });\n}\n\nfunction createBlobResource(\n  source: BlobViewerSource,\n  descriptor: ViewerDescriptor,\n  keys: ViewerResourceKeys,\n): ViewerResource {\n  const blob = source.blob;\n  const content = internBlobResourceContent(source, keys);\n  const originalDownload = source.downloadUrl\n    ? createHrefDownloadAction({\n        id: \"download-original\",\n        label: \"Download\",\n        href: source.downloadUrl,\n        fileName: descriptor.fileName,\n      })\n    : createBlobDownloadAction({\n        id: \"download-original\",\n        label: \"Download\",\n        blob,\n        fileName: descriptor.fileName,\n      });\n\n  return resourceBase(source, descriptor, keys, { content, originalDownload });\n}\n\nfunction internBlobResourceContent(\n  source: BlobViewerSource,\n  keys: ViewerResourceKeys,\n): ViewerResourceContent {\n  const blob = source.blob;\n  let contents = blobViewerResourceContentRegistry.get(blob);\n  if (!contents) {\n    contents = new Map();\n    blobViewerResourceContentRegistry.set(blob, contents);\n  }\n\n  const cached = contents.get(keys.load);\n  if (cached) return cached;\n\n  const content = resourceContentBase(source, keys, {\n    directUrl: null,\n    payload: { kind: \"blob\", blob },\n    readBlob: async () => blob,\n    readBytes: async () => blob.arrayBuffer(),\n    readText: async ({ maxBytes, maxLines } = {}) =>\n      readBoundedBlobText(blob, { maxBytes, maxLines }),\n    readStream: async () => blob.stream(),\n    readRange: async (range) => {\n      validateByteRange(range);\n      const { start, end } = range;\n      validateKnownByteRangeStart(start, blob.size);\n      const rangeBlob = blob.slice(start, end + 1);\n      return {\n        buffer: await rangeBlob.arrayBuffer(),\n        contentRange: {\n          start,\n          end: Math.min(end, blob.size - 1),\n          total: blob.size,\n        },\n        isComplete: end >= blob.size - 1,\n      };\n    },\n  });\n  contents.set(keys.load, content);\n  return content;\n}\n\nfunction createTextResource(\n  source: TextSource,\n  descriptor: ViewerDescriptor,\n  keys: ViewerResourceKeys,\n): ViewerResource {\n  const content = internTextResourceContent(source, keys);\n  const originalDownload = createTextDownloadAction({\n    id: \"download-original\",\n    label: \"Download\",\n    text: source.text,\n    fileName: descriptor.fileName,\n    mimeType: descriptor.mimeType,\n  });\n\n  return resourceBase(source, descriptor, keys, { content, originalDownload });\n}\n\nfunction internTextResourceContent(\n  source: TextSource,\n  keys: ViewerResourceKeys,\n): ViewerResourceContent {\n  const cached = textViewerResourceContentRegistry.get(keys.load);\n  if (cached) return cached;\n\n  const content = resourceContentBase(source, keys, {\n    directUrl: null,\n    payload: { kind: \"text\", text: source.text },\n    readBlob: async () =>\n      new Blob([source.text], {\n        type: \"text/plain;charset=utf-8\",\n      }),\n    readBytes: async () =>\n      typedArrayBuffer(new TextEncoder().encode(source.text)),\n    readText: async ({ maxBytes, maxLines } = {}) =>\n      readBoundedInlineText(source.text, { maxBytes, maxLines }),\n    readStream: async () => new Blob([source.text]).stream(),\n    readRange: async (range) => {\n      validateByteRange(range);\n      const { start, end } = range;\n      const buffer = new TextEncoder().encode(source.text);\n      validateKnownByteRangeStart(start, buffer.byteLength);\n      const slice = buffer.slice(start, end + 1);\n      return {\n        buffer: typedArrayBuffer(slice),\n        contentRange: {\n          start,\n          end: Math.min(end, buffer.byteLength - 1),\n          total: buffer.byteLength,\n        },\n        isComplete: end >= buffer.byteLength - 1,\n      };\n    },\n  });\n  textViewerResourceContentRegistry.set(keys.load, content);\n  pruneTextResourceContentRegistry();\n  return content;\n}\n\nfunction resourceBase(\n  source: ViewerSource,\n  descriptor: ViewerDescriptor,\n  keys: ViewerResourceKeys,\n  options: {\n    content: ViewerResourceContent;\n    originalDownload: ViewerDownloadAction;\n  },\n): ViewerResource {\n  const { content, originalDownload } = options;\n  return Object.freeze({\n    descriptor,\n    sourceKind: source.kind,\n    keys,\n    identityKey: descriptor.identityKey,\n    fileName: descriptor.fileName,\n    mimeType: descriptor.mimeType,\n    content,\n    originalDownload,\n  });\n}\n\nfunction resourceContentBase(\n  source: ViewerSource,\n  keys: ViewerResourceKeys,\n  methods: Omit<ViewerResourceContent, \"key\" | \"sourceKind\" | \"mimeType\">,\n): ViewerResourceContent {\n  return Object.freeze({\n    key: keys.load,\n    sourceKind: source.kind,\n    mimeType: sourceMimeType(source),\n    ...methods,\n  });\n}\n\nfunction typedArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n  return bytes.buffer.slice(\n    bytes.byteOffset,\n    bytes.byteOffset + bytes.byteLength,\n  ) as ArrayBuffer;\n}\n\nasync function fetchResource(\n  input: RequestInfo | URL,\n  init?: RequestInit,\n): Promise<Response> {\n  let response: Response;\n  try {\n    response = await fetch(input, init);\n  } catch (error) {\n    if (isAbortError(error)) {\n      throw new ResourceError({\n        kind: \"aborted\",\n        message: \"Loading was cancelled.\",\n        cause: error,\n      });\n    }\n    throw new ResourceError({\n      kind: \"fetch_failed\",\n      message: \"Could not fetch this resource.\",\n      cause: error,\n    });\n  }\n\n  if (!response.ok && response.status !== 206) {\n    throw new ResourceError({\n      kind: \"http_error\",\n      message: `Failed to load resource: ${response.status}`,\n      status: response.status,\n    });\n  }\n\n  return response;\n}\n\nasync function readBoundedResponseText(\n  response: Response,\n  bounds: { maxBytes?: number; maxLines?: number },\n) {\n  validateFullContentResponse(response);\n\n  const maxBytes = bounds.maxBytes;\n  if (\n    isContentLengthOverLimit(response.headers.get(\"content-length\"), maxBytes)\n  ) {\n    throw tooLarge(\"bytes\");\n  }\n\n  const body = response.body;\n  if (!body) {\n    const buffer = await readResponseArrayBuffer(response);\n    if (maxBytes != null && buffer.byteLength > maxBytes) {\n      throw tooLarge(\"bytes\");\n    }\n    const text = new TextDecoder().decode(buffer);\n    assertLineLimit(text, bounds.maxLines);\n    return text;\n  }\n\n  const reader = body.getReader();\n  const decoder = new TextDecoder();\n  const lineLimitTracker = createLineLimitTracker(bounds.maxLines);\n  let receivedBytes = 0;\n  let text = \"\";\n\n  while (true) {\n    const { done, value } = await readResponseStreamChunk(reader);\n    if (done) break;\n    receivedBytes += value.byteLength;\n    if (maxBytes != null && receivedBytes > maxBytes) {\n      await cancelReaderSilently(reader);\n      throw tooLarge(\"bytes\");\n    }\n    const chunkText = decoder.decode(value, { stream: true });\n    try {\n      lineLimitTracker.push(chunkText);\n    } catch (error) {\n      await cancelReaderSilently(reader);\n      throw error;\n    }\n    text += chunkText;\n  }\n\n  const finalText = decoder.decode();\n  lineLimitTracker.push(finalText);\n  text += finalText;\n  return text;\n}\n\nfunction isContentLengthOverLimit(\n  contentLength: string | null,\n  maxBytes: number | undefined,\n) {\n  if (maxBytes == null || contentLength == null) return false;\n\n  const normalizedLength = contentLength.trim().replace(/^0+(?=\\d)/, \"\");\n  if (!/^\\d+$/.test(normalizedLength)) return false;\n\n  const maxLength = String(maxBytes);\n  return (\n    normalizedLength.length > maxLength.length ||\n    (normalizedLength.length === maxLength.length &&\n      normalizedLength > maxLength)\n  );\n}\n\nfunction validateFullContentResponse(response: Response) {\n  if (response.status !== 206) return;\n\n  const contentRange = parseContentRange(response.headers.get(\"content-range\"));\n  if (\n    contentRange?.total != null &&\n    contentRange.start === 0 &&\n    contentRange.end === contentRange.total - 1\n  ) {\n    return;\n  }\n\n  throw new ResourceError({\n    kind: \"partial_content\",\n    message: \"Full response returned partial content.\",\n    status: response.status,\n  });\n}\n\nasync function readResponseStreamChunk(\n  reader: ReadableStreamDefaultReader<Uint8Array>,\n) {\n  try {\n    return await reader.read();\n  } catch (error) {\n    throw resourceReadError(error);\n  }\n}\n\nasync function readResponseArrayBuffer(response: Response) {\n  try {\n    return await response.arrayBuffer();\n  } catch (error) {\n    throw resourceReadError(error);\n  }\n}\n\nasync function readResponseBlob(response: Response) {\n  try {\n    return await response.blob();\n  } catch (error) {\n    throw resourceReadError(error);\n  }\n}\n\nfunction resourceReadError(error: unknown) {\n  if (isAbortError(error)) {\n    return new ResourceError({\n      kind: \"aborted\",\n      message: \"Loading was cancelled.\",\n      cause: error,\n    });\n  }\n  return new ResourceError({\n    kind: \"fetch_failed\",\n    message: \"Could not read this resource.\",\n    cause: error,\n  });\n}\n\nasync function readBoundedBlobText(\n  blob: Blob,\n  bounds: { maxBytes?: number; maxLines?: number },\n) {\n  if (bounds.maxBytes != null && blob.size > bounds.maxBytes) {\n    throw tooLarge(\"bytes\");\n  }\n  const text = await blob.text();\n  assertLineLimit(text, bounds.maxLines);\n  return text;\n}\n\nfunction readBoundedInlineText(\n  text: string,\n  { maxBytes, maxLines }: { maxBytes?: number; maxLines?: number },\n) {\n  // For inline sources the string *is* the resource, so its UTF-8 byte length\n  // is the authoritative size to measure against maxBytes.\n  if (\n    maxBytes != null &&\n    new TextEncoder().encode(text).byteLength > maxBytes\n  ) {\n    throw tooLarge(\"bytes\");\n  }\n  assertLineLimit(text, maxLines);\n  return text;\n}\n\n// Used after a transferred-byte check has already enforced maxBytes (URL/blob).\n// Re-encoding the decoded text here would double-count: invalid UTF-8 decodes to\n// U+FFFD (3 bytes each), inflating the measured size past the real wire bytes\n// and falsely rejecting small resources as \"too large\".\nfunction assertLineLimit(text: string, maxLines: number | undefined) {\n  if (\n    maxLines != null &&\n    text.split(TEXT_LINE_BREAK_PATTERN).length > maxLines\n  ) {\n    throw tooLarge(\"lines\");\n  }\n}\n\nfunction tooLarge(reason: ResourceTooLargeReason) {\n  return new ResourceError({\n    kind: \"too_large\",\n    tooLargeReason: reason,\n    message: `Resource exceeds ${reason} limit.`,\n  });\n}\n\nasync function cancelReaderSilently(\n  reader: ReadableStreamDefaultReader<Uint8Array>,\n) {\n  try {\n    await reader.cancel();\n  } catch {\n    // Preserve the user-facing load failure; cancellation is best-effort cleanup.\n  }\n}\n\nfunction validateByteRange({ start, end }: ByteRange) {\n  if (\n    !Number.isSafeInteger(start) ||\n    !Number.isSafeInteger(end) ||\n    start < 0 ||\n    end < start\n  ) {\n    throw new ResourceError({\n      kind: \"invalid_range\",\n      message: \"Byte range must use non-negative integer bounds.\",\n    });\n  }\n}\n\nfunction validateKnownByteRangeStart(start: number, total: number) {\n  if (start > 0 && start >= total) {\n    throw new ResourceError({\n      kind: \"invalid_range\",\n      message: \"Byte range starts past the available resource.\",\n    });\n  }\n}\n\nfunction validateUrlRangeResponse({\n  bufferLength,\n  contentRange,\n  range,\n  status,\n}: {\n  bufferLength: number;\n  contentRange: ByteRangeResult[\"contentRange\"];\n  range: ByteRange;\n  status: number;\n}) {\n  if (status === 200) {\n    if (range.start !== 0 || bufferLength > range.end - range.start + 1) {\n      throw new ResourceError({\n        kind: \"invalid_range\",\n        message: \"Full response does not match the requested byte range.\",\n      });\n    }\n    return;\n  }\n  if (status !== 206) {\n    throw new ResourceError({\n      kind: \"invalid_range\",\n      message: \"Range response must return full or partial content.\",\n    });\n  }\n  if (!contentRange) {\n    throw new ResourceError({\n      kind: \"invalid_range\",\n      message: \"Partial content response is missing a valid byte range.\",\n    });\n  }\n  const declaredLength = contentRange.end - contentRange.start + 1;\n  if (\n    contentRange.start !== range.start ||\n    contentRange.end < contentRange.start ||\n    contentRange.end > range.end ||\n    (contentRange.total != null && contentRange.end >= contentRange.total) ||\n    declaredLength !== bufferLength\n  ) {\n    throw new ResourceError({\n      kind: \"invalid_range\",\n      message: \"Response byte range does not match the requested range.\",\n    });\n  }\n}\n\nfunction isByteRangeComplete({\n  bufferLength,\n  contentRange,\n  requestedLength,\n  status,\n}: {\n  bufferLength: number;\n  contentRange: ByteRangeResult[\"contentRange\"];\n  requestedLength: number;\n  status: number;\n}) {\n  if (status === 200) return true;\n  if (contentRange?.total != null) {\n    if (contentRange.total <= 0) return true;\n    return contentRange.end >= contentRange.total - 1;\n  }\n  if (contentRange) return false;\n  return bufferLength < requestedLength;\n}\n\nfunction isStandaloneLineBreak(character: string) {\n  const code = character.charCodeAt(0);\n  return code === 0x0a || code === 0x2028 || code === 0x2029;\n}\n\nfunction createLineLimitTracker(maxLines: number | undefined) {\n  let lineCount = 1;\n  let previousWasCR = false;\n\n  return {\n    push(text: string) {\n      if (maxLines == null || text.length === 0) return;\n\n      for (const character of text) {\n        if (previousWasCR) {\n          previousWasCR = false;\n          if (character === \"\\n\") continue;\n        }\n\n        if (character === \"\\r\") {\n          lineCount += 1;\n          previousWasCR = true;\n        } else if (isStandaloneLineBreak(character)) {\n          // LF, plus LINE/PARAGRAPH SEPARATOR (U+2028/U+2029); none pair with CR.\n          lineCount += 1;\n        }\n\n        if (lineCount > maxLines) {\n          throw tooLarge(\"lines\");\n        }\n      }\n    },\n  };\n}\n\nfunction parseContentRange(value: string | null) {\n  if (!value) return undefined;\n  const match = value.match(/^bytes\\s+(\\d+)-(\\d+)\\/(\\d+|\\*)\\s*$/i);\n  if (!match) return undefined;\n  const start = parseContentRangeNumber(match[1]);\n  const end = parseContentRangeNumber(match[2]);\n  const total =\n    match[3] === \"*\" ? null : parseContentRangeNumber(match[3] ?? \"\");\n  if (start == null || end == null || total === undefined) return undefined;\n  return {\n    start,\n    end,\n    total,\n  };\n}\n\nfunction parseContentRangeNumber(value: string) {\n  const number = Number(value);\n  return Number.isSafeInteger(number) ? number : undefined;\n}\n",
      "type": "registry:lib",
      "target": "@lib/viewer-resource.ts"
    },
    {
      "path": "registry/new-york-v4/lib/viewer-download-actions.ts",
      "content": "export type ViewerDownloadOrigin = \"original\" | \"derived\";\n\nexport type ViewerDownloadPayload =\n  | { kind: \"href\"; href: string }\n  | { kind: \"blob\"; blob: Blob }\n  | { kind: \"text\"; text: string; mimeType?: string }\n  | { kind: \"none\" };\n\nexport interface ViewerDownloadAction {\n  id: string;\n  label: string;\n  fileName: string;\n  origin: ViewerDownloadOrigin;\n  isDisabled?: boolean;\n  getPayload: (options?: {\n    signal?: AbortSignal;\n  }) => ViewerDownloadPayload | Promise<ViewerDownloadPayload>;\n}\n\nexport type ViewerDownloadErrorKind =\n  | \"disabled\"\n  | \"aborted\"\n  | \"payload_failed\"\n  | \"unsupported\";\n\nexport class ViewerDownloadError extends Error {\n  readonly kind: ViewerDownloadErrorKind;\n  readonly actionId: string;\n  override readonly cause?: unknown;\n\n  constructor({\n    actionId,\n    kind,\n    message,\n    cause,\n  }: {\n    actionId: string;\n    kind: ViewerDownloadErrorKind;\n    message: string;\n    cause?: unknown;\n  }) {\n    super(message);\n    this.name = \"ViewerDownloadError\";\n    this.actionId = actionId;\n    this.kind = kind;\n    this.cause = cause;\n  }\n}\n\nexport function createHrefDownloadAction({\n  id,\n  label = \"Download\",\n  href,\n  fileName,\n  origin = \"original\",\n}: {\n  id: string;\n  label?: string;\n  href: string;\n  fileName: string;\n  origin?: ViewerDownloadOrigin;\n}): ViewerDownloadAction {\n  return {\n    id,\n    label,\n    fileName,\n    origin,\n    getPayload: () => ({ kind: \"href\", href }),\n  };\n}\n\nexport function createBlobDownloadAction({\n  id,\n  label = \"Download\",\n  blob,\n  fileName,\n  origin = \"original\",\n}: {\n  id: string;\n  label?: string;\n  blob: Blob;\n  fileName: string;\n  origin?: ViewerDownloadOrigin;\n}): ViewerDownloadAction {\n  return {\n    id,\n    label,\n    fileName,\n    origin,\n    getPayload: () => ({ kind: \"blob\", blob }),\n  };\n}\n\nexport function createTextDownloadAction({\n  id,\n  label = \"Download\",\n  text,\n  fileName,\n  mimeType,\n  origin = \"original\",\n}: {\n  id: string;\n  label?: string;\n  text: string;\n  fileName: string;\n  mimeType?: string;\n  origin?: ViewerDownloadOrigin;\n}): ViewerDownloadAction {\n  return {\n    id,\n    label,\n    fileName,\n    origin,\n    getPayload: () => ({ kind: \"text\", text, mimeType }),\n  };\n}\n\nexport function createDisabledDownloadAction({\n  id,\n  label = \"Download\",\n  fileName,\n  origin = \"original\",\n}: {\n  id: string;\n  label?: string;\n  fileName: string;\n  origin?: ViewerDownloadOrigin;\n}): ViewerDownloadAction {\n  return {\n    id,\n    label,\n    fileName,\n    origin,\n    isDisabled: true,\n    getPayload: () => ({ kind: \"none\" }),\n  };\n}\n",
      "type": "registry:lib",
      "target": "@lib/viewer-download-actions.ts"
    },
    {
      "path": "registry/new-york-v4/lib/viewer-errors.ts",
      "content": "export type ViewerFormat =\n  | \"pdf\"\n  | \"image\"\n  | \"text\"\n  | \"csv\"\n  | \"docx\"\n  | \"xlsx\"\n  | \"pptx\"\n  | \"file\";\n\nexport type ViewerErrorDomain =\n  | \"resource\"\n  | \"format\"\n  | \"state\"\n  | \"unsupported\"\n  | \"unknown\";\n\nexport type ResourceErrorKind =\n  | \"fetch_failed\"\n  | \"http_error\"\n  | \"aborted\"\n  | \"invalid_range\"\n  | \"partial_content\"\n  | \"too_large\"\n  | \"unsupported_capability\"\n  | \"unknown\";\n\nexport type ResourceTooLargeReason = \"bytes\" | \"lines\";\n\nexport class ResourceError extends Error {\n  readonly domain = \"resource\";\n  readonly kind: ResourceErrorKind;\n  readonly status?: number;\n  readonly tooLargeReason?: ResourceTooLargeReason;\n  override readonly cause?: unknown;\n\n  constructor({\n    kind,\n    message,\n    status,\n    tooLargeReason,\n    cause,\n  }: {\n    kind: ResourceErrorKind;\n    message: string;\n    status?: number;\n    tooLargeReason?: ResourceTooLargeReason;\n    cause?: unknown;\n  }) {\n    super(message);\n    this.name = \"ResourceError\";\n    this.kind = kind;\n    this.status = status;\n    this.tooLargeReason = tooLargeReason;\n    this.cause = cause;\n  }\n}\n\nexport type ViewerFormatErrorKind =\n  | \"bounds\"\n  | \"decode_failed\"\n  | \"disposed\"\n  | \"index_out_of_range\"\n  | \"load_failed\"\n  | \"parse_failed\"\n  | \"render_failed\"\n  | \"worker_failed\"\n  | \"unknown\";\n\nexport interface ViewerFormatErrorMapperOptions {\n  kind: ViewerFormatErrorKind;\n  message: string;\n}\n\nexport class ViewerFormatError extends Error {\n  readonly domain = \"format\";\n  readonly format: ViewerFormat;\n  readonly kind: ViewerFormatErrorKind;\n  override readonly cause?: unknown;\n\n  constructor({\n    format,\n    kind,\n    message,\n    cause,\n  }: {\n    format: ViewerFormat;\n    kind: ViewerFormatErrorKind;\n    message: string;\n    cause?: unknown;\n  }) {\n    super(message);\n    this.name = \"ViewerFormatError\";\n    this.format = format;\n    this.kind = kind;\n    this.cause = cause;\n  }\n}\n\nexport type ViewerStateErrorKind =\n  | \"invalid_bounds\"\n  | \"invalid_target\"\n  | \"out_of_range\"\n  | \"stale_resource\"\n  | \"unknown\";\n\nexport class ViewerStateError extends Error {\n  readonly domain = \"state\";\n  readonly format?: ViewerFormat;\n  readonly kind: ViewerStateErrorKind;\n  override readonly cause?: unknown;\n\n  constructor({\n    format,\n    kind,\n    message,\n    cause,\n  }: {\n    format?: ViewerFormat;\n    kind: ViewerStateErrorKind;\n    message: string;\n    cause?: unknown;\n  }) {\n    super(message);\n    this.name = \"ViewerStateError\";\n    this.format = format;\n    this.kind = kind;\n    this.cause = cause;\n  }\n}\n\nexport class ViewerUnsupportedError extends Error {\n  readonly domain = \"unsupported\";\n  readonly format?: ViewerFormat;\n  readonly sourceKind?: string;\n  override readonly cause?: unknown;\n\n  constructor({\n    format,\n    sourceKind,\n    message,\n    cause,\n  }: {\n    format?: ViewerFormat;\n    sourceKind?: string;\n    message: string;\n    cause?: unknown;\n  }) {\n    super(message);\n    this.name = \"ViewerUnsupportedError\";\n    this.format = format;\n    this.sourceKind = sourceKind;\n    this.cause = cause;\n  }\n}\n\nexport interface ViewerErrorInfo {\n  domain: ViewerErrorDomain;\n  format?: ViewerFormat;\n  kind: string;\n  message: string;\n  status?: number;\n  isRetryable: boolean;\n  isDownloadUseful: boolean;\n  userMessage: string;\n  cause?: unknown;\n}\n\nexport interface ViewerErrorContext {\n  format?: ViewerFormat;\n  sourceKind?: \"url\" | \"blob\" | \"text\";\n  canDownload?: boolean;\n  retry?: \"auto\" | \"always\" | \"never\";\n}\n\nexport function isAbortError(error: unknown): boolean {\n  return (\n    (error instanceof DOMException && error.name === \"AbortError\") ||\n    (error instanceof Error && error.name === \"AbortError\")\n  );\n}\n\nexport function isResourceError(error: unknown): error is ResourceError {\n  return (\n    error instanceof ResourceError ||\n    isErrorLike(error, \"ResourceError\", \"resource\")\n  );\n}\n\nexport function isViewerFormatError(\n  error: unknown,\n): error is ViewerFormatError {\n  return (\n    error instanceof ViewerFormatError ||\n    isErrorLike(error, \"ViewerFormatError\", \"format\")\n  );\n}\n\nexport function isViewerStateError(error: unknown): error is ViewerStateError {\n  return (\n    error instanceof ViewerStateError ||\n    isErrorLike(error, \"ViewerStateError\", \"state\")\n  );\n}\n\nexport function isViewerUnsupportedError(\n  error: unknown,\n): error is ViewerUnsupportedError {\n  return (\n    error instanceof ViewerUnsupportedError ||\n    isErrorLike(error, \"ViewerUnsupportedError\", \"unsupported\")\n  );\n}\n\nexport function toViewerErrorInfo(\n  error: unknown,\n  context: ViewerErrorContext = {},\n): ViewerErrorInfo {\n  const canDownload = context.canDownload ?? true;\n\n  if (isResourceError(error)) {\n    return {\n      domain: \"resource\",\n      format: context.format,\n      kind: error.kind,\n      message: error.message,\n      status: error.status,\n      isRetryable: retryable(\n        context,\n        resourceErrorDefaultRetry(error, context),\n      ),\n      isDownloadUseful: canDownload && error.kind !== \"aborted\",\n      userMessage: resourceErrorUserMessage(error),\n      cause: error.cause,\n    };\n  }\n\n  if (isViewerFormatError(error)) {\n    const format = error.format ?? context.format;\n    return {\n      domain: \"format\",\n      format,\n      kind: error.kind,\n      message: error.message,\n      isRetryable: retryable(\n        context,\n        formatErrorDefaultRetry(error, context, format),\n      ),\n      isDownloadUseful: canDownload,\n      userMessage: formatErrorUserMessage(format, error.kind, error),\n      cause: error.cause,\n    };\n  }\n\n  if (isViewerStateError(error)) {\n    return {\n      domain: \"state\",\n      format: error.format ?? context.format,\n      kind: error.kind,\n      message: error.message,\n      isRetryable: retryable(context, false),\n      isDownloadUseful: canDownload,\n      userMessage: stateErrorUserMessage(error.kind),\n      cause: error.cause,\n    };\n  }\n\n  if (isViewerUnsupportedError(error)) {\n    return {\n      domain: \"unsupported\",\n      format: error.format ?? context.format,\n      kind: \"unsupported\",\n      message: error.message,\n      isRetryable: retryable(context, false),\n      isDownloadUseful: canDownload,\n      userMessage: \"This file cannot be previewed here.\",\n      cause: error.cause,\n    };\n  }\n\n  if (isAbortError(error)) {\n    return {\n      domain: \"resource\",\n      format: context.format,\n      kind: \"aborted\",\n      message: \"Loading was cancelled.\",\n      isRetryable: retryable(context, false),\n      isDownloadUseful: false,\n      userMessage: \"Loading was cancelled.\",\n      cause: error,\n    };\n  }\n\n  const message = error instanceof Error ? error.message : String(error);\n  return {\n    domain: \"unknown\",\n    format: context.format,\n    kind: \"unknown\",\n    message,\n    isRetryable: retryable(context, unknownErrorDefaultRetry(context)),\n    isDownloadUseful: canDownload,\n    userMessage: fallbackUserMessage(context.format),\n    cause: error,\n  };\n}\n\nfunction isErrorLike(error: unknown, name: string, domain: ViewerErrorDomain) {\n  if (!error || typeof error !== \"object\") return false;\n  const candidate = error as {\n    name?: unknown;\n    domain?: unknown;\n    kind?: unknown;\n  };\n  return (\n    (candidate.name === name || candidate.domain === domain) &&\n    typeof candidate.kind === \"string\"\n  );\n}\n\nfunction retryable(context: ViewerErrorContext, fallback: boolean) {\n  if (context.retry === \"always\") return true;\n  if (context.retry === \"never\") return false;\n  return fallback;\n}\n\nfunction resourceErrorDefaultRetry(\n  error: ResourceError,\n  context: ViewerErrorContext,\n) {\n  if (error.kind === \"aborted\") return false;\n  if (error.kind === \"invalid_range\") return false;\n  if (error.kind === \"too_large\") return false;\n  if (error.kind === \"unsupported_capability\") return false;\n  return context.sourceKind === \"url\";\n}\n\nfunction formatErrorDefaultRetry(\n  error: ViewerFormatError,\n  context: ViewerErrorContext,\n  format: ViewerFormat | undefined,\n) {\n  if (format === \"text\" && error.kind === \"bounds\") return false;\n  if (error.kind === \"disposed\") return false;\n  if (error.kind === \"index_out_of_range\") return false;\n  if (format === \"docx\") return true;\n  return context.sourceKind === \"url\";\n}\n\nfunction unknownErrorDefaultRetry(context: ViewerErrorContext) {\n  if (context.format === \"docx\") return true;\n  return context.sourceKind === \"url\";\n}\n\nfunction resourceErrorUserMessage(error: ResourceError) {\n  if (error.kind === \"http_error\") {\n    return error.status\n      ? `Failed to load file: ${error.status}.`\n      : \"Couldn't load this file.\";\n  }\n  if (error.kind === \"fetch_failed\") return \"Couldn't load this file.\";\n  if (error.kind === \"aborted\") return \"Loading was cancelled.\";\n  if (error.kind === \"invalid_range\") return \"This source range is invalid.\";\n  if (error.kind === \"too_large\") {\n    return error.tooLargeReason === \"lines\"\n      ? \"This file has too many lines to preview.\"\n      : \"This file is too large to preview.\";\n  }\n  if (error.kind === \"partial_content\") {\n    return \"This source returned partial content and cannot be previewed here.\";\n  }\n  if (error.kind === \"unsupported_capability\") {\n    return \"This source cannot be previewed here.\";\n  }\n  return \"Couldn't load this file.\";\n}\n\nfunction formatErrorUserMessage(\n  format: ViewerFormat | undefined,\n  kind: string,\n  error?: unknown,\n) {\n  if (format === \"pdf\") return \"Couldn't load this PDF.\";\n  if (format === \"image\") {\n    if (kind === \"index_out_of_range\")\n      return \"This image page is out of range.\";\n    if (kind === \"decode_failed\") return \"Couldn't decode this image.\";\n    return \"Couldn't load this image.\";\n  }\n  if (format === \"text\") {\n    if (kind === \"render_failed\") return \"Couldn't render this text file.\";\n    if (kind === \"bounds\") {\n      const boundsError = error as {\n        reason?: unknown;\n        boundName?: unknown;\n      };\n      if (boundsError.reason === \"lines\") {\n        return \"This text file has too many lines to preview.\";\n      }\n      if (boundsError.reason === \"bytes\") {\n        return \"This text file is too large to preview.\";\n      }\n      if (typeof boundsError.boundName === \"string\") {\n        return \"Text viewer bounds are invalid.\";\n      }\n    }\n    return \"Couldn't load this text file.\";\n  }\n  if (format === \"csv\") return \"Couldn't parse this table.\";\n  if (format === \"docx\") return \"Couldn't render this document.\";\n  if (format === \"xlsx\") return \"Couldn't parse this spreadsheet.\";\n  if (format === \"pptx\") {\n    if (kind === \"render_failed\") return \"Couldn't render this slide.\";\n    return \"Couldn't load this presentation.\";\n  }\n  return \"Couldn't load this file.\";\n}\n\nfunction stateErrorUserMessage(kind: ViewerStateErrorKind) {\n  if (kind === \"invalid_bounds\") return \"Viewer bounds are invalid.\";\n  if (kind === \"invalid_target\") return \"The requested target is invalid.\";\n  if (kind === \"out_of_range\") return \"The requested item is out of range.\";\n  if (kind === \"stale_resource\") return \"This viewer state is no longer valid.\";\n  return \"Couldn't load this file.\";\n}\n\nfunction fallbackUserMessage(format: ViewerFormat | undefined) {\n  if (format === \"pdf\") return \"Couldn't load this PDF.\";\n  if (format === \"image\") return \"Couldn't load this image.\";\n  if (format === \"text\") return \"Couldn't load this text file.\";\n  if (format === \"csv\") return \"Couldn't parse this table.\";\n  if (format === \"docx\") return \"Couldn't load this document.\";\n  if (format === \"xlsx\") return \"Couldn't load this spreadsheet.\";\n  if (format === \"pptx\") return \"Couldn't load this presentation.\";\n  return \"Couldn't load this file.\";\n}\n",
      "type": "registry:lib",
      "target": "@lib/viewer-errors.ts"
    },
    {
      "path": "registry/new-york-v4/ui/use-is-client.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nconst emptySubscribe = () => () => {};\nconst getClientSnapshot = () => true;\nconst getServerSnapshot = () => false;\n\n/**\n * SSR gate: `false` on the server (and during hydration's first pass),\n * `true` on the client.\n *\n * This must stay a synchronous external-store read, NOT the\n * `useState(false)` + mount-effect flip. The flip pattern makes every\n * viewer mount its Suspense boundary in a later update; when two such\n * boundaries suspend on pending resources in the same flush as other\n * commit-phase updates (viewer sidebar/geometry registration), React 19's\n * retry lanes desynchronize and re-attempt each other's boundary on every\n * commit — an unbounded synchronous suspend/retry loop that starves the\n * event loop (jsdom tests OOM; browsers busy-spin until the resource\n * resolves). With the store read, client renders suspend on mount, which\n * never enters that loop. Regression-guarded in\n * tests/pdf-viewer-thumbnails.test.tsx (\"shares one document resource…\").\n */\nexport function useIsClient(): boolean {\n  return React.useSyncExternalStore(\n    emptySubscribe,\n    getClientSnapshot,\n    getServerSnapshot,\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/use-is-client.ts"
    },
    {
      "path": "registry/new-york-v4/ui/markdown-greenfield-content.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useMountEffect } from \"@/hooks/use-mount-effect\";\n\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\n\nimport {\n  createMarkdownGreenfieldDocument,\n  findMarkdownGreenfieldChunkByBlockId,\n  findMarkdownGreenfieldChunkBySourceLine,\n  findMarkdownGreenfieldFragmentTargetById,\n  type MarkdownGreenfieldChunk,\n} from \"./markdown-greenfield-document\";\nimport { useMarkdownGreenfieldDocument } from \"./markdown-greenfield-document-store\";\nimport {\n  layoutMarkdownGreenfieldDocument,\n  MARKDOWN_GREENFIELD_CHUNK_MAX_INLINE_SIZE,\n  MARKDOWN_GREENFIELD_LAYOUT_POLICY_VERSION,\n  type MarkdownGreenfieldChunkFrame,\n  type MarkdownGreenfieldMeasurementContext,\n} from \"./markdown-greenfield-layout\";\nimport { useMarkdownGreenfieldRendererFrame } from \"./markdown-greenfield-renderer-frame\";\nimport { MarkdownGreenfieldChunkRenderer } from \"./markdown-greenfield-renderer\";\nimport {\n  createMarkdownGreenfieldVisibleProjection,\n  getMarkdownGreenfieldProjectedVisibleFrames,\n  getMarkdownGreenfieldScrollAnchor,\n  getMarkdownGreenfieldScrollTopForLineRange,\n  getMarkdownGreenfieldVisibleProjection,\n  getMarkdownGreenfieldVisibleRange,\n  isMarkdownGreenfieldVisibleProjectionSameWindow,\n  resolveMarkdownGreenfieldScrollAnchor,\n  type MarkdownGreenfieldScrollAnchor,\n  type MarkdownGreenfieldVisibleProjection,\n} from \"./markdown-greenfield-virtualizer\";\nimport { ScrollArea } from \"./scroll-area\";\nimport { TextViewerControls, TextViewerFrame } from \"./text-viewer-chrome\";\nimport { normalizeTextLineRange } from \"./text-viewer-ranges\";\nimport {\n  readTextResource,\n  resolvedTextViewerBounds,\n} from \"./text-viewer-resource\";\nimport { clampTextViewerScale } from \"./text-viewer-scale\";\nimport { useTextViewerScrollInteractions } from \"./text-viewer-scroll-interactions\";\nimport type { TextViewerHandle, TextViewerProps } from \"./text-viewer-types\";\nimport { getTextInverseStickyWindow } from \"./text-viewer-virtualization\";\nimport type { ViewerDownloadErrorHandler } from \"./viewer-download\";\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\nimport { useKeyedLayoutEffect } from \"@/hooks/use-keyed-layout-effect\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nconst DEFAULT_VIEWPORT_HEIGHT = 640;\nconst DEFAULT_VIEWPORT_WIDTH = 900;\nconst INITIAL_CONTENT_WIDTH = 820;\nconst VIEWER_HORIZONTAL_PADDING = 32;\nconst OVERSCAN_PX = 800;\nconst NATIVE_FIND_TEXT_BATCH_CHUNKS = 16;\nconst NATIVE_FIND_TEXT_IDLE_MIN_REMAINING_MS = 4;\nconst MARKDOWN_GREENFIELD_HIGHLIGHT_STYLE = {\n  backgroundColor:\n    \"color-mix(in oklab, var(--foreground) 8%, var(--background))\",\n  boxShadow: \"inset 2px 0 0 0 var(--primary)\",\n} satisfies React.CSSProperties;\n\ntype MarkdownIdleWindow = Window &\n  typeof globalThis & {\n    cancelIdleCallback?: Window[\"cancelIdleCallback\"];\n    requestIdleCallback?: Window[\"requestIdleCallback\"];\n  };\n\ntype ViewportSize = {\n  height: number;\n  width: number;\n};\n\ntype MarkdownScrollToLineOptions = ScrollToOptions & {\n  preferredChunkId?: string | null;\n};\n\ntype NativeFindTextRecord = {\n  chunk: MarkdownGreenfieldChunk;\n  text: string;\n};\n\ntype NativeFindTextState = {\n  entries: readonly NativeFindTextRecord[];\n  key: string | null;\n};\n\nexport type MarkdownViewerProps = TextViewerProps & {\n  /** Bind document fragment IDs to window.location.hash. Disable for embedded previews. */\n  urlFragmentNavigation?: boolean;\n};\n\nexport type MarkdownGreenfieldContentProps = Omit<\n  MarkdownViewerProps,\n  \"source\"\n> & {\n  resource: ViewerResource;\n  retryVersion: number;\n  forwardedRef?: React.ForwardedRef<TextViewerHandle>;\n};\n\nexport function MarkdownGreenfieldContent({\n  resource,\n  className,\n  controls = true,\n  download = true,\n  highlight,\n  bare = false,\n  maxBytes,\n  maxLines,\n  retryVersion,\n  forwardedRef,\n  urlFragmentNavigation = true,\n}: MarkdownGreenfieldContentProps) {\n  const bounds = React.useMemo(\n    () => resolvedTextViewerBounds({ maxBytes, maxLines }),\n    [maxBytes, maxLines],\n  );\n  const text = React.useMemo(\n    () =>\n      readTextResource({\n        bounds,\n        content: resource.content,\n        retryVersion,\n      }),\n    [bounds, resource.content, retryVersion],\n  );\n  const parsedDocument = useMarkdownGreenfieldDocument(text);\n  const pendingDocument = React.useMemo(\n    () => createMarkdownGreenfieldDocument(\" \"),\n    [],\n  );\n  const document = parsedDocument ?? pendingDocument;\n  const isDocumentPending = parsedDocument == null;\n  const documentMeasurementId = React.useMemo(\n    () => measurementDocumentIdForText(text),\n    [text],\n  );\n  const downloadAction = download ? resource.originalDownload : null;\n  const [downloadError, setDownloadError] = React.useState(\"\");\n  const [fontScale, setFontScale] = React.useState(1);\n  const [measuredHeightsRevision, setMeasuredHeightsRevision] =\n    React.useState(0);\n  const [viewportSize, setViewportSize] = React.useState<ViewportSize>({\n    height: 0,\n    width: 0,\n  });\n  const [contentWidth, setContentWidth] = React.useState(INITIAL_CONTENT_WIDTH);\n  const documentRef = React.useRef(document);\n  const measuredHeightsRef = React.useRef(new Map<string, number>());\n  const viewportRef = React.useRef<HTMLDivElement | null>(null);\n  const stickyWindowRef = React.useRef<HTMLDivElement | null>(null);\n  const scrollTopRef = React.useRef(0);\n  const scrollFrameRef = React.useRef<number | null>(null);\n  const frameChunksRef = React.useRef<readonly MarkdownGreenfieldChunkFrame[]>(\n    [],\n  );\n  const viewportHeightRef = React.useRef(DEFAULT_VIEWPORT_HEIGHT);\n  const pendingMeasuredHeightsRef = React.useRef(new Map<string, number>());\n  const measuredHeightFrameRef = React.useRef<number | null>(null);\n  const pendingAnchorRef = React.useRef<MarkdownGreenfieldScrollAnchor | null>(\n    null,\n  );\n  const prevFrameChunksRef = React.useRef<\n    readonly MarkdownGreenfieldChunkFrame[] | null\n  >(null);\n  const viewportHeight = viewportSize.height || DEFAULT_VIEWPORT_HEIGHT;\n  const measuredViewportWidth = viewportSize.width || DEFAULT_VIEWPORT_WIDTH;\n  const captureAnchor = React.useCallback(() => {\n    // Read the live scroll position from the DOM rather than the `scrollTop`\n    // state, which lags behind during fast scrolling. Capturing a stale value\n    // here makes the anchor-restore effect yank the viewport back to an old\n    // (often near-top) position when a freshly revealed chunk is measured.\n    const liveScrollTop =\n      viewportRef.current?.scrollTop ?? scrollTopRef.current;\n    pendingAnchorRef.current = getMarkdownGreenfieldScrollAnchor({\n      frames: frameChunksRef.current,\n      scrollTop: liveScrollTop,\n    });\n  }, []);\n  const markdownRendererFrame = useMarkdownGreenfieldRendererFrame({\n    fallbackViewportInlineSize: measuredViewportWidth,\n    onBeforeLayoutMotion: captureAnchor,\n  });\n  const viewportWidth = markdownRendererFrame.viewportInlineSize;\n  const measuredHeightLookup = React.useMemo(\n    () => ({\n      get: (\n        chunk: MarkdownGreenfieldChunk,\n        context: MarkdownGreenfieldMeasurementContext,\n      ) =>\n        measuredHeightsRef.current.get(\n          measuredHeightKey({\n            chunk,\n            context,\n            documentMeasurementId,\n          }),\n        ),\n      cacheKey: `${documentMeasurementId}:${measuredHeightsRevision}`,\n    }),\n    [documentMeasurementId, measuredHeightsRevision],\n  );\n  const frame = React.useMemo(\n    () =>\n      layoutMarkdownGreenfieldDocument({\n        contentWidth,\n        document,\n        fontScale,\n        measuredHeights: measuredHeightLookup,\n      }),\n    [contentWidth, document, fontScale, measuredHeightLookup],\n  );\n  documentRef.current = document;\n  frameChunksRef.current = frame.chunks;\n  viewportHeightRef.current = viewportHeight;\n  const highlightRange = React.useMemo(\n    () => normalizeTextLineRange(highlight, document.lineCount),\n    [document.lineCount, highlight],\n  );\n  const visibleHighlightRange = highlightRange;\n  const [visibleProjection, setVisibleProjection] =\n    React.useState<MarkdownGreenfieldVisibleProjection>(() =>\n      getMarkdownGreenfieldVisibleProjection({\n        frames: frame.chunks,\n        overscanPx: OVERSCAN_PX,\n        scrollTop: scrollTopRef.current,\n        viewportHeight,\n      }),\n    );\n  const visibleProjectionRef = React.useRef(visibleProjection);\n  const visibleFrames = React.useMemo(\n    () =>\n      getMarkdownGreenfieldProjectedVisibleFrames({\n        frames: frame.chunks,\n        projection: visibleProjection,\n      }),\n    [frame.chunks, visibleProjection],\n  );\n  const stickyWindow = React.useMemo(\n    () =>\n      getTextInverseStickyWindow({\n        renderedBottom: visibleFrames.at(-1)?.bottom ?? 0,\n        renderedTop:\n          visibleFrames[0]?.index === 0 ? 0 : (visibleFrames[0]?.top ?? 0),\n        totalHeight: frame.totalHeight,\n        viewportHeight,\n      }),\n    [frame.totalHeight, viewportHeight, visibleFrames],\n  );\n  const projectVisibleProjection = React.useCallback(() => {\n    const frames = frameChunksRef.current;\n    const nextRange = getMarkdownGreenfieldVisibleRange({\n      frames,\n      overscanPx: OVERSCAN_PX,\n      scrollTop: scrollTopRef.current,\n      viewportHeight: viewportHeightRef.current,\n    });\n    const currentProjection = visibleProjectionRef.current;\n    if (\n      isMarkdownGreenfieldVisibleProjectionSameWindow({\n        frames,\n        projection: currentProjection,\n        range: nextRange,\n      })\n    ) {\n      if (\n        currentProjection.range.start !== nextRange.start ||\n        currentProjection.range.end !== nextRange.end\n      ) {\n        visibleProjectionRef.current = {\n          frameIds: currentProjection.frameIds,\n          range: nextRange,\n        };\n      }\n      return;\n    }\n    const nextProjection = createMarkdownGreenfieldVisibleProjection({\n      frames,\n      range: nextRange,\n    });\n    visibleProjectionRef.current = nextProjection;\n    setVisibleProjection(nextProjection);\n  }, []);\n  const commitScrollTop = React.useCallback(\n    (nextScrollTop: number) => {\n      scrollTopRef.current = nextScrollTop;\n      projectVisibleProjection();\n    },\n    [projectVisibleProjection],\n  );\n\n  const scheduleScrollTop = React.useCallback(\n    (nextScrollTop: number) => {\n      scrollTopRef.current = nextScrollTop;\n      if (scrollFrameRef.current !== null) return;\n      if (typeof requestAnimationFrame !== \"function\") {\n        commitScrollTop(scrollTopRef.current);\n        return;\n      }\n      scrollFrameRef.current = requestAnimationFrame(() => {\n        scrollFrameRef.current = null;\n        commitScrollTop(scrollTopRef.current);\n      });\n    },\n    [commitScrollTop],\n  );\n  const handleViewportScroll = React.useCallback(\n    (viewport: HTMLElement) => scheduleScrollTop(viewport.scrollTop),\n    [scheduleScrollTop],\n  );\n  const getScrollInteractionTarget = React.useCallback(\n    () => stickyWindowRef.current,\n    [],\n  );\n  const getScrollOverflowTarget = React.useCallback(\n    () => stickyWindowRef.current,\n    [],\n  );\n\n  const flushMeasuredHeights = React.useCallback(() => {\n    measuredHeightFrameRef.current = null;\n    const pending = pendingMeasuredHeightsRef.current;\n    if (!pending.size) return;\n    pendingMeasuredHeightsRef.current = new Map();\n    let didChange = false;\n    const measuredHeights = measuredHeightsRef.current;\n    for (const [key, height] of pending) {\n      if (Math.abs((measuredHeights.get(key) ?? 0) - height) < 1) continue;\n      measuredHeights.set(key, height);\n      didChange = true;\n    }\n    if (didChange) {\n      setMeasuredHeightsRevision((revision) => revision + 1);\n    }\n  }, []);\n\n  const scheduleMeasuredHeightsFlush = React.useCallback(() => {\n    if (measuredHeightFrameRef.current !== null) return;\n    if (typeof requestAnimationFrame !== \"function\") {\n      flushMeasuredHeights();\n      return;\n    }\n    measuredHeightFrameRef.current =\n      requestAnimationFrame(flushMeasuredHeights);\n  }, [flushMeasuredHeights]);\n\n  useKeyedLayoutEffect(joinEffectKey([commitScrollTop, document]), () => {\n    measuredHeightsRef.current = new Map();\n    pendingMeasuredHeightsRef.current = new Map();\n    setMeasuredHeightsRevision((revision) => revision + 1);\n    commitScrollTop(0);\n    viewportRef.current?.scrollTo({ left: 0, top: 0 });\n  });\n\n  useMountEffect(() => () => {\n    if (\n      scrollFrameRef.current !== null &&\n      typeof cancelAnimationFrame === \"function\"\n    ) {\n      cancelAnimationFrame(scrollFrameRef.current);\n    }\n    if (\n      measuredHeightFrameRef.current !== null &&\n      typeof cancelAnimationFrame === \"function\"\n    ) {\n      cancelAnimationFrame(measuredHeightFrameRef.current);\n    }\n  });\n\n  useTextViewerScrollInteractions({\n    getInteractionTarget: getScrollInteractionTarget,\n    getOverflowTarget: getScrollOverflowTarget,\n    onScroll: handleViewportScroll,\n    viewportRef,\n  });\n\n  useKeyedLayoutEffect(\n    joinEffectKey([\n      \"markdown-viewport-resize\",\n      markdownRendererFrame.usesShellGeometry,\n    ]),\n    () => {\n      const viewport = viewportRef.current;\n      if (!viewport) return;\n\n      const readSize = () => {\n        setViewportSize((current) => {\n          const nextHeight = viewport.clientHeight;\n          const nextWidth =\n            markdownRendererFrame.usesShellGeometry && current.width > 0\n              ? current.width\n              : viewport.clientWidth;\n          const next = {\n            height: nextHeight,\n            width: nextWidth,\n          };\n          return current.height === next.height && current.width === next.width\n            ? current\n            : next;\n        });\n      };\n\n      readSize();\n      const observer =\n        typeof ResizeObserver === \"undefined\"\n          ? null\n          : new ResizeObserver(readSize);\n      observer?.observe(viewport);\n      return () => observer?.disconnect();\n    },\n  );\n\n  useKeyedLayoutEffect(joinEffectKey([captureAnchor, viewportWidth]), () => {\n    const nextWidth = Math.max(\n      1,\n      viewportWidth - VIEWER_HORIZONTAL_PADDING * 2,\n    );\n    setContentWidth((current) => {\n      if (current === nextWidth) return current;\n      captureAnchor();\n      return nextWidth;\n    });\n  });\n\n  useKeyedLayoutEffect(joinEffectKey([commitScrollTop, frame.chunks]), () => {\n    const viewport = viewportRef.current;\n    const previousChunks = prevFrameChunksRef.current;\n    prevFrameChunksRef.current = frame.chunks;\n    if (!viewport) return;\n\n    // Intentional full reflows (width, zoom, font readiness, mode switch) set an\n    // explicit anchor and want to restore the reader's relative position.\n    const anchor = pendingAnchorRef.current;\n    if (anchor) {\n      pendingAnchorRef.current = null;\n      const nextScrollTop = resolveMarkdownGreenfieldScrollAnchor({\n        anchor,\n        frames: frame.chunks,\n      });\n      if (nextScrollTop == null) return;\n      viewport.scrollTop = nextScrollTop;\n      commitScrollTop(nextScrollTop);\n      return;\n    }\n\n    // Measurement-driven changes keep whatever the reader is looking at stable by\n    // compensating scrollTop for the height delta of chunks ENTIRELY above the\n    // viewport top. Only already-measured chunks (real height in both layouts)\n    // count: a chunk's first estimate->measured correction happens as it is\n    // revealed near/below the viewport, never while the reader sits above it, so\n    // excluding it avoids the estimate-collapse plunge while still pinning the\n    // viewport when async rich blocks above re-settle.\n    if (!previousChunks) return;\n    const liveScrollTop = viewport.scrollTop;\n    const previousById = new Map(\n      previousChunks.map((chunkFrame) => [chunkFrame.id, chunkFrame]),\n    );\n    let delta = 0;\n    for (const chunkFrame of frame.chunks) {\n      const previous = previousById.get(chunkFrame.id);\n      if (\n        previous &&\n        previous.measuredHeight != null &&\n        chunkFrame.measuredHeight != null &&\n        previous.bottom <= liveScrollTop\n      ) {\n        delta += chunkFrame.measuredHeight - previous.measuredHeight;\n      }\n    }\n    if (delta === 0) return;\n    const nextScrollTop = Math.max(0, liveScrollTop + delta);\n    viewport.scrollTop = nextScrollTop;\n    commitScrollTop(nextScrollTop);\n  });\n\n  useKeyedLayoutEffect(\n    joinEffectKey([\"markdown-visible-range\", frame.chunks, viewportHeight]),\n    () => {\n      projectVisibleProjection();\n    },\n  );\n\n  const scrollToLineRange = React.useCallback(\n    (\n      range: ReturnType<typeof normalizeTextLineRange>,\n      options?: MarkdownScrollToLineOptions,\n    ) => {\n      const viewport = viewportRef.current;\n      if (!viewport || !range) return;\n      const currentDocument = documentRef.current;\n\n      const preferredChunkId =\n        options?.preferredChunkId ??\n        findMarkdownGreenfieldChunkBySourceLine(currentDocument, range.start)\n          ?.id;\n      const top = getMarkdownGreenfieldScrollTopForLineRange({\n        chunks: currentDocument.chunks,\n        frames: frameChunksRef.current,\n        preferredChunkId,\n        range,\n        viewportHeight: viewport.clientHeight || viewportHeightRef.current,\n      });\n      if (top == null) return;\n      viewport.scrollTo({\n        behavior: resolveScrollBehavior(options?.behavior),\n        left: options?.left,\n        top,\n      });\n      commitScrollTop(top);\n    },\n    [commitScrollTop],\n  );\n\n  // A stable handle to the latest scrollToLineRange. Target-driven effects use\n  // this ref so layout measurement changes cannot yank the viewport back to a\n  // highlight/search/hash target while the reader is scrolling.\n  const scrollToLineRangeRef = React.useRef(scrollToLineRange);\n  useKeyedLayoutEffect(joinEffectKey([scrollToLineRange]), () => {\n    scrollToLineRangeRef.current = scrollToLineRange;\n  });\n\n  React.useImperativeHandle(\n    forwardedRef ?? null,\n    () => ({\n      getViewportElement: () => viewportRef.current,\n      scrollToLineRange: (range, options) => {\n        scrollToLineRangeRef.current(\n          normalizeTextLineRange(range, document.lineCount),\n          options,\n        );\n      },\n    }),\n    [document.lineCount],\n  );\n\n  // Scrolling is an imperative DOM mutation that must run before paint to avoid\n  // a visible jump, so these reactions to a changed target line/range live in\n  // layout-timed reactions.\n  useKeyedLayoutEffect(joinEffectKey([highlightRange]), () => {\n    if (!highlightRange) return;\n    scrollToLineRangeRef.current(highlightRange);\n  });\n\n  // Subscribes to the browser's hash/history (a non-React external source) and\n  // scrolls the matching fragment into view before paint.\n  useKeyedLayoutEffect(joinEffectKey([document, urlFragmentNavigation]), () => {\n    if (!urlFragmentNavigation) return;\n\n    const scrollToCurrentHash = () => {\n      const hash = window.location.hash;\n      if (!hash) return;\n      const target = findMarkdownGreenfieldFragmentTargetById(document, hash);\n      if (!target) return;\n      const chunk = findMarkdownGreenfieldChunkByBlockId(\n        document,\n        target.blockId,\n      );\n      scrollToLineRangeRef.current(\n        normalizeTextLineRange(\n          { end: target.sourceLine, start: target.sourceLine },\n          document.lineCount,\n        ),\n        { behavior: \"auto\", preferredChunkId: chunk?.id },\n      );\n    };\n\n    scrollToCurrentHash();\n    window.addEventListener(\"hashchange\", scrollToCurrentHash);\n    window.addEventListener(\"popstate\", scrollToCurrentHash);\n    return () => {\n      window.removeEventListener(\"hashchange\", scrollToCurrentHash);\n      window.removeEventListener(\"popstate\", scrollToCurrentHash);\n    };\n  });\n\n  const recordMeasuredHeight = React.useCallback(\n    (chunk: MarkdownGreenfieldChunk, height: number) => {\n      if (!Number.isFinite(height) || height <= 0) return;\n      const key = measuredHeightKey({\n        chunk,\n        context: {\n          fontScale,\n          policyVersion: MARKDOWN_GREENFIELD_LAYOUT_POLICY_VERSION,\n          width: Math.max(1, contentWidth),\n        },\n        documentMeasurementId,\n      });\n      pendingMeasuredHeightsRef.current.set(key, height);\n      scheduleMeasuredHeightsFlush();\n    },\n    [\n      contentWidth,\n      documentMeasurementId,\n      fontScale,\n      scheduleMeasuredHeightsFlush,\n    ],\n  );\n  const handleDownloadError = React.useCallback<ViewerDownloadErrorHandler>(\n    (error) => {\n      if (error.kind === \"aborted\") return;\n      setDownloadError(error.message || \"Could not download Markdown.\");\n    },\n    [],\n  );\n  const zoom = (factor: number) => {\n    captureAnchor();\n    setFontScale((scale) => clampTextViewerScale(scale * factor));\n  };\n  const resetZoom = () => {\n    captureAnchor();\n    setFontScale(1);\n  };\n\n  return (\n    <TextViewerFrame className={className} bare={bare}>\n      {controls ? (\n        <TextViewerControls\n          downloadAction={downloadAction}\n          extra={<DownloadError message={downloadError} />}\n          fontScale={fontScale}\n          wordCount={\n            isDocumentPending\n              ? estimateMarkdownWordCount(text)\n              : document.wordCount\n          }\n          onDownloadError={handleDownloadError}\n          onResetZoom={resetZoom}\n          onZoomIn={() => zoom(1.2)}\n          onZoomOut={() => zoom(1 / 1.2)}\n        />\n      ) : null}\n      {/* overflow-anchor:none disables the browser's native scroll anchoring,\n          which otherwise fights this component's virtualization: as chunks\n          mount/unmount and the canvas height corrects, the browser shifts\n          scrollTop on its own, jerking the viewport while the reader scrolls. */}\n      <ScrollArea\n        className=\"bg-background min-h-0 flex-1\"\n        orientation=\"vertical\"\n        viewportClassName=\"bg-background [overflow-anchor:none]\"\n        viewportRef={viewportRef}\n        viewportProps={{\n          onClickCapture: (event) =>\n            handleRenderedClick({ document, event, scrollToLineRange }),\n        }}\n      >\n        <div\n          ref={markdownRendererFrame.setDocumentSurfaceElement}\n          className=\"relative min-w-0\"\n          data-projection=\"unified-hast-markdown\"\n          data-slot=\"markdown-virtual-canvas\"\n          style={{\n            height: Math.max(frame.totalHeight, viewportHeight),\n            minWidth: viewportWidth,\n            transformOrigin: markdownRendererFrame.transformOrigin,\n          }}\n        >\n          {isDocumentPending ? null : (\n            <DeferredNativeFindIndex\n              chunks={document.chunks}\n              lineCount={document.lineCount}\n              scrollToLineRange={scrollToLineRange}\n            />\n          )}\n          {isDocumentPending ? (\n            <div\n              aria-label=\"Preparing Markdown document\"\n              className=\"text-muted-foreground absolute inset-x-4 top-0 flex min-h-40 items-center justify-center text-sm\"\n              data-slot=\"markdown-loading-state\"\n              role=\"status\"\n            >\n              Preparing Markdown...\n            </div>\n          ) : document.text.trim() ? (\n            <>\n              <div\n                aria-hidden=\"true\"\n                data-slot=\"markdown-sticky-before-buffer\"\n                style={{\n                  contain: \"layout size\",\n                  height: stickyWindow.beforeHeight,\n                }}\n              />\n              <div\n                ref={stickyWindowRef}\n                data-slot=\"markdown-sticky-window\"\n                style={{\n                  bottom: stickyWindow.stickyOffset,\n                  contain: \"layout style inline-size\",\n                  display: \"flex\",\n                  flexDirection: \"column\",\n                  height: stickyWindow.renderedHeight,\n                  isolation: \"isolate\",\n                  left: 0,\n                  overflow: \"visible\",\n                  position: \"sticky\",\n                  top: stickyWindow.stickyOffset,\n                  width: \"100%\",\n                }}\n              >\n                <div\n                  data-slot=\"markdown-sticky-content\"\n                  style={{\n                    height: stickyWindow.renderedHeight,\n                    position: \"relative\",\n                    width: \"100%\",\n                  }}\n                >\n                  {visibleFrames.map((chunkFrame) => {\n                    const chunk = document.chunks[chunkFrame.index];\n                    if (!chunk) return null;\n                    return (\n                      <ChunkFrame\n                        key={chunk.id}\n                        chunk={chunk}\n                        frame={chunkFrame}\n                        highlightRange={visibleHighlightRange}\n                        highlighted={chunkIntersectsLineRange({\n                          chunkFrame,\n                          range: visibleHighlightRange,\n                        })}\n                        measurementKey={measuredHeightKey({\n                          chunk,\n                          context: {\n                            fontScale,\n                            policyVersion:\n                              MARKDOWN_GREENFIELD_LAYOUT_POLICY_VERSION,\n                            width: Math.max(1, contentWidth),\n                          },\n                          documentMeasurementId,\n                        })}\n                        renderedTop={stickyWindow.renderedTop}\n                        onMeasuredHeight={recordMeasuredHeight}\n                      >\n                        <MarkdownGreenfieldChunkRenderer\n                          chunk={chunk}\n                          fontScale={fontScale}\n                          urlFragmentNavigation={urlFragmentNavigation}\n                        />\n                      </ChunkFrame>\n                    );\n                  })}\n                </div>\n              </div>\n              <div\n                aria-hidden=\"true\"\n                data-slot=\"markdown-sticky-after-buffer\"\n                style={{\n                  contain: \"layout size\",\n                  height: stickyWindow.afterHeight,\n                }}\n              />\n            </>\n          ) : (\n            <div\n              aria-label=\"Empty Markdown document\"\n              className=\"text-muted-foreground absolute inset-x-4 top-0 flex min-h-40 items-center justify-center text-sm\"\n              data-slot=\"markdown-empty-state\"\n              role=\"status\"\n            >\n              Empty Markdown document\n            </div>\n          )}\n        </div>\n      </ScrollArea>\n    </TextViewerFrame>\n  );\n}\n\nfunction DeferredNativeFindIndex({\n  chunks,\n  lineCount,\n  scrollToLineRange,\n}: {\n  chunks: readonly MarkdownGreenfieldChunk[];\n  lineCount: number;\n  scrollToLineRange: (\n    range: ReturnType<typeof normalizeTextLineRange>,\n    options?: MarkdownScrollToLineOptions,\n  ) => void;\n}) {\n  const [isReady, setIsReady] = React.useState(false);\n\n  useKeyedMountEffect(joinEffectKey([chunks]), () => {\n    setIsReady(false);\n    const show = () => setIsReady(true);\n    if (typeof window === \"undefined\") return;\n    const browserWindow = window as MarkdownIdleWindow;\n    if (browserWindow.requestIdleCallback && browserWindow.cancelIdleCallback) {\n      const idleId = browserWindow.requestIdleCallback(show, { timeout: 400 });\n      return () => browserWindow.cancelIdleCallback?.(idleId);\n    }\n    const timeoutId = browserWindow.setTimeout(show, 80);\n    return () => browserWindow.clearTimeout(timeoutId);\n  });\n\n  if (!isReady) return null;\n  return (\n    <NativeFindIndex\n      chunks={chunks}\n      lineCount={lineCount}\n      scrollToLineRange={scrollToLineRange}\n    />\n  );\n}\n\nfunction NativeFindIndex({\n  chunks,\n  lineCount,\n  scrollToLineRange,\n}: {\n  chunks: readonly MarkdownGreenfieldChunk[];\n  lineCount: number;\n  scrollToLineRange: (\n    range: ReturnType<typeof normalizeTextLineRange>,\n    options?: MarkdownScrollToLineOptions,\n  ) => void;\n}) {\n  const entries = useBatchedNativeFindTextEntries(chunks);\n  return (\n    <div\n      aria-hidden=\"true\"\n      className=\"pointer-events-none absolute top-0 left-0 h-px w-px overflow-hidden opacity-0\"\n      data-slot=\"markdown-native-find-index\"\n      data-native-find-indexed-chunks={entries.length}\n      data-native-find-total-chunks={chunks.length}\n    >\n      {entries.map(({ chunk, text }) => (\n        <NativeFindEntry\n          key={chunk.id}\n          chunk={chunk}\n          lineCount={lineCount}\n          scrollToLineRange={scrollToLineRange}\n          text={text}\n        />\n      ))}\n    </div>\n  );\n}\n\nfunction NativeFindEntry({\n  chunk,\n  lineCount,\n  scrollToLineRange,\n  text,\n}: {\n  chunk: MarkdownGreenfieldChunk;\n  lineCount: number;\n  scrollToLineRange: (\n    range: ReturnType<typeof normalizeTextLineRange>,\n    options?: MarkdownScrollToLineOptions,\n  ) => void;\n  text: string;\n}) {\n  const ref = React.useRef<HTMLSpanElement | null>(null);\n\n  useKeyedLayoutEffect(\n    joinEffectKey([\n      chunk.id,\n      chunk.sourceEndLine,\n      chunk.sourceStartLine,\n      lineCount,\n      scrollToLineRange,\n    ]),\n    () => {\n      const element = ref.current;\n      if (!element) return;\n      element.setAttribute(\"hidden\", \"until-found\");\n\n      const handleBeforeMatch = () => {\n        scrollToLineRange(\n          normalizeTextLineRange(\n            {\n              end: chunk.sourceEndLine,\n              start: chunk.sourceStartLine,\n            },\n            lineCount,\n          ),\n          { behavior: \"auto\", preferredChunkId: chunk.id },\n        );\n        requestAnimationFrame(() => {\n          element.setAttribute(\"hidden\", \"until-found\");\n        });\n      };\n\n      element.addEventListener(\"beforematch\", handleBeforeMatch);\n      return () => {\n        element.removeEventListener(\"beforematch\", handleBeforeMatch);\n      };\n    },\n  );\n\n  return (\n    <span\n      ref={ref}\n      className=\"absolute top-0 left-0 block h-px w-px overflow-hidden whitespace-pre\"\n      data-native-find-chunk-id={chunk.id}\n      data-native-find-end-line={chunk.sourceEndLine}\n      data-native-find-start-line={chunk.sourceStartLine}\n    >\n      {text || \" \"}\n    </span>\n  );\n}\n\nfunction useBatchedNativeFindTextEntries(\n  chunks: readonly MarkdownGreenfieldChunk[],\n) {\n  const buildKey = React.useMemo(\n    () => joinEffectKey([\"native-find-text\", chunks]),\n    [chunks],\n  );\n  const [state, setState] = React.useState<NativeFindTextState>({\n    entries: [],\n    key: null,\n  });\n\n  useKeyedMountEffect(buildKey, () => {\n    let isCancelled = false;\n    let cancelScheduledBatch: (() => void) | null = null;\n    let index = 0;\n    const entries: NativeFindTextRecord[] = [];\n\n    setState({ entries: [], key: buildKey });\n\n    const runBatch = (deadline?: IdleDeadline) => {\n      cancelScheduledBatch = null;\n      if (isCancelled) return;\n\n      let processed = 0;\n      while (\n        index < chunks.length &&\n        shouldBuildNativeFindTextInCurrentBatch(deadline, processed)\n      ) {\n        const chunk = chunks[index];\n        index += 1;\n        if (!chunk) continue;\n        entries.push({\n          chunk,\n          text: chunk.nativeFindText,\n        });\n        processed += 1;\n      }\n\n      if (processed > 0) {\n        setState({ entries: entries.slice(), key: buildKey });\n      }\n      if (index < chunks.length) {\n        cancelScheduledBatch = scheduleNativeFindTextBatch(runBatch);\n      }\n    };\n\n    cancelScheduledBatch = scheduleNativeFindTextBatch(runBatch);\n    return () => {\n      isCancelled = true;\n      cancelScheduledBatch?.();\n    };\n  });\n\n  return state.key === buildKey ? state.entries : [];\n}\n\nfunction shouldBuildNativeFindTextInCurrentBatch(\n  deadline: IdleDeadline | undefined,\n  processed: number,\n) {\n  if (processed === 0) return true;\n  if (processed >= NATIVE_FIND_TEXT_BATCH_CHUNKS) return false;\n  if (!deadline) return true;\n  return deadline.timeRemaining() > NATIVE_FIND_TEXT_IDLE_MIN_REMAINING_MS;\n}\n\nfunction scheduleNativeFindTextBatch(\n  callback: (deadline?: IdleDeadline) => void,\n) {\n  if (typeof window === \"undefined\") return null;\n  const browserWindow = window as MarkdownIdleWindow;\n  if (browserWindow.requestIdleCallback) {\n    const idleId = browserWindow.requestIdleCallback(callback, {\n      timeout: 250,\n    });\n    return () => browserWindow.cancelIdleCallback?.(idleId);\n  }\n\n  const timeoutId = browserWindow.setTimeout(() => callback(), 0);\n  return () => browserWindow.clearTimeout(timeoutId);\n}\n\nfunction DownloadError({ message }: { message: string }) {\n  if (!message) return null;\n  return (\n    <span\n      className=\"text-destructive max-w-48 truncate text-xs\"\n      data-slot=\"markdown-download-error\"\n      role=\"status\"\n    >\n      {message}\n    </span>\n  );\n}\n\nfunction ChunkFrame({\n  children,\n  chunk,\n  frame,\n  highlightRange,\n  highlighted,\n  measurementKey,\n  renderedTop,\n  onMeasuredHeight,\n}: {\n  children: React.ReactNode;\n  chunk: MarkdownGreenfieldChunk;\n  frame: MarkdownGreenfieldChunkFrame;\n  highlightRange: { end: number; start: number } | null;\n  highlighted: boolean;\n  measurementKey: string;\n  renderedTop: number;\n  onMeasuredHeight: (chunk: MarkdownGreenfieldChunk, height: number) => void;\n}) {\n  const ref = React.useRef<HTMLDivElement | null>(null);\n\n  useKeyedLayoutEffect(joinEffectKey([chunk, onMeasuredHeight]), () => {\n    measure();\n    const element = ref.current;\n    if (!element || typeof ResizeObserver === \"undefined\") return;\n    const observer = new ResizeObserver(measure);\n    observer.observe(element);\n    return () => observer.disconnect();\n\n    function measure() {\n      const element = ref.current;\n      if (!element) return;\n      onMeasuredHeight(chunk, element.getBoundingClientRect().height);\n    }\n  });\n\n  return (\n    <section\n      ref={ref}\n      aria-label={\n        highlighted\n          ? `Highlighted source lines ${highlightRange?.start}-${highlightRange?.end}`\n          : `Markdown lines ${frame.sourceStartLine} to ${frame.sourceEndLine}`\n      }\n      className={[\n        \"absolute left-1/2 w-full -translate-x-1/2 px-8 py-1\",\n      ].join(\" \")}\n      data-markdown-chunk=\"\"\n      data-markdown-highlighted={highlighted ? \"\" : undefined}\n      data-pretext-measured-height-key={measurementKey}\n      data-source-highlight-end={highlighted ? highlightRange?.end : undefined}\n      data-source-highlight-start={\n        highlighted ? highlightRange?.start : undefined\n      }\n      data-source-end-line={frame.sourceEndLine}\n      data-source-start-line={frame.sourceStartLine}\n      role={highlighted ? \"region\" : undefined}\n      style={{\n        maxWidth: MARKDOWN_GREENFIELD_CHUNK_MAX_INLINE_SIZE,\n        top: frame.top - renderedTop,\n        ...(highlighted ? MARKDOWN_GREENFIELD_HIGHLIGHT_STYLE : null),\n      }}\n    >\n      {children}\n    </section>\n  );\n}\n\nfunction resolveScrollBehavior(behavior: ScrollBehavior | undefined) {\n  if (behavior) return behavior;\n  if (\n    typeof window !== \"undefined\" &&\n    window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches\n  ) {\n    return \"auto\";\n  }\n  return \"smooth\";\n}\n\nfunction handleRenderedClick({\n  document,\n  event,\n  scrollToLineRange,\n}: {\n  document: ReturnType<typeof createMarkdownGreenfieldDocument>;\n  event: React.MouseEvent;\n  scrollToLineRange: (\n    range: ReturnType<typeof normalizeTextLineRange>,\n    options?: MarkdownScrollToLineOptions,\n  ) => void;\n}) {\n  const link = (event.target as HTMLElement | null)?.closest(\"a[href]\");\n  const href = link?.getAttribute(\"href\");\n  if (!href?.startsWith(\"#\")) return;\n\n  const target = findMarkdownGreenfieldFragmentTargetById(document, href);\n  if (!target) return;\n  const chunk = findMarkdownGreenfieldChunkByBlockId(document, target.blockId);\n\n  event.preventDefault();\n  window.history.pushState(null, \"\", href);\n  scrollToLineRange(\n    normalizeTextLineRange(\n      { end: target.sourceLine, start: target.sourceLine },\n      document.lineCount,\n    ),\n    { behavior: \"smooth\", preferredChunkId: chunk?.id },\n  );\n}\n\nfunction chunkIntersectsLineRange({\n  chunkFrame,\n  range,\n}: {\n  chunkFrame: MarkdownGreenfieldChunkFrame;\n  range: { end: number; start: number } | null;\n}) {\n  if (!range) return false;\n  return (\n    chunkFrame.sourceStartLine <= range.end &&\n    chunkFrame.sourceEndLine >= range.start\n  );\n}\n\nfunction measuredHeightKey({\n  chunk,\n  context,\n  documentMeasurementId,\n}: {\n  chunk: MarkdownGreenfieldChunk;\n  context: MarkdownGreenfieldMeasurementContext;\n  documentMeasurementId: string;\n}) {\n  return [\n    documentMeasurementId,\n    chunk.id,\n    Math.round(context.width),\n    context.fontScale.toFixed(4),\n    context.policyVersion,\n  ].join(\":\");\n}\n\nfunction measurementDocumentIdForText(text: string) {\n  let hash = 2166136261;\n  for (let index = 0; index < text.length; index += 1) {\n    hash ^= text.charCodeAt(index);\n    hash = Math.imul(hash, 16777619);\n  }\n  return `md-${text.length}-${(hash >>> 0).toString(36)}`;\n}\n\nfunction estimateMarkdownWordCount(text: string) {\n  return text.trim().split(/\\s+/).filter(Boolean).length;\n}\n",
      "type": "registry:ui",
      "target": "@ui/markdown-greenfield-content.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/markdown-greenfield-code-highlight.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n  ensureCodePrismLanguage,\n  tokenizeCodeLine as tokenizePrismCodeLine,\n} from \"./code-viewer-syntax-prism\";\nimport { shouldTokenizeCodeLine } from \"./code-viewer-syntax-protocol\";\nimport { markdownCodeTokensToHtml } from \"./markdown-greenfield-code-highlight-html\";\nimport {\n  MARKDOWN_CODE_HIGHLIGHT_BATCH_SIZE,\n  MARKDOWN_CODE_HIGHLIGHT_CACHE_LIMIT,\n  MARKDOWN_CODE_HIGHLIGHT_RENDERER_VERSION,\n  type MarkdownCodeHighlightLineRequest,\n  type MarkdownCodeHighlightLineResult,\n  type MarkdownCodeHighlightWorkerRequest,\n  type MarkdownCodeHighlightWorkerResponse,\n} from \"./markdown-greenfield-code-highlight-protocol\";\n\nexport const MARKDOWN_CODE_HIGHLIGHT_STYLES = `\n.cv-token-comment { color: var(--cv-token-comment, #6e7781); font-style: italic; }\n.cv-token-property,\n.cv-token-tag,\n.cv-token-attr-name,\n.cv-token-symbol { color: var(--cv-token-property, #0550ae); }\n.cv-token-string,\n.cv-token-char,\n.cv-token-attr-value,\n.cv-token-url,\n.cv-token-regex { color: var(--cv-token-string, #0a7d33); }\n.cv-token-number { color: var(--cv-token-number, #b5690c); }\n.cv-token-keyword,\n.cv-token-boolean,\n.cv-token-null,\n.cv-token-constant,\n.cv-token-atrule,\n.cv-token-important { color: var(--cv-token-keyword, #8250df); }\n.cv-token-function,\n.cv-token-class-name,\n.cv-token-builtin { color: var(--cv-token-function, #8250df); }\n.cv-token-variable { color: var(--cv-token-variable, #953800); }\n.cv-token-punctuation,\n.cv-token-operator { color: var(--cv-token-punctuation, color-mix(in oklab, var(--foreground) 55%, transparent)); }\n.dark .cv-token-comment { color: var(--cv-token-comment, #8b949e); }\n.dark .cv-token-property,\n.dark .cv-token-tag,\n.dark .cv-token-attr-name,\n.dark .cv-token-symbol { color: var(--cv-token-property, #6cb6ff); }\n.dark .cv-token-string,\n.dark .cv-token-char,\n.dark .cv-token-attr-value,\n.dark .cv-token-url,\n.dark .cv-token-regex { color: var(--cv-token-string, #8ddb8c); }\n.dark .cv-token-number { color: var(--cv-token-number, #e3b341); }\n.dark .cv-token-keyword,\n.dark .cv-token-boolean,\n.dark .cv-token-null,\n.dark .cv-token-constant,\n.dark .cv-token-atrule,\n.dark .cv-token-important { color: var(--cv-token-keyword, #dcbdfb); }\n.dark .cv-token-function,\n.dark .cv-token-class-name,\n.dark .cv-token-builtin { color: var(--cv-token-function, #d2a8ff); }\n.dark .cv-token-variable { color: var(--cv-token-variable, #ffa657); }\n`;\n\ntype MarkdownCodeLineHtmlRequest = MarkdownCodeHighlightLineRequest & {\n  highlightPattern: string;\n  key: string;\n  languageId: string;\n};\n\ntype MarkdownCodeHighlightTaskHandle =\n  | { id: number; kind: \"idle\" }\n  | { id: number; kind: \"timeout\" };\n\ntype MarkdownCodeHighlightIdleWindow = Window &\n  typeof globalThis & {\n    cancelIdleCallback?: Window[\"cancelIdleCallback\"];\n    requestIdleCallback?: Window[\"requestIdleCallback\"];\n  };\n\nconst resolvedMarkdownCodeLineHtml = new Map<string, string | null>();\nconst pendingMarkdownCodeLineHtml = new Map<\n  string,\n  MarkdownCodeLineHtmlRequest\n>();\nconst markdownCodeLineHtmlSubscribers = new Map<string, Set<() => void>>();\nconst activeWorkerRequests = new Map<\n  number,\n  readonly MarkdownCodeLineHtmlRequest[]\n>();\n\nlet markdownCodeHighlightFlushHandle: MarkdownCodeHighlightTaskHandle | null =\n  null;\nlet markdownCodeHighlightWorker: Worker | null = null;\nlet isMarkdownCodeHighlightWorkerFailed = false;\nlet markdownCodeHighlightRequestId = 0;\n\nexport function isSafeHighlightedCodeLine(line: number) {\n  return Number.isInteger(line) && line > 0 && line <= 100_000;\n}\n\nexport function normalizeCodeLanguage(language: string | null) {\n  const value = (language ?? \"text\").toLowerCase();\n  const aliases: Record<string, string> = {\n    bash: \"shell\",\n    docker: \"dockerfile\",\n    javascript: \"js\",\n    jsonc: \"json\",\n    md: \"markdown\",\n    patch: \"diff\",\n    rb: \"ruby\",\n    \"shell-session\": \"shell\",\n    terminal: \"shell\",\n    typescript: \"ts\",\n    yml: \"yaml\",\n  };\n  return aliases[value] ?? value;\n}\n\nexport function diffLineKind(line: string) {\n  if (line.startsWith(\"+\") && !line.startsWith(\"+++\")) return \"add\";\n  if (line.startsWith(\"-\") && !line.startsWith(\"---\")) return \"remove\";\n  return null;\n}\n\nexport function renderCodeLine({\n  fallbackLanguage,\n  line,\n  lineHtml,\n  pattern,\n}: {\n  fallbackLanguage: string;\n  line: string;\n  lineHtml: string | undefined;\n  pattern: string;\n}) {\n  if (lineHtml !== undefined) {\n    return (\n      <span\n        data-pretext-code-line-html=\"\"\n        dangerouslySetInnerHTML={{ __html: lineHtml }}\n      />\n    );\n  }\n  if (!pattern) return renderFallbackCodeTokens(line || \" \", fallbackLanguage);\n  const index = line.indexOf(pattern);\n  if (index < 0) return renderFallbackCodeTokens(line || \" \", fallbackLanguage);\n  return (\n    <>\n      {renderFallbackCodeTokens(line.slice(0, index), fallbackLanguage)}\n      <span data-highlighted-chars=\"\">{pattern}</span>\n      {renderFallbackCodeTokens(\n        line.slice(index + pattern.length),\n        fallbackLanguage,\n      )}\n    </>\n  );\n}\n\nexport function useMarkdownCodeLineHtml({\n  end,\n  highlightPattern,\n  language,\n  sourceLines,\n  start,\n}: {\n  end: number;\n  highlightPattern: string;\n  language: string;\n  sourceLines: readonly string[];\n  start: number;\n}) {\n  const requests = React.useMemo(\n    () =>\n      createMarkdownCodeLineHtmlRequests({\n        end,\n        highlightPattern,\n        language,\n        sourceLines,\n        start,\n      }),\n    [end, highlightPattern, language, sourceLines, start],\n  );\n  const subscribe = React.useCallback(\n    (onStoreChange: () => void) => {\n      for (const request of requests) {\n        let subscribers = markdownCodeLineHtmlSubscribers.get(request.key);\n        if (!subscribers) {\n          subscribers = new Set();\n          markdownCodeLineHtmlSubscribers.set(request.key, subscribers);\n        }\n        subscribers.add(onStoreChange);\n        ensureMarkdownCodeLineHtml(request);\n      }\n      return () => {\n        for (const request of requests) {\n          const subscribers = markdownCodeLineHtmlSubscribers.get(request.key);\n          if (!subscribers) continue;\n          subscribers.delete(onStoreChange);\n          if (!subscribers.size) {\n            markdownCodeLineHtmlSubscribers.delete(request.key);\n          }\n        }\n      };\n    },\n    [requests],\n  );\n  const getSnapshot = React.useCallback(\n    () =>\n      requests\n        .map((request) =>\n          resolvedMarkdownCodeLineHtml.has(request.key) ? \"1\" : \"0\",\n        )\n        .join(\"\"),\n    [requests],\n  );\n  const resolvedVersion = React.useSyncExternalStore(\n    subscribe,\n    getSnapshot,\n    () => \"\",\n  );\n\n  return React.useMemo(() => {\n    void resolvedVersion;\n    const htmlByIndex = new Map<number, string>();\n    for (const request of requests) {\n      const html = resolvedMarkdownCodeLineHtml.get(request.key);\n      if (typeof html === \"string\") htmlByIndex.set(request.index, html);\n    }\n    return htmlByIndex;\n  }, [requests, resolvedVersion]);\n}\n\nfunction createMarkdownCodeLineHtmlRequests({\n  end,\n  highlightPattern,\n  language,\n  sourceLines,\n  start,\n}: {\n  end: number;\n  highlightPattern: string;\n  language: string;\n  sourceLines: readonly string[];\n  start: number;\n}) {\n  const languageId = prismLanguageForMarkdownCode(language);\n  if (!languageId) return [];\n\n  const requests: MarkdownCodeLineHtmlRequest[] = [];\n  const boundedStart = Math.max(0, Math.min(start, sourceLines.length));\n  const boundedEnd = Math.max(boundedStart, Math.min(end, sourceLines.length));\n  for (let index = boundedStart; index < boundedEnd; index += 1) {\n    const line = sourceLines[index] ?? \"\";\n    requests.push({\n      highlightPattern,\n      index,\n      key: markdownCodeLineHtmlKey({\n        highlightPattern,\n        languageId,\n        line,\n      }),\n      languageId,\n      line,\n    });\n  }\n  return requests;\n}\n\nfunction ensureMarkdownCodeLineHtml(request: MarkdownCodeLineHtmlRequest) {\n  if (\n    resolvedMarkdownCodeLineHtml.has(request.key) ||\n    pendingMarkdownCodeLineHtml.has(request.key)\n  ) {\n    return;\n  }\n  pendingMarkdownCodeLineHtml.set(request.key, request);\n  scheduleMarkdownCodeHighlightFlush();\n}\n\nfunction scheduleMarkdownCodeHighlightFlush() {\n  if (markdownCodeHighlightFlushHandle) return;\n  markdownCodeHighlightFlushHandle = scheduleMarkdownCodeHighlightTask(() => {\n    markdownCodeHighlightFlushHandle = null;\n    flushPendingMarkdownCodeHighlights();\n  });\n}\n\nfunction flushPendingMarkdownCodeHighlights() {\n  const firstRequest = pendingMarkdownCodeLineHtml.values().next().value;\n  if (!firstRequest) return;\n\n  const requests: MarkdownCodeLineHtmlRequest[] = [];\n  for (const request of pendingMarkdownCodeLineHtml.values()) {\n    if (\n      request.languageId !== firstRequest.languageId ||\n      request.highlightPattern !== firstRequest.highlightPattern\n    ) {\n      continue;\n    }\n    pendingMarkdownCodeLineHtml.delete(request.key);\n    requests.push(request);\n    if (requests.length >= MARKDOWN_CODE_HIGHLIGHT_BATCH_SIZE) break;\n  }\n\n  if (requests.length === 0) return;\n  if (canUseMarkdownCodeHighlightWorker()) {\n    requestMarkdownCodeHighlightWorker(requests);\n  } else {\n    void highlightMarkdownCodeLinesOnMainThread(requests);\n  }\n  if (pendingMarkdownCodeLineHtml.size > 0)\n    scheduleMarkdownCodeHighlightFlush();\n}\n\nfunction canUseMarkdownCodeHighlightWorker() {\n  return (\n    !isMarkdownCodeHighlightWorkerFailed &&\n    typeof Worker !== \"undefined\" &&\n    typeof window !== \"undefined\"\n  );\n}\n\nfunction requestMarkdownCodeHighlightWorker(\n  requests: readonly MarkdownCodeLineHtmlRequest[],\n) {\n  const worker = getMarkdownCodeHighlightWorker();\n  if (!worker) {\n    void highlightMarkdownCodeLinesOnMainThread(requests);\n    return;\n  }\n\n  markdownCodeHighlightRequestId += 1;\n  const requestId = markdownCodeHighlightRequestId;\n  activeWorkerRequests.set(requestId, requests);\n  const firstRequest = requests[0]!;\n  const request: MarkdownCodeHighlightWorkerRequest = {\n    generation: MARKDOWN_CODE_HIGHLIGHT_RENDERER_VERSION,\n    highlightPattern: firstRequest.highlightPattern,\n    languageId: firstRequest.languageId,\n    lines: requests.map(({ index, line }) => ({ index, line })),\n    requestId,\n    type: \"highlight\",\n  };\n  worker.postMessage(request);\n}\n\nfunction getMarkdownCodeHighlightWorker() {\n  if (markdownCodeHighlightWorker) return markdownCodeHighlightWorker;\n  try {\n    markdownCodeHighlightWorker = new Worker(\n      new URL(\n        \"./markdown-greenfield-code-highlight.worker.ts\",\n        import.meta.url,\n      ),\n      { type: \"module\" },\n    );\n    markdownCodeHighlightWorker.onmessage = (\n      event: MessageEvent<MarkdownCodeHighlightWorkerResponse>,\n    ) => handleMarkdownCodeHighlightWorkerMessage(event.data);\n    markdownCodeHighlightWorker.onerror = () =>\n      failMarkdownCodeHighlightWorker();\n    markdownCodeHighlightWorker.onmessageerror = () =>\n      failMarkdownCodeHighlightWorker();\n    return markdownCodeHighlightWorker;\n  } catch {\n    isMarkdownCodeHighlightWorkerFailed = true;\n    return null;\n  }\n}\n\nfunction handleMarkdownCodeHighlightWorkerMessage(\n  message: MarkdownCodeHighlightWorkerResponse,\n) {\n  const requests = activeWorkerRequests.get(message.requestId);\n  if (\n    !requests ||\n    message.generation !== MARKDOWN_CODE_HIGHLIGHT_RENDERER_VERSION\n  ) {\n    return;\n  }\n  activeWorkerRequests.delete(message.requestId);\n  if (message.type === \"error\") {\n    resolveMarkdownCodeLineHtmlRequests(\n      requests.map((request) => ({ html: null, index: request.index })),\n      requests,\n    );\n    return;\n  }\n  resolveMarkdownCodeLineHtmlRequests(message.results, requests);\n}\n\nfunction failMarkdownCodeHighlightWorker() {\n  const activeRequests = Array.from(activeWorkerRequests.values()).flat();\n  activeWorkerRequests.clear();\n  markdownCodeHighlightWorker?.terminate();\n  markdownCodeHighlightWorker = null;\n  isMarkdownCodeHighlightWorkerFailed = true;\n  if (activeRequests.length) {\n    void highlightMarkdownCodeLinesOnMainThread(activeRequests);\n  }\n}\n\nasync function highlightMarkdownCodeLinesOnMainThread(\n  requests: readonly MarkdownCodeLineHtmlRequest[],\n) {\n  const firstRequest = requests[0];\n  if (!firstRequest) return;\n  try {\n    await ensureCodePrismLanguage(firstRequest.languageId);\n    resolveMarkdownCodeLineHtmlRequests(\n      requests.map((request) => ({\n        html: shouldTokenizeCodeLine(request.line)\n          ? markdownCodeTokensToHtml({\n              highlightPattern: request.highlightPattern,\n              line: request.line,\n              tokens: tokenizePrismCodeLine(request.languageId, request.line),\n            })\n          : null,\n        index: request.index,\n      })),\n      requests,\n    );\n  } catch {\n    resolveMarkdownCodeLineHtmlRequests(\n      requests.map((request) => ({ html: null, index: request.index })),\n      requests,\n    );\n  }\n}\n\nfunction resolveMarkdownCodeLineHtmlRequests(\n  results: readonly MarkdownCodeHighlightLineResult[],\n  requests: readonly MarkdownCodeLineHtmlRequest[],\n) {\n  const requestsByIndex = new Map(\n    requests.map((request) => [request.index, request] as const),\n  );\n  const resolvedKeys: string[] = [];\n  for (const result of results) {\n    const request = requestsByIndex.get(result.index);\n    if (!request) continue;\n    resolvedMarkdownCodeLineHtml.set(request.key, result.html);\n    resolvedKeys.push(request.key);\n  }\n  trimMarkdownCodeLineHtmlCache();\n  notifyMarkdownCodeLineHtmlSubscribers(resolvedKeys);\n}\n\nfunction notifyMarkdownCodeLineHtmlSubscribers(keys: readonly string[]) {\n  const subscribers = new Set<() => void>();\n  for (const key of keys) {\n    for (const subscriber of markdownCodeLineHtmlSubscribers.get(key) ?? []) {\n      subscribers.add(subscriber);\n    }\n  }\n  for (const subscriber of subscribers) subscriber();\n}\n\nfunction trimMarkdownCodeLineHtmlCache() {\n  while (\n    resolvedMarkdownCodeLineHtml.size > MARKDOWN_CODE_HIGHLIGHT_CACHE_LIMIT\n  ) {\n    const oldestKey = resolvedMarkdownCodeLineHtml.keys().next().value;\n    if (oldestKey === undefined) break;\n    resolvedMarkdownCodeLineHtml.delete(oldestKey);\n  }\n}\n\nfunction scheduleMarkdownCodeHighlightTask(callback: () => void) {\n  if (typeof window === \"undefined\") {\n    const id = setTimeout(callback, 0) as unknown as number;\n    return { id, kind: \"timeout\" as const };\n  }\n  const browserWindow = window as MarkdownCodeHighlightIdleWindow;\n  if (browserWindow.requestIdleCallback) {\n    return {\n      id: browserWindow.requestIdleCallback(callback, { timeout: 120 }),\n      kind: \"idle\" as const,\n    };\n  }\n  return {\n    id: browserWindow.setTimeout(callback, 0),\n    kind: \"timeout\" as const,\n  };\n}\n\nexport function resetMarkdownCodeHighlightForTests() {\n  resolvedMarkdownCodeLineHtml.clear();\n  pendingMarkdownCodeLineHtml.clear();\n  markdownCodeLineHtmlSubscribers.clear();\n  activeWorkerRequests.clear();\n  if (markdownCodeHighlightFlushHandle) {\n    cancelMarkdownCodeHighlightTask(markdownCodeHighlightFlushHandle);\n  }\n  markdownCodeHighlightFlushHandle = null;\n  markdownCodeHighlightWorker?.terminate();\n  markdownCodeHighlightWorker = null;\n  isMarkdownCodeHighlightWorkerFailed = false;\n  markdownCodeHighlightRequestId = 0;\n}\n\nfunction cancelMarkdownCodeHighlightTask(\n  handle: MarkdownCodeHighlightTaskHandle,\n) {\n  if (typeof window === \"undefined\") {\n    clearTimeout(handle.id);\n    return;\n  }\n  const browserWindow = window as MarkdownCodeHighlightIdleWindow;\n  if (handle.kind === \"idle\") {\n    browserWindow.cancelIdleCallback?.(handle.id);\n    return;\n  }\n  browserWindow.clearTimeout(handle.id);\n}\n\nfunction markdownCodeLineHtmlKey({\n  highlightPattern,\n  languageId,\n  line,\n}: {\n  highlightPattern: string;\n  languageId: string;\n  line: string;\n}) {\n  return [\n    MARKDOWN_CODE_HIGHLIGHT_RENDERER_VERSION,\n    languageId,\n    highlightPattern,\n    line,\n  ].join(\"\\0\");\n}\n\nfunction prismLanguageForMarkdownCode(language: string) {\n  const aliases: Record<string, string> = {\n    dockerfile: \"dockerfile\",\n    js: \"javascript\",\n    shell: \"bash\",\n    ts: \"typescript\",\n  };\n  const languageId = aliases[language] ?? language;\n  if (languageId === \"text\" || languageId === \"plaintext\") return \"\";\n  return languageId;\n}\n\nfunction renderFallbackCodeTokens(line: string, language: string) {\n  const tokens = tokenizeFallbackCodeLine(line, language);\n  if (!tokens.length) return \" \";\n  return tokens.map((token, index) =>\n    token.kind === \"plain\" ? (\n      <React.Fragment key={index}>{token.value}</React.Fragment>\n    ) : (\n      <span\n        key={index}\n        className={codeTokenClassName(token.kind)}\n        data-pretext-code-token={token.kind}\n      >\n        {token.value}\n      </span>\n    ),\n  );\n}\n\ntype CodeToken = {\n  kind: \"comment\" | \"keyword\" | \"literal\" | \"number\" | \"plain\" | \"string\";\n  value: string;\n};\n\nfunction tokenizeFallbackCodeLine(line: string, language: string): CodeToken[] {\n  if (!line) return [];\n  if (language === \"diff\") return tokenizeDiffLine(line);\n  if (language === \"json\") return tokenizeJsonLikeLine(line);\n  if (language === \"yaml\") return tokenizeYamlLine(line);\n  if (\n    language === \"js\" ||\n    language === \"jsx\" ||\n    language === \"ts\" ||\n    language === \"tsx\"\n  ) {\n    return tokenizeCStyleLine(line);\n  }\n  if (\n    language === \"shell\" ||\n    language === \"bash\" ||\n    language === \"dockerfile\"\n  ) {\n    return tokenizeShellLine(line);\n  }\n  return [{ kind: \"plain\", value: line }];\n}\n\nfunction tokenizeCStyleLine(line: string): CodeToken[] {\n  const commentIndex = line.indexOf(\"//\");\n  const codePart = commentIndex >= 0 ? line.slice(0, commentIndex) : line;\n  const commentPart = commentIndex >= 0 ? line.slice(commentIndex) : \"\";\n  return [\n    ...tokenizeByPattern(\n      codePart,\n      /(\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'|`(?:\\\\.|[^`\\\\])*`|\\b(?:as|async|await|break|case|catch|class|const|continue|default|do|else|export|extends|false|finally|for|from|function|if|import|in|instanceof|interface|let|new|null|of|return|satisfies|switch|throw|true|try|type|typeof|undefined|var|while|yield)\\b|\\b\\d+(?:\\.\\d+)?\\b)/g,\n      classifyCStyleToken,\n    ),\n    ...(commentPart ? [{ kind: \"comment\" as const, value: commentPart }] : []),\n  ];\n}\n\nfunction classifyCStyleToken(value: string): CodeToken[\"kind\"] {\n  if (/^[\"'`]/.test(value)) return \"string\";\n  if (/^\\d/.test(value)) return \"number\";\n  if (/^(?:true|false|null|undefined)$/.test(value)) return \"literal\";\n  return \"keyword\";\n}\n\nfunction tokenizeJsonLikeLine(line: string): CodeToken[] {\n  return tokenizeByPattern(\n    line,\n    /(\"(?:\\\\.|[^\"\\\\])*\"|\\b(?:true|false|null)\\b|-?\\b\\d+(?:\\.\\d+)?(?:e[+-]?\\d+)?\\b)/gi,\n    (value) => {\n      if (/^\"/.test(value)) return \"string\";\n      if (/^(?:true|false|null)$/i.test(value)) return \"literal\";\n      return \"number\";\n    },\n  );\n}\n\nfunction tokenizeYamlLine(line: string): CodeToken[] {\n  const commentIndex = line.indexOf(\"#\");\n  const codePart = commentIndex >= 0 ? line.slice(0, commentIndex) : line;\n  const commentPart = commentIndex >= 0 ? line.slice(commentIndex) : \"\";\n  return [\n    ...tokenizeByPattern(\n      codePart,\n      /(\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'|\\b(?:true|false|null)\\b|-?\\b\\d+(?:\\.\\d+)?\\b)/gi,\n      (value) => {\n        if (/^[\"']/.test(value)) return \"string\";\n        if (/^(?:true|false|null)$/i.test(value)) return \"literal\";\n        return \"number\";\n      },\n    ),\n    ...(commentPart ? [{ kind: \"comment\" as const, value: commentPart }] : []),\n  ];\n}\n\nfunction tokenizeShellLine(line: string): CodeToken[] {\n  const commentIndex = line.search(/(^|\\s)#/);\n  const codePart = commentIndex >= 0 ? line.slice(0, commentIndex) : line;\n  const commentPart = commentIndex >= 0 ? line.slice(commentIndex) : \"\";\n  return [\n    ...tokenizeByPattern(\n      codePart,\n      /(\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'|\\b(?:cd|cp|curl|echo|export|git|grep|mkdir|mv|node|npm|pnpm|rm|sed|test|yarn)\\b)/g,\n      (value) => (/^[\"']/.test(value) ? \"string\" : \"keyword\"),\n    ),\n    ...(commentPart ? [{ kind: \"comment\" as const, value: commentPart }] : []),\n  ];\n}\n\nfunction tokenizeDiffLine(line: string): CodeToken[] {\n  if (line.startsWith(\"+\") && !line.startsWith(\"+++\")) {\n    return [{ kind: \"literal\", value: line }];\n  }\n  if (line.startsWith(\"-\") && !line.startsWith(\"---\")) {\n    return [{ kind: \"comment\", value: line }];\n  }\n  return [{ kind: \"plain\", value: line }];\n}\n\nfunction tokenizeByPattern(\n  line: string,\n  pattern: RegExp,\n  classify: (value: string) => CodeToken[\"kind\"],\n): CodeToken[] {\n  const tokens: CodeToken[] = [];\n  let cursor = 0;\n  for (const match of line.matchAll(pattern)) {\n    const index = match.index ?? 0;\n    if (index > cursor) {\n      tokens.push({ kind: \"plain\", value: line.slice(cursor, index) });\n    }\n    const value = match[0];\n    tokens.push({ kind: classify(value), value });\n    cursor = index + value.length;\n  }\n  if (cursor < line.length) {\n    tokens.push({ kind: \"plain\", value: line.slice(cursor) });\n  }\n  return tokens;\n}\n\nfunction codeTokenClassName(kind: CodeToken[\"kind\"]) {\n  switch (kind) {\n    case \"comment\":\n      return \"text-muted-foreground italic\";\n    case \"keyword\":\n      return \"font-semibold text-sky-700 dark:text-sky-300\";\n    case \"literal\":\n      return \"text-purple-700 dark:text-purple-300\";\n    case \"number\":\n      return \"text-amber-700 dark:text-amber-300\";\n    case \"string\":\n      return \"text-emerald-700 dark:text-emerald-300\";\n    default:\n      return \"\";\n  }\n}\n",
      "type": "registry:ui",
      "target": "@ui/markdown-greenfield-code-highlight.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/markdown-greenfield-diagram.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Check, Clipboard } from \"lucide-react\";\nimport { useKeyedLayoutEffect } from \"@/hooks/use-keyed-layout-effect\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\nimport {\n  MERMAID_VIEWER_STYLES,\n  describeDiagram,\n  estimateDiagramBodyHeight,\n  readDiagramLimitMessage,\n  renderDiagram,\n  type DiagramState,\n} from \"@/components/ui/mermaid-renderer\";\n\nexport { resetMermaidRendererForTests as resetMarkdownMermaidRendererForTests } from \"@/components/ui/mermaid-renderer\";\n\nconst MERMAID_VIEWPORT_ROOT_MARGIN = \"640px 0px\";\n\n// A per-instance async store for one diagram's mermaid render. Kept per\n// component (not module-global) so instances never share state — no cross-\n// render notify fan-out and no shared cache keyed by a render-order id.\ntype MermaidDiagramStore = {\n  key: string;\n  result: DiagramState | null;\n  listeners: Set<() => void>;\n};\n\nexport function MarkdownGreenfieldDiagram({\n  caption,\n  componentName,\n  onContentReady,\n  source,\n  title,\n}: {\n  caption?: string;\n  componentName?: string;\n  onContentReady?: () => void;\n  source: string;\n  title?: string;\n}) {\n  const limitMessage = React.useMemo(\n    () => readDiagramLimitMessage(source),\n    [source],\n  );\n  const diagramId = React.useId().replace(/:/g, \"\");\n  const elementId = `markdown-diagram-${diagramId}`;\n  const description = React.useMemo(() => describeDiagram(source), [source]);\n  const bodyHeight = React.useMemo(\n    () => estimateDiagramBodyHeight(source),\n    [source],\n  );\n  const descriptionId = description\n    ? `markdown-diagram-description-${diagramId}`\n    : undefined;\n  const captionId = caption\n    ? `markdown-diagram-caption-${diagramId}`\n    : undefined;\n  const describedBy =\n    [descriptionId, captionId].filter(Boolean).join(\" \") || undefined;\n  const [figureRef, isRenderEligible] = useMermaidRenderEligibility({\n    disabled: Boolean(limitMessage),\n    source,\n  });\n\n  // Derive the displayed state: a limit message fails immediately; otherwise the\n  // state follows this instance's async mermaid store (loading until resolved).\n  const storeRef = React.useRef<MermaidDiagramStore | null>(null);\n  if (storeRef.current === null) {\n    storeRef.current = { key: \"\", listeners: new Set(), result: null };\n  }\n  const store = storeRef.current;\n  const renderKey =\n    limitMessage || !isRenderEligible ? \"\" : `${elementId}\\0${source}`;\n  const subscribe = React.useCallback(\n    (onStoreChange: () => void) => {\n      store.listeners.add(onStoreChange);\n      // Kick off (or restart on source change) the render here, in the store\n      // subscription, so no effect is needed.\n      if (!limitMessage && store.key !== renderKey) {\n        store.key = renderKey;\n        store.result = null;\n        void renderDiagram(source, elementId).then((result) => {\n          if (store.key !== renderKey) return;\n          store.result = result;\n          for (const listener of store.listeners) listener();\n        });\n      }\n      return () => {\n        store.listeners.delete(onStoreChange);\n      };\n    },\n    [elementId, limitMessage, renderKey, source, store],\n  );\n  const getSnapshot = React.useCallback(\n    () => (store.key === renderKey ? store.result : null),\n    [renderKey, store],\n  );\n  const resolvedState = React.useSyncExternalStore(\n    subscribe,\n    getSnapshot,\n    () => null,\n  );\n  const state: DiagramState = limitMessage\n    ? { status: \"failed\", message: limitMessage }\n    : (resolvedState ?? { status: \"loading\" });\n\n  useKeyedLayoutEffect(\n    joinEffectKey([bodyHeight, onContentReady, state.status]),\n    () => {\n      onContentReady?.();\n    },\n  );\n\n  return (\n    <figure\n      ref={figureRef}\n      aria-describedby={describedBy}\n      aria-label={title || \"Mermaid diagram\"}\n      className=\"group bg-muted/30 my-5 min-h-40 overflow-hidden rounded-md border\"\n      data-diagram-language=\"mermaid\"\n      data-diagram-renderer={\n        state.status === \"ready\" ? state.renderer : undefined\n      }\n      data-diagram-reserved-height={bodyHeight}\n      data-diagram-state={state.status}\n      data-pretext-component={componentName}\n      role=\"group\"\n      style={\n        {\n          \"--pretext-diagram-body-height\": `${bodyHeight}px`,\n        } as React.CSSProperties\n      }\n    >\n      <div className=\"bg-muted/60 flex h-9 items-center gap-1 border-b px-3\">\n        <span className=\"text-muted-foreground min-w-0 truncate text-xs font-medium\">\n          {title || \"mermaid\"}\n        </span>\n        <DiagramCopyButton\n          ariaLabel=\"Copy diagram source\"\n          className=\"ml-auto\"\n          text={source}\n        />\n        {state.status === \"ready\" ? (\n          <DiagramCopyButton ariaLabel=\"Copy diagram SVG\" text={state.svg} />\n        ) : null}\n      </div>\n      {description ? (\n        <p\n          className=\"sr-only\"\n          data-pretext-diagram-description=\"\"\n          id={descriptionId}\n        >\n          {description}\n        </p>\n      ) : null}\n      <div\n        aria-label=\"Mermaid diagram body\"\n        className=\"h-(--pretext-diagram-body-height) overflow-auto p-4\"\n        data-pretext-diagram-body=\"\"\n        onKeyDown={(event) => {\n          const element = event.currentTarget;\n          if (event.key === \"ArrowRight\") {\n            element.scrollLeft += 50;\n            event.preventDefault();\n          } else if (event.key === \"ArrowLeft\") {\n            element.scrollLeft -= 50;\n            event.preventDefault();\n          } else if (event.key === \"End\") {\n            element.scrollLeft = Math.max(\n              0,\n              element.scrollWidth - element.clientWidth,\n            );\n            event.preventDefault();\n          } else if (event.key === \"Home\") {\n            element.scrollLeft = 0;\n            event.preventDefault();\n          }\n        }}\n        role=\"region\"\n        tabIndex={0}\n      >\n        {state.status === \"ready\" ? (\n          <>\n            <style data-pretext-mermaid-styles=\"\">\n              {MERMAID_VIEWER_STYLES}\n            </style>\n            <div\n              className=\"text-foreground\"\n              data-pretext-mermaid-svg=\"\"\n              dangerouslySetInnerHTML={{ __html: state.svg }}\n            />\n          </>\n        ) : (\n          <>\n            {state.status === \"failed\" ? (\n              <p\n                className=\"border-destructive/25 bg-destructive/10 text-destructive mb-3 rounded border px-3 py-2 text-sm\"\n                data-pretext-diagram-error=\"\"\n                role=\"alert\"\n              >\n                {state.message}\n              </p>\n            ) : null}\n            <pre\n              aria-label=\"Mermaid diagram source\"\n              className=\"text-muted-foreground m-0 overflow-x-auto font-mono text-[0.82em] leading-relaxed\"\n              tabIndex={0}\n            >\n              {source}\n            </pre>\n          </>\n        )}\n      </div>\n      {caption ? (\n        <figcaption\n          className=\"bg-muted/30 text-muted-foreground border-t px-3 py-2 text-sm\"\n          data-pretext-diagram-caption=\"\"\n          id={captionId}\n        >\n          {caption}\n        </figcaption>\n      ) : null}\n    </figure>\n  );\n}\n\nfunction useMermaidRenderEligibility({\n  disabled,\n  source,\n}: {\n  disabled: boolean;\n  source: string;\n}) {\n  const ref = React.useRef<HTMLElement | null>(null);\n  const [isEligible, setIsEligible] = React.useState(\n    () => !disabled && typeof IntersectionObserver === \"undefined\",\n  );\n\n  useKeyedLayoutEffect(joinEffectKey([disabled, source]), () => {\n    if (disabled) {\n      setIsEligible(false);\n      return;\n    }\n    if (typeof IntersectionObserver === \"undefined\") {\n      setIsEligible(true);\n      return;\n    }\n    const element = ref.current;\n    if (!element) return;\n    const observer = new IntersectionObserver(\n      (entries) => {\n        const entry = entries[0];\n        if (!entry?.isIntersecting && (entry?.intersectionRatio ?? 0) <= 0) {\n          return;\n        }\n        setIsEligible(true);\n        observer.disconnect();\n      },\n      { root: null, rootMargin: MERMAID_VIEWPORT_ROOT_MARGIN },\n    );\n    observer.observe(element);\n    return () => observer.disconnect();\n  });\n\n  return [ref, isEligible] as const;\n}\n\nfunction DiagramCopyButton({\n  ariaLabel,\n  className,\n  text,\n}: {\n  ariaLabel: string;\n  className?: string;\n  text: string;\n}) {\n  const [isCopied, setIsCopied] = React.useState(false);\n\n  return (\n    <button\n      aria-label={isCopied ? \"Copied\" : ariaLabel}\n      className={[\n        \"text-muted-foreground hover:bg-background hover:text-foreground focus-visible:ring-ring inline-flex size-7 items-center justify-center rounded-md transition focus-visible:ring-2 focus-visible:outline-none\",\n        className,\n      ]\n        .filter(Boolean)\n        .join(\" \")}\n      type=\"button\"\n      onClick={() => {\n        void navigator.clipboard?.writeText(text).then(() => {\n          setIsCopied(true);\n          window.setTimeout(() => setIsCopied(false), 1200);\n        });\n      }}\n    >\n      {isCopied ? (\n        <Check aria-hidden=\"true\" className=\"size-3.5\" />\n      ) : (\n        <Clipboard aria-hidden=\"true\" className=\"size-3.5\" />\n      )}\n    </button>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/markdown-greenfield-diagram.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/markdown-greenfield-document.ts",
      "content": "\"use client\";\n\nimport type {\n  MarkdownHastElement,\n  MarkdownHastNode,\n} from \"./markdown-hast-types\";\nimport {\n  markdownSourceRangeFromPosition,\n  markdownSourceTextForRange,\n  type MarkdownSourceRange,\n} from \"./markdown-source-map\";\nimport {\n  createMarkdownUnifiedDocument,\n  type MarkdownUnifiedDocument,\n} from \"./markdown-unified-pipeline\";\n\nconst TARGET_CHUNK_SOURCE_LINES = 42;\nconst MAX_CHUNK_SOURCE_LINES = 64;\nconst HOSTILE_CODE_LINE_COUNT = 400;\nconst HOSTILE_HAST_NODE_COUNT = 3_000;\nconst HOSTILE_HAST_DEPTH = 80;\nconst HOSTILE_TEXT_LENGTH = 20_000;\nconst HOSTILE_TABLE_CELL_COUNT = 50_000;\nconst HOSTILE_TABLE_HAST_NODE_COUNT = 120_000;\nconst HOSTILE_TABLE_TEXT_LENGTH = 1_000_000;\nconst DOCUMENT_CACHE_LIMIT = 24;\n\nconst markdownGreenfieldDocumentCache = new Map<\n  string,\n  {\n    document: MarkdownGreenfieldDocument;\n    text: string;\n  }\n>();\n\nexport type MarkdownGreenfieldBlockKind =\n  | \"blockquote\"\n  | \"code\"\n  | \"component\"\n  | \"diagram\"\n  | \"footnotes\"\n  | \"frontmatter\"\n  | \"heading\"\n  | \"html\"\n  | \"image\"\n  | \"list\"\n  | \"math\"\n  | \"paragraph\"\n  | \"table\"\n  | \"thematicBreak\"\n  | \"unknown\";\n\nexport type MarkdownGreenfieldDocument = {\n  blocks: MarkdownGreenfieldBlock[];\n  chunks: MarkdownGreenfieldChunk[];\n  fragmentTargets: MarkdownGreenfieldFragmentTarget[];\n  headings: MarkdownGreenfieldHeading[];\n  lineCount: number;\n  text: string;\n  unified: MarkdownUnifiedDocument;\n  wordCount: number;\n};\n\nexport type MarkdownGreenfieldHeading = {\n  blockId: string;\n  id: string;\n  sourceLine: number;\n  text: string;\n};\n\nexport type MarkdownGreenfieldFragmentTarget = {\n  blockId: string;\n  id: string;\n  sourceLine: number;\n};\n\nexport type MarkdownGreenfieldBlock = {\n  hastChildren: MarkdownHastNode[];\n  id: string;\n  index: number;\n  isGenerated: boolean;\n  isHostile: boolean;\n  kind: MarkdownGreenfieldBlockKind;\n  sourceLineCount: number;\n  sourceLineLengths: readonly number[];\n  sourceRange: MarkdownSourceRange | null;\n  sourceText: string;\n};\n\nexport type MarkdownGreenfieldChunk = {\n  blockIds: string[];\n  hastChildren: MarkdownHastNode[];\n  id: string;\n  index: number;\n  isHostile: boolean;\n  nativeFindText: string;\n  sourceEndLine: number;\n  sourceLineCount: number;\n  sourceRange: MarkdownSourceRange | null;\n  sourceStartLine: number;\n  sourceText: string;\n};\n\nexport function createMarkdownGreenfieldDocument(\n  markdown: string,\n): MarkdownGreenfieldDocument {\n  const text = markdown.length ? markdown : \" \";\n  const cacheKey = markdownGreenfieldDocumentTextKey(text);\n  const cached = markdownGreenfieldDocumentCache.get(cacheKey);\n  if (cached?.text === text) {\n    markdownGreenfieldDocumentCache.delete(cacheKey);\n    markdownGreenfieldDocumentCache.set(cacheKey, cached);\n    return cached.document;\n  }\n\n  const document = createUncachedMarkdownGreenfieldDocument(text);\n  markdownGreenfieldDocumentCache.set(cacheKey, {\n    document,\n    text,\n  });\n  while (markdownGreenfieldDocumentCache.size > DOCUMENT_CACHE_LIMIT) {\n    const oldestKey = markdownGreenfieldDocumentCache.keys().next().value;\n    if (!oldestKey) break;\n    markdownGreenfieldDocumentCache.delete(oldestKey);\n  }\n\n  return document;\n}\n\nfunction createUncachedMarkdownGreenfieldDocument(\n  text: string,\n): MarkdownGreenfieldDocument {\n  const unified = createMarkdownUnifiedDocument(text);\n  normalizeMarkdownGreenfieldHeadingIds(unified.hast.children, unified);\n  normalizeMarkdownGreenfieldTables(unified.hast.children);\n  annotateMarkdownGreenfieldSourceMetadata(unified.hast.children, unified);\n  const blocks = createMarkdownGreenfieldBlocks({ text, unified });\n  const chunks = createMarkdownGreenfieldChunks({ blocks, text });\n  const headings = createMarkdownGreenfieldHeadings(blocks);\n  const fragmentTargets = createMarkdownGreenfieldFragmentTargets({\n    blocks,\n    unified,\n  });\n\n  return freezeMarkdownGreenfieldDocument({\n    blocks,\n    chunks,\n    fragmentTargets,\n    headings,\n    lineCount: unified.sourceMap.lineCount,\n    text,\n    unified,\n    wordCount: text.trim().split(/\\s+/).filter(Boolean).length,\n  });\n}\n\nexport function findMarkdownGreenfieldHeadingById(\n  document: MarkdownGreenfieldDocument,\n  headingId: string,\n) {\n  const normalizedId = headingId.replace(/^#/, \"\");\n  return document.headings.find((heading) => heading.id === normalizedId);\n}\n\nexport function findMarkdownGreenfieldFragmentTargetById(\n  document: MarkdownGreenfieldDocument,\n  fragmentId: string,\n) {\n  const normalizedId = normalizeFragmentTargetId(fragmentId);\n  return document.fragmentTargets.find((target) => target.id === normalizedId);\n}\n\nexport function findMarkdownGreenfieldBlockById(\n  document: MarkdownGreenfieldDocument,\n  blockId: string,\n) {\n  return document.blocks.find((block) => block.id === blockId) ?? null;\n}\n\nexport function findMarkdownGreenfieldChunkByBlockId(\n  document: MarkdownGreenfieldDocument,\n  blockId: string,\n) {\n  return (\n    document.chunks.find((chunk) => chunk.blockIds.includes(blockId)) ?? null\n  );\n}\n\nexport function findMarkdownGreenfieldBlockBySourceLine(\n  document: MarkdownGreenfieldDocument,\n  sourceLine: number,\n) {\n  const line = clampSourceLine(sourceLine, document.lineCount);\n  return (\n    document.blocks.find((block) => {\n      const range = block.sourceRange;\n      return range && range.startLine <= line && range.endLine >= line;\n    }) ?? null\n  );\n}\n\nexport function findMarkdownGreenfieldChunkBySourceLine(\n  document: MarkdownGreenfieldDocument,\n  sourceLine: number,\n) {\n  const block = findMarkdownGreenfieldBlockBySourceLine(document, sourceLine);\n  if (block) return findMarkdownGreenfieldChunkByBlockId(document, block.id);\n\n  const line = clampSourceLine(sourceLine, document.lineCount);\n  return (\n    document.chunks.find(\n      (chunk) => chunk.sourceStartLine <= line && chunk.sourceEndLine >= line,\n    ) ?? null\n  );\n}\n\nexport function findMarkdownGreenfieldBlockBySourceOffset(\n  document: MarkdownGreenfieldDocument,\n  sourceOffset: number,\n) {\n  const offset = clampSourceOffset(sourceOffset, document.text.length);\n  return (\n    document.blocks.find((block) => {\n      const range = block.sourceRange;\n      return range && range.startOffset <= offset && range.endOffset > offset;\n    }) ?? null\n  );\n}\n\nexport function findMarkdownGreenfieldChunkBySourceOffset(\n  document: MarkdownGreenfieldDocument,\n  sourceOffset: number,\n) {\n  const block = findMarkdownGreenfieldBlockBySourceOffset(\n    document,\n    sourceOffset,\n  );\n  return block\n    ? findMarkdownGreenfieldChunkByBlockId(document, block.id)\n    : null;\n}\n\nfunction createMarkdownGreenfieldBlocks({\n  text,\n  unified,\n}: {\n  text: string;\n  unified: MarkdownUnifiedDocument;\n}) {\n  const blocks: MarkdownGreenfieldBlock[] = [];\n\n  for (const child of unified.hast.children) {\n    if (isWhitespaceText(child)) continue;\n\n    const directSourceRange = markdownSourceRangeFromPosition({\n      position: child.position,\n      sourceMap: unified.sourceMap,\n    });\n    const sourceRange =\n      directSourceRange ?? markdownSyntheticSourceRangeForNode(child, unified);\n    const kind = markdownBlockKindForHastChild(child);\n    const sourceText = markdownSourceTextForRange({\n      range: sourceRange,\n      sourceMap: unified.sourceMap,\n    });\n    const normalizedSourceText =\n      sourceText ||\n      (sourceRange\n        ? text.slice(sourceRange.startOffset, sourceRange.endOffset)\n        : \"\");\n    const sourceMetrics =\n      markdownGreenfieldSourceMetricsForText(normalizedSourceText);\n    const line = sourceRange?.startLine ?? unified.sourceMap.lineCount;\n    const block: MarkdownGreenfieldBlock = {\n      hastChildren: [child],\n      id: `block-${blocks.length + 1}-${line}-${kind}`,\n      index: blocks.length,\n      isGenerated: !directSourceRange,\n      isHostile: isHostileMarkdownGreenfieldBlock({\n        child,\n        kind,\n        sourceLineCount: sourceMetrics.sourceLineCount,\n        sourceText: normalizedSourceText,\n      }),\n      kind,\n      sourceLineCount: sourceMetrics.sourceLineCount,\n      sourceLineLengths: sourceMetrics.sourceLineLengths,\n      sourceRange,\n      sourceText: normalizedSourceText,\n    };\n    blocks.push(block);\n  }\n\n  if (!blocks.length) {\n    const sourceMetrics = markdownGreenfieldSourceMetricsForText(text);\n    blocks.push({\n      hastChildren: [],\n      id: \"block-1-empty\",\n      index: 0,\n      isGenerated: true,\n      isHostile: false,\n      kind: \"paragraph\",\n      sourceLineCount: sourceMetrics.sourceLineCount,\n      sourceLineLengths: sourceMetrics.sourceLineLengths,\n      sourceRange: {\n        endLine: 1,\n        endOffset: text.length,\n        startLine: 1,\n        startOffset: 0,\n      },\n      sourceText: text,\n    });\n  }\n\n  return blocks;\n}\n\nfunction createMarkdownGreenfieldChunks({\n  blocks,\n  text,\n}: {\n  blocks: readonly MarkdownGreenfieldBlock[];\n  text: string;\n}) {\n  const chunks: MarkdownGreenfieldChunk[] = [];\n  let current: MarkdownGreenfieldBlock[] = [];\n\n  const flush = () => {\n    if (!current.length) return;\n    chunks.push(createChunk(current, chunks.length, text));\n    current = [];\n  };\n\n  for (const block of blocks) {\n    if (block.isHostile) {\n      flush();\n      current.push(block);\n      flush();\n      continue;\n    }\n\n    const nextLineCount = lineCountForBlocks(current, block);\n    const startsNewChunk =\n      current.length > 0 &&\n      block.kind === \"heading\" &&\n      nextLineCount >= TARGET_CHUNK_SOURCE_LINES;\n    const exceedsMax =\n      current.length > 0 && nextLineCount > MAX_CHUNK_SOURCE_LINES;\n\n    if (startsNewChunk || exceedsMax) flush();\n    current.push(block);\n  }\n\n  flush();\n  return chunks;\n}\n\nfunction createChunk(\n  blocks: readonly MarkdownGreenfieldBlock[],\n  index: number,\n  text: string,\n): MarkdownGreenfieldChunk {\n  const ranges = blocks\n    .map((block) => block.sourceRange)\n    .filter((range): range is MarkdownSourceRange => Boolean(range));\n  const sourceRange = ranges.length\n    ? {\n        endLine: Math.max(...ranges.map((range) => range.endLine)),\n        endOffset: Math.max(...ranges.map((range) => range.endOffset)),\n        startLine: Math.min(...ranges.map((range) => range.startLine)),\n        startOffset: Math.min(...ranges.map((range) => range.startOffset)),\n      }\n    : null;\n  const sourceStartLine =\n    sourceRange?.startLine ?? blocks[0]?.sourceRange?.startLine ?? 1;\n  const sourceEndLine =\n    sourceRange?.endLine ??\n    blocks[blocks.length - 1]?.sourceRange?.endLine ??\n    sourceStartLine;\n  const sourceLineCount = sourceRange\n    ? sourceEndLine - sourceStartLine + 1\n    : Math.max(\n        1,\n        blocks.reduce((sum, block) => sum + block.sourceLineCount, 0),\n      );\n  const hastChildren = blocks.flatMap((block) => block.hastChildren);\n\n  return {\n    blockIds: blocks.map((block) => block.id),\n    hastChildren,\n    id: `chunk-${index + 1}-${sourceStartLine}`,\n    index,\n    isHostile: blocks.some((block) => block.isHostile),\n    nativeFindText: nativeFindTextForHastChildren(hastChildren),\n    sourceEndLine,\n    sourceLineCount,\n    sourceRange,\n    sourceStartLine,\n    sourceText: sourceRange\n      ? text.slice(sourceRange.startOffset, sourceRange.endOffset)\n      : \"\",\n  };\n}\n\nfunction createMarkdownGreenfieldHeadings(\n  blocks: readonly MarkdownGreenfieldBlock[],\n) {\n  return blocks.flatMap((block) => {\n    const element = readHastElement(block.hastChildren[0]);\n    if (!element || !/^h[1-6]$/.test(element.tagName)) return [];\n\n    const id = readStringProperty(element.properties?.id);\n    if (!id) return [];\n\n    return [\n      {\n        blockId: block.id,\n        id,\n        sourceLine: block.sourceRange?.startLine ?? 1,\n        text: extractHastText(element).trim(),\n      },\n    ];\n  });\n}\n\nfunction createMarkdownGreenfieldFragmentTargets({\n  blocks,\n  unified,\n}: {\n  blocks: readonly MarkdownGreenfieldBlock[];\n  unified: MarkdownUnifiedDocument;\n}) {\n  const targets = new Map<string, MarkdownGreenfieldFragmentTarget>();\n\n  for (const block of blocks) {\n    for (const child of block.hastChildren) {\n      collectFragmentTargets({\n        block,\n        node: child,\n        targets,\n        unified,\n      });\n    }\n  }\n\n  return Array.from(targets.values());\n}\n\nfunction collectFragmentTargets({\n  block,\n  node,\n  targets,\n  unified,\n}: {\n  block: MarkdownGreenfieldBlock;\n  node: MarkdownHastNode;\n  targets: Map<string, MarkdownGreenfieldFragmentTarget>;\n  unified: MarkdownUnifiedDocument;\n}) {\n  const element = readHastElement(node);\n  if (!element) return;\n\n  const id = readStringProperty(element.properties?.id);\n  if (id) {\n    const sourceRange =\n      markdownSourceRangeFromPosition({\n        position: element.position,\n        sourceMap: unified.sourceMap,\n      }) ?? block.sourceRange;\n    const target = {\n      blockId: block.id,\n      id,\n      sourceLine: sourceRange?.startLine ?? unified.sourceMap.lineCount,\n    };\n\n    for (const alias of fragmentTargetAliases(id)) {\n      if (!targets.has(alias)) {\n        targets.set(alias, { ...target, id: alias });\n      }\n    }\n  }\n\n  for (const child of element.children) {\n    collectFragmentTargets({ block, node: child, targets, unified });\n  }\n}\n\nfunction markdownSyntheticSourceRangeForNode(\n  node: MarkdownHastNode,\n  unified: MarkdownUnifiedDocument,\n): MarkdownSourceRange | null {\n  const ranges = collectMarkdownSourceRanges(node, unified);\n  if (!ranges.length) return null;\n  return {\n    endLine: Math.max(...ranges.map((range) => range.endLine)),\n    endOffset: Math.max(...ranges.map((range) => range.endOffset)),\n    startLine: Math.min(...ranges.map((range) => range.startLine)),\n    startOffset: Math.min(...ranges.map((range) => range.startOffset)),\n  };\n}\n\nfunction collectMarkdownSourceRanges(\n  node: MarkdownHastNode,\n  unified: MarkdownUnifiedDocument,\n): MarkdownSourceRange[] {\n  const range = markdownSourceRangeFromPosition({\n    position: node.position,\n    sourceMap: unified.sourceMap,\n  });\n  const element = readHastElement(node);\n  return [\n    ...(range ? [range] : []),\n    ...(element?.children ?? []).flatMap((child) =>\n      collectMarkdownSourceRanges(child, unified),\n    ),\n  ];\n}\n\nfunction normalizeMarkdownGreenfieldHeadingIds(\n  nodes: readonly MarkdownHastNode[],\n  unified: MarkdownUnifiedDocument,\n) {\n  const usedIds = new Map<string, number>();\n  for (const node of nodes) {\n    normalizeHeadingIdsInNode(node, usedIds, unified);\n  }\n}\n\nfunction normalizeHeadingIdsInNode(\n  node: MarkdownHastNode,\n  usedIds: Map<string, number>,\n  unified: MarkdownUnifiedDocument,\n) {\n  const element = readHastElement(node);\n  if (!element) return;\n\n  if (/^h[1-6]$/.test(element.tagName)) {\n    const visibleText = extractHastText(element);\n    const markdownText = markdownHeadingTextFromSource(element, unified);\n    const baseId = safeHeadingIdForText(\n      markdownText.includes(\"__proto__\") ? markdownText : visibleText,\n    );\n    const duplicateCount = usedIds.get(baseId) ?? 0;\n    usedIds.set(baseId, duplicateCount + 1);\n    element.properties = {\n      ...element.properties,\n      id: duplicateCount === 0 ? baseId : `${baseId}-${duplicateCount}`,\n    };\n  }\n\n  for (const child of element.children) {\n    normalizeHeadingIdsInNode(child, usedIds, unified);\n  }\n}\n\nfunction markdownHeadingTextFromSource(\n  element: MarkdownHastElement,\n  unified: MarkdownUnifiedDocument,\n) {\n  const range = markdownSourceRangeFromPosition({\n    position: element.position,\n    sourceMap: unified.sourceMap,\n  });\n  if (!range) return \"\";\n  return markdownSourceTextForRange({\n    range,\n    sourceMap: unified.sourceMap,\n  })\n    .replace(/^\\s{0,3}#{1,6}[ \\t]*/, \"\")\n    .replace(/[ \\t]+#*\\s*$/, \"\")\n    .trim();\n}\n\nfunction safeHeadingIdForText(text: string) {\n  const slug =\n    text\n      .toLowerCase()\n      .normalize(\"NFKD\")\n      .replace(/[\\u0300-\\u036f]/g, \"\")\n      .replace(/[^\\w\\s-]/g, \"\")\n      .trim()\n      .replace(/\\s+/g, \"-\")\n      .replace(/-+/g, \"-\") || \"section\";\n\n  return isDomClobberingId(slug) ? `section-${slug}` : slug;\n}\n\nfunction normalizeFragmentTargetId(fragmentId: string) {\n  return decodeURIComponent(fragmentId.replace(/^#/, \"\"));\n}\n\nfunction fragmentTargetAliases(id: string) {\n  const aliases = new Set<string>([normalizeFragmentTargetId(id)]);\n  const withoutRepeatedClobberPrefix = id.replace(\n    /^(user-content-)+/,\n    \"user-content-\",\n  );\n  aliases.add(withoutRepeatedClobberPrefix);\n  aliases.add(withoutRepeatedClobberPrefix.replace(/^user-content-/, \"\"));\n  return Array.from(aliases).filter(Boolean);\n}\n\nfunction normalizeMarkdownGreenfieldTables(nodes: readonly MarkdownHastNode[]) {\n  let tableIndex = 0;\n  for (const node of nodes) {\n    tableIndex = normalizeTablesInNode(node, tableIndex);\n  }\n}\n\nfunction normalizeTablesInNode(node: MarkdownHastNode, tableIndex: number) {\n  const element = readHastElement(node);\n  if (!element) return tableIndex;\n\n  if (element.tagName === \"table\") {\n    normalizeTableElement(element, tableIndex);\n    tableIndex += 1;\n  }\n\n  for (const child of element.children) {\n    tableIndex = normalizeTablesInNode(child, tableIndex);\n  }\n  return tableIndex;\n}\n\nfunction normalizeTableElement(table: MarkdownHastElement, tableIndex: number) {\n  const rows = tableRows(table);\n  const headerIds = new Map<number, string>();\n\n  rows.forEach((row, rowIndex) => {\n    row.properties = {\n      ...row.properties,\n      ariaRowIndex: rowIndex + 1,\n      dataPretextTableRowIndex: rowIndex + 1,\n    };\n\n    tableCells(row).forEach((cell, columnIndex) => {\n      const column = columnIndex + 1;\n      const properties = {\n        ...cell.properties,\n        ariaColIndex: column,\n        dataPretextTableColumnIndex: column,\n      };\n      if (cell.tagName === \"th\") {\n        const id = `markdown-table-${tableIndex + 1}-column-${column}`;\n        headerIds.set(columnIndex, id);\n        cell.properties = {\n          ...properties,\n          id,\n          scope: \"col\",\n        };\n      } else {\n        cell.properties = {\n          ...properties,\n          headers: headerIds.get(columnIndex),\n        };\n      }\n    });\n  });\n}\n\nfunction annotateMarkdownGreenfieldSourceMetadata(\n  nodes: readonly MarkdownHastNode[],\n  unified: MarkdownUnifiedDocument,\n) {\n  for (const node of nodes) {\n    annotateSourceMetadataInNode(node, unified);\n  }\n}\n\nfunction annotateSourceMetadataInNode(\n  node: MarkdownHastNode,\n  unified: MarkdownUnifiedDocument,\n) {\n  const element = readHastElement(node);\n  if (!element) return;\n\n  const sourceRange = markdownSourceRangeFromPosition({\n    position: element.position,\n    sourceMap: unified.sourceMap,\n  });\n  if (sourceRange) {\n    element.properties = {\n      ...element.properties,\n      dataPretextSourceEndLine: sourceRange.endLine,\n      dataPretextSourceEndOffset: sourceRange.endOffset,\n      dataPretextSourceStartLine: sourceRange.startLine,\n      dataPretextSourceStartOffset: sourceRange.startOffset,\n    };\n  }\n\n  for (const child of element.children) {\n    annotateSourceMetadataInNode(child, unified);\n  }\n}\n\nfunction tableRows(element: MarkdownHastElement) {\n  const rows: MarkdownHastElement[] = [];\n  for (const child of element.children) {\n    const childElement = readHastElement(child);\n    if (!childElement) continue;\n    if (childElement.tagName === \"tr\") {\n      rows.push(childElement);\n    } else if (\n      childElement.tagName === \"thead\" ||\n      childElement.tagName === \"tbody\" ||\n      childElement.tagName === \"tfoot\"\n    ) {\n      rows.push(...tableRows(childElement));\n    }\n  }\n  return rows;\n}\n\nfunction tableCells(row: MarkdownHastElement) {\n  return row.children\n    .map(readHastElement)\n    .filter(\n      (child): child is MarkdownHastElement =>\n        child?.tagName === \"td\" || child?.tagName === \"th\",\n    );\n}\n\nfunction isDomClobberingId(id: string) {\n  return [\n    \"__proto__\",\n    \"constructor\",\n    \"document\",\n    \"forms\",\n    \"history\",\n    \"location\",\n    \"name\",\n    \"prototype\",\n    \"window\",\n  ].includes(id);\n}\n\nfunction markdownBlockKindForHastChild(\n  child: MarkdownHastNode,\n): MarkdownGreenfieldBlockKind {\n  const element = readHastElement(child);\n  if (!element) return child.type === \"text\" ? \"paragraph\" : \"unknown\";\n\n  if (element.properties?.dataFootnotes != null) return \"footnotes\";\n  if (element.properties?.dataMarkdownFrontmatter != null) return \"frontmatter\";\n  if (/^h[1-6]$/.test(element.tagName)) return \"heading\";\n  if (isMarkdownDiagramElement(element)) return \"diagram\";\n  if (isMarkdownComponentElement(element)) return \"component\";\n  if (isDisplayMathElement(element)) return \"math\";\n\n  switch (element.tagName) {\n    case \"blockquote\":\n      return \"blockquote\";\n    case \"hr\":\n      return \"thematicBreak\";\n    case \"ol\":\n    case \"ul\":\n      return \"list\";\n    case \"p\":\n      if (isDisplayMathElement(element)) return \"math\";\n      return firstElementChild(element)?.tagName === \"img\"\n        ? \"image\"\n        : \"paragraph\";\n    case \"pre\":\n      if (isMermaidCodeElement(element)) return \"diagram\";\n      return \"code\";\n    case \"table\":\n      return \"table\";\n    default:\n      return \"html\";\n  }\n}\n\nfunction isMarkdownDiagramElement(element: MarkdownHastElement) {\n  return (\n    readStringProperty(element.properties?.dataPretextComponentName) ===\n    \"Diagram\"\n  );\n}\n\nfunction isMarkdownComponentElement(element: MarkdownHastElement) {\n  return (\n    element.properties?.dataPretextComponentName != null ||\n    element.properties?.dataPretextComponentFallback != null ||\n    element.properties?.dataPretextCalloutKind != null\n  );\n}\n\nfunction isDisplayMathElement(element: MarkdownHastElement): boolean {\n  if (\n    hasClassName(element, \"katex-display\") ||\n    hasClassName(element, \"math-display\")\n  )\n    return true;\n  return element.children\n    .map(readHastElement)\n    .some((child) => child != null && isDisplayMathElement(child));\n}\n\nfunction isMermaidCodeElement(element: MarkdownHastElement) {\n  const code = firstElementChild(element);\n  return (\n    code?.tagName === \"code\" &&\n    [\"language-mermaid\", \"language-mmd\", \"language-mermaid-js\"].some(\n      (className) => hasClassName(code, className),\n    )\n  );\n}\n\nfunction hasClassName(element: MarkdownHastElement, className: string) {\n  const classes = element.properties?.className;\n  return Array.isArray(classes)\n    ? classes.includes(className)\n    : typeof classes === \"string\" && classes.split(/\\s+/).includes(className);\n}\n\nfunction isHostileMarkdownGreenfieldBlock({\n  child,\n  kind,\n  sourceLineCount,\n  sourceText,\n}: {\n  child: MarkdownHastNode;\n  kind: MarkdownGreenfieldBlockKind;\n  sourceLineCount: number;\n  sourceText: string;\n}) {\n  if (kind === \"table\") {\n    if (sourceText.length > HOSTILE_TABLE_TEXT_LENGTH) return true;\n    if (countTableCells(child) > HOSTILE_TABLE_CELL_COUNT) return true;\n    if (countHastNodes(child) > HOSTILE_TABLE_HAST_NODE_COUNT) return true;\n    if (maxHastDepth(child) > HOSTILE_HAST_DEPTH) return true;\n    return false;\n  }\n  if (sourceText.length > HOSTILE_TEXT_LENGTH) return true;\n  if (countHastNodes(child) > HOSTILE_HAST_NODE_COUNT) return true;\n  if (maxHastDepth(child) > HOSTILE_HAST_DEPTH) return true;\n  if (kind === \"code\" && sourceLineCount > HOSTILE_CODE_LINE_COUNT) {\n    return true;\n  }\n  return false;\n}\n\nfunction countHastNodes(node: MarkdownHastNode): number {\n  const element = readHastElement(node);\n  return (\n    1 +\n    (element?.children ?? []).reduce(\n      (sum, child) => sum + countHastNodes(child),\n      0,\n    )\n  );\n}\n\nfunction maxHastDepth(node: MarkdownHastNode): number {\n  const element = readHastElement(node);\n  if (!element?.children.length) return 1;\n  return 1 + Math.max(...element.children.map(maxHastDepth));\n}\n\nfunction lineCountForBlocks(\n  blocks: readonly MarkdownGreenfieldBlock[],\n  nextBlock: MarkdownGreenfieldBlock,\n) {\n  let fallbackLineCount = 0;\n  let sourceEndLine = 0;\n  let sourceStartLine = 0;\n\n  for (const block of blocks) {\n    fallbackLineCount += block.sourceLineCount;\n    const range = block.sourceRange;\n    if (!range) continue;\n    sourceStartLine = sourceStartLine\n      ? Math.min(sourceStartLine, range.startLine)\n      : range.startLine;\n    sourceEndLine = Math.max(sourceEndLine, range.endLine);\n  }\n  fallbackLineCount += nextBlock.sourceLineCount;\n  const nextRange = nextBlock.sourceRange;\n  if (nextRange) {\n    sourceStartLine = sourceStartLine\n      ? Math.min(sourceStartLine, nextRange.startLine)\n      : nextRange.startLine;\n    sourceEndLine = Math.max(sourceEndLine, nextRange.endLine);\n  }\n\n  if (!sourceStartLine) return Math.max(1, fallbackLineCount);\n  return sourceEndLine - sourceStartLine + 1;\n}\n\nfunction countTableCells(node: MarkdownHastNode): number {\n  const element = readHastElement(node);\n  if (!element) return 0;\n  const self = element.tagName === \"td\" || element.tagName === \"th\" ? 1 : 0;\n  return (\n    self +\n    (element.children ?? []).reduce(\n      (sum, child) => sum + countTableCells(child),\n      0,\n    )\n  );\n}\n\nfunction markdownGreenfieldSourceMetricsForText(sourceText: string) {\n  const sourceLineLengths: number[] = [];\n  let sourceLineLength = 0;\n\n  for (let index = 0; index < sourceText.length; index += 1) {\n    const charCode = sourceText.charCodeAt(index);\n    if (charCode === 13) {\n      sourceLineLengths.push(sourceLineLength);\n      sourceLineLength = 0;\n      if (sourceText.charCodeAt(index + 1) === 10) index += 1;\n      continue;\n    }\n    if (charCode === 10 || charCode === 0x2028 || charCode === 0x2029) {\n      sourceLineLengths.push(sourceLineLength);\n      sourceLineLength = 0;\n      continue;\n    }\n    sourceLineLength += 1;\n  }\n\n  sourceLineLengths.push(sourceLineLength);\n  return {\n    sourceLineCount: sourceLineLengths.length,\n    sourceLineLengths,\n  };\n}\n\nfunction readHastElement(node: unknown): MarkdownHastElement | null {\n  return node &&\n    typeof node === \"object\" &&\n    (node as MarkdownHastElement).type === \"element\"\n    ? (node as MarkdownHastElement)\n    : null;\n}\n\nfunction firstElementChild(element: MarkdownHastElement) {\n  return element.children.map(readHastElement).find(Boolean) ?? null;\n}\n\nfunction isWhitespaceText(node: MarkdownHastNode) {\n  return (\n    node.type === \"text\" &&\n    typeof node.value === \"string\" &&\n    node.value.trim() === \"\"\n  );\n}\n\nfunction readStringProperty(value: unknown) {\n  if (typeof value === \"string\") return value;\n  if (Array.isArray(value)) return value.filter(Boolean).join(\" \");\n  return \"\";\n}\n\nfunction extractHastText(node: MarkdownHastNode): string {\n  if (node.type === \"text\" && typeof node.value === \"string\") return node.value;\n  const element = readHastElement(node);\n  if (!element) return \"\";\n  return element.children.map(extractHastText).join(\"\");\n}\n\nfunction nativeFindTextForHastChildren(children: readonly MarkdownHastNode[]) {\n  const text: string[] = [];\n  const stack = [...children].reverse();\n  while (stack.length) {\n    const node = stack.pop();\n    if (!node) continue;\n    if (node.type === \"text\" && typeof node.value === \"string\") {\n      text.push(node.value);\n      continue;\n    }\n\n    const element = readHastElement(node);\n    if (!element) continue;\n    if (element.tagName === \"script\" || element.tagName === \"style\") continue;\n    for (let index = element.children.length - 1; index >= 0; index -= 1) {\n      stack.push(element.children[index]!);\n    }\n  }\n  return text.join(\" \").trim();\n}\n\nfunction freezeMarkdownHastNode(node: unknown) {\n  if (!node || typeof node !== \"object\" || Object.isFrozen(node)) return;\n\n  const record = node as Record<string, unknown>;\n  for (const value of Object.values(record)) {\n    if (Array.isArray(value)) {\n      value.forEach(freezeMarkdownHastNode);\n      Object.freeze(value);\n      continue;\n    }\n    if (value && typeof value === \"object\") {\n      freezeMarkdownHastNode(value);\n    }\n  }\n  Object.freeze(node);\n}\n\nexport function freezeMarkdownGreenfieldDocument(\n  document: MarkdownGreenfieldDocument,\n) {\n  freezeMarkdownHastNode(document.unified.hast);\n  for (const block of document.blocks) {\n    if (block.sourceRange) Object.freeze(block.sourceRange);\n    Object.freeze(block.hastChildren);\n    Object.freeze(block.sourceLineLengths);\n    Object.freeze(block);\n  }\n  for (const chunk of document.chunks) {\n    if (chunk.sourceRange) Object.freeze(chunk.sourceRange);\n    Object.freeze(chunk.blockIds);\n    Object.freeze(chunk.hastChildren);\n    Object.freeze(chunk);\n  }\n  for (const heading of document.headings) Object.freeze(heading);\n  for (const target of document.fragmentTargets) Object.freeze(target);\n  Object.freeze(document.blocks);\n  Object.freeze(document.chunks);\n  Object.freeze(document.headings);\n  Object.freeze(document.fragmentTargets);\n  return Object.freeze(document);\n}\n\nexport function markdownGreenfieldDocumentTextKey(text: string) {\n  let hash = 2166136261;\n  for (let index = 0; index < text.length; index += 1) {\n    hash ^= text.charCodeAt(index);\n    hash = Math.imul(hash, 16777619);\n  }\n  return `${text.length}:${(hash >>> 0).toString(36)}`;\n}\n\nfunction clampSourceLine(line: number, lineCount: number) {\n  if (!Number.isFinite(line)) return 1;\n  return Math.max(1, Math.min(lineCount, Math.floor(line)));\n}\n\nfunction clampSourceOffset(offset: number, textLength: number) {\n  if (!Number.isFinite(offset)) return 0;\n  return Math.max(0, Math.min(textLength, Math.floor(offset)));\n}\n",
      "type": "registry:ui",
      "target": "@ui/markdown-greenfield-document.ts"
    },
    {
      "path": "registry/new-york-v4/ui/markdown-greenfield-document-store.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n  createMarkdownGreenfieldDocument,\n  freezeMarkdownGreenfieldDocument,\n  markdownGreenfieldDocumentTextKey,\n  type MarkdownGreenfieldDocument,\n} from \"./markdown-greenfield-document\";\n\nexport const MARKDOWN_GREENFIELD_ASYNC_DOCUMENT_MIN_CHARS = 60_000;\nexport const MARKDOWN_GREENFIELD_DOCUMENT_WORKER_LOCAL_STORAGE_KEY =\n  \"retab:markdown-document-worker\";\nexport const MARKDOWN_GREENFIELD_DOCUMENT_WORKER_SEARCH_PARAM =\n  \"markdownDocumentWorker\";\n\nconst MARKDOWN_GREENFIELD_DOCUMENT_WORKER_READY_TIMEOUT_MS = 800;\nconst MARKDOWN_GREENFIELD_DOCUMENT_ENTRY_CACHE_LIMIT = 16;\n\ntype MarkdownIdleWindow = Window &\n  typeof globalThis & {\n    requestIdleCallback?: Window[\"requestIdleCallback\"];\n  };\n\ntype MarkdownDocumentState =\n  | { status: \"failed\"; error: Error }\n  | { status: \"pending\" }\n  | { status: \"ready\"; document: MarkdownGreenfieldDocument };\n\ntype MarkdownDocumentEntry = {\n  key: string;\n  listeners: Set<() => void>;\n  started: boolean;\n  state: MarkdownDocumentState;\n  text: string;\n};\n\ntype MarkdownDocumentWorkerRequest = {\n  id: number;\n  text: string;\n  type: \"parse\";\n};\n\ntype MarkdownDocumentWorkerResponse =\n  | {\n      type: \"ready\";\n    }\n  | {\n      document: MarkdownGreenfieldDocument;\n      id: number;\n      ok: true;\n      type: \"result\";\n    }\n  | {\n      failure: \"clone_failed\" | \"parse_failed\";\n      id: number;\n      message: string;\n      ok: false;\n      type: \"result\";\n    };\n\nconst markdownDocumentEntries = new Map<string, MarkdownDocumentEntry>();\nlet nextMarkdownDocumentWorkerRequestId = 1;\n\nexport function useMarkdownGreenfieldDocument(text: string) {\n  const shouldLoadAsync =\n    text.length >= MARKDOWN_GREENFIELD_ASYNC_DOCUMENT_MIN_CHARS;\n  const syncDocument = React.useMemo(\n    () => (shouldLoadAsync ? null : createMarkdownGreenfieldDocument(text)),\n    [shouldLoadAsync, text],\n  );\n  const entry = React.useMemo(\n    () => (shouldLoadAsync ? getMarkdownDocumentEntry(text) : null),\n    [shouldLoadAsync, text],\n  );\n  const asyncState = React.useSyncExternalStore(\n    React.useCallback(\n      (onStoreChange) => {\n        if (!entry) return () => {};\n        entry.listeners.add(onStoreChange);\n        startMarkdownDocumentEntry(entry);\n        return () => {\n          entry.listeners.delete(onStoreChange);\n        };\n      },\n      [entry],\n    ),\n    React.useCallback(() => entry?.state ?? null, [entry]),\n    () => null,\n  );\n\n  if (syncDocument) return syncDocument;\n  if (!asyncState || asyncState.status === \"pending\") return null;\n  if (asyncState.status === \"failed\") throw asyncState.error;\n  return asyncState.document;\n}\n\nfunction getMarkdownDocumentEntry(text: string) {\n  const key = markdownGreenfieldDocumentTextKey(text);\n  const existing = markdownDocumentEntries.get(key);\n  if (existing?.text === text) return existing;\n\n  const entry: MarkdownDocumentEntry = {\n    key,\n    listeners: new Set(),\n    started: false,\n    state: { status: \"pending\" },\n    text,\n  };\n  markdownDocumentEntries.set(key, entry);\n  evictMarkdownDocumentEntries(key);\n  return entry;\n}\n\nfunction evictMarkdownDocumentEntries(currentKey: string) {\n  for (const [key, entry] of markdownDocumentEntries) {\n    if (\n      markdownDocumentEntries.size <=\n      MARKDOWN_GREENFIELD_DOCUMENT_ENTRY_CACHE_LIMIT\n    ) {\n      return;\n    }\n    if (key === currentKey || entry.listeners.size > 0) continue;\n    markdownDocumentEntries.delete(key);\n  }\n}\n\nfunction startMarkdownDocumentEntry(entry: MarkdownDocumentEntry) {\n  if (entry.started || entry.state.status !== \"pending\") return;\n  entry.started = true;\n\n  if (typeof Worker !== \"undefined\" && isMarkdownDocumentWorkerEnabled()) {\n    try {\n      startMarkdownDocumentWorker(entry);\n      return;\n    } catch {\n      startMarkdownDocumentFallback(entry);\n      return;\n    }\n  }\n\n  startMarkdownDocumentFallback(entry);\n}\n\nfunction startMarkdownDocumentWorker(entry: MarkdownDocumentEntry) {\n  const worker = new Worker(\n    new URL(\"./markdown-greenfield-document.worker.ts\", import.meta.url),\n    { type: \"module\" },\n  );\n  const id = nextMarkdownDocumentWorkerRequestId++;\n  let isSettled = false;\n\n  const fallBackToMainThread = () => {\n    if (isSettled) return;\n    isSettled = true;\n    window.clearTimeout(readyTimeoutId);\n    if (!isCurrentMarkdownDocumentEntry(entry)) {\n      worker.terminate();\n      return;\n    }\n    worker.terminate();\n    startMarkdownDocumentFallback(entry);\n  };\n  const readyTimeoutId = window.setTimeout(() => {\n    fallBackToMainThread();\n  }, MARKDOWN_GREENFIELD_DOCUMENT_WORKER_READY_TIMEOUT_MS);\n\n  worker.onmessage = (event: MessageEvent<unknown>) => {\n    const message = markdownDocumentWorkerResponseFromValue(event.data);\n    if (!message) {\n      fallBackToMainThread();\n      return;\n    }\n\n    if (message.type === \"ready\") {\n      window.clearTimeout(readyTimeoutId);\n      try {\n        worker.postMessage({\n          id,\n          text: entry.text,\n          type: \"parse\",\n        } satisfies MarkdownDocumentWorkerRequest);\n      } catch {\n        fallBackToMainThread();\n      }\n      return;\n    }\n    if (message.id !== id || isSettled) {\n      return;\n    }\n    isSettled = true;\n    window.clearTimeout(readyTimeoutId);\n    worker.terminate();\n    if (!isCurrentMarkdownDocumentEntry(entry)) return;\n    if (message.ok) {\n      publishMarkdownDocumentState(entry, {\n        document: freezeMarkdownGreenfieldDocument(message.document),\n        status: \"ready\",\n      });\n      return;\n    }\n    if (message.failure === \"clone_failed\") {\n      startMarkdownDocumentFallback(entry);\n      return;\n    }\n    publishMarkdownDocumentState(entry, {\n      error: new Error(message.message),\n      status: \"failed\",\n    });\n  };\n  worker.onerror = fallBackToMainThread;\n  worker.onmessageerror = fallBackToMainThread;\n}\n\nfunction startMarkdownDocumentFallback(entry: MarkdownDocumentEntry) {\n  scheduleMarkdownDocumentTask(() => {\n    if (!isCurrentMarkdownDocumentEntry(entry)) return;\n    try {\n      publishMarkdownDocumentState(entry, {\n        document: createMarkdownGreenfieldDocument(entry.text),\n        status: \"ready\",\n      });\n    } catch (error) {\n      publishMarkdownDocumentState(entry, {\n        error:\n          error instanceof Error\n            ? error\n            : new Error(\"Could not parse Markdown.\"),\n        status: \"failed\",\n      });\n    }\n  });\n}\n\nfunction scheduleMarkdownDocumentTask(callback: () => void) {\n  if (typeof window === \"undefined\") return;\n  const browserWindow = window as MarkdownIdleWindow;\n  const runWhenIdle = () => {\n    if (browserWindow.requestIdleCallback) {\n      browserWindow.requestIdleCallback(callback, { timeout: 120 });\n      return;\n    }\n    browserWindow.setTimeout(callback, 0);\n  };\n\n  if (browserWindow.requestAnimationFrame) {\n    browserWindow.requestAnimationFrame(() => {\n      browserWindow.setTimeout(runWhenIdle, 0);\n    });\n    return;\n  }\n  browserWindow.setTimeout(runWhenIdle, 0);\n}\n\nfunction publishMarkdownDocumentState(\n  entry: MarkdownDocumentEntry,\n  state: MarkdownDocumentState,\n) {\n  if (!isCurrentMarkdownDocumentEntry(entry)) return;\n  entry.state = state;\n  for (const listener of entry.listeners) listener();\n}\n\nfunction isCurrentMarkdownDocumentEntry(entry: MarkdownDocumentEntry) {\n  const current = markdownDocumentEntries.get(entry.key);\n  return current === entry && current.text === entry.text;\n}\n\nfunction isMarkdownDocumentWorkerEnabled() {\n  if (typeof window === \"undefined\") return false;\n  const urlFlag = readMarkdownDocumentWorkerSearchFlag(window.location.search);\n  if (urlFlag != null) return urlFlag;\n  return readMarkdownDocumentWorkerStorageFlag() ?? true;\n}\n\nfunction readMarkdownDocumentWorkerSearchFlag(search: string) {\n  try {\n    const value = new URLSearchParams(search).get(\n      MARKDOWN_GREENFIELD_DOCUMENT_WORKER_SEARCH_PARAM,\n    );\n    return markdownDocumentWorkerFlagValue(value);\n  } catch {\n    return null;\n  }\n}\n\nfunction readMarkdownDocumentWorkerStorageFlag() {\n  try {\n    return markdownDocumentWorkerFlagValue(\n      window.localStorage.getItem(\n        MARKDOWN_GREENFIELD_DOCUMENT_WORKER_LOCAL_STORAGE_KEY,\n      ),\n    );\n  } catch {\n    return null;\n  }\n}\n\nfunction markdownDocumentWorkerFlagValue(value: string | null) {\n  if (value == null) return null;\n  switch (value.trim().toLowerCase()) {\n    case \"1\":\n    case \"true\":\n    case \"yes\":\n    case \"on\":\n    case \"worker\":\n      return true;\n    case \"0\":\n    case \"false\":\n    case \"no\":\n    case \"off\":\n    case \"main\":\n      return false;\n    default:\n      return null;\n  }\n}\n\nfunction markdownDocumentWorkerResponseFromValue(\n  value: unknown,\n): MarkdownDocumentWorkerResponse | null {\n  if (!value || typeof value !== \"object\") return null;\n  const response = value as Partial<MarkdownDocumentWorkerResponse>;\n  if (response.type === \"ready\") return { type: \"ready\" };\n  if (response.type !== \"result\" || typeof response.id !== \"number\") {\n    return null;\n  }\n  if (response.ok === true && response.document) {\n    return response as MarkdownDocumentWorkerResponse;\n  }\n  if (\n    response.ok === false &&\n    (response.failure === \"clone_failed\" ||\n      response.failure === \"parse_failed\") &&\n    typeof response.message === \"string\"\n  ) {\n    return response as MarkdownDocumentWorkerResponse;\n  }\n  return null;\n}\n",
      "type": "registry:ui",
      "target": "@ui/markdown-greenfield-document-store.ts"
    },
    {
      "path": "registry/new-york-v4/ui/markdown-greenfield-document.worker.ts",
      "content": "import { createMarkdownGreenfieldDocument } from \"./markdown-greenfield-document\";\n\ntype MarkdownDocumentWorkerRequest = {\n  id: number;\n  text: string;\n  type: \"parse\";\n};\n\ntype MarkdownDocumentWorkerResponse =\n  | {\n      type: \"ready\";\n    }\n  | {\n      document: ReturnType<typeof createMarkdownGreenfieldDocument>;\n      id: number;\n      ok: true;\n      type: \"result\";\n    }\n  | {\n      failure: \"clone_failed\" | \"parse_failed\";\n      id: number;\n      message: string;\n      ok: false;\n      type: \"result\";\n    };\n\nself.postMessage({ type: \"ready\" } satisfies MarkdownDocumentWorkerResponse);\n\nself.onmessage = (event: MessageEvent<MarkdownDocumentWorkerRequest>) => {\n  const { id, text, type } = event.data;\n  if (type !== \"parse\") {\n    postMarkdownDocumentWorkerError({\n      failure: \"parse_failed\",\n      id,\n      message: \"Markdown document worker received an invalid request.\",\n    });\n    return;\n  }\n\n  let document: ReturnType<typeof createMarkdownGreenfieldDocument>;\n  try {\n    document = createMarkdownGreenfieldDocument(text);\n  } catch (error) {\n    postMarkdownDocumentWorkerError({\n      failure: \"parse_failed\",\n      id,\n      message:\n        error instanceof Error ? error.message : \"Could not parse Markdown.\",\n    });\n    return;\n  }\n\n  try {\n    assertStructuredCloneable(document);\n    self.postMessage({\n      document,\n      id,\n      ok: true,\n      type: \"result\",\n    } satisfies MarkdownDocumentWorkerResponse);\n  } catch (error) {\n    postMarkdownDocumentWorkerError({\n      failure: \"clone_failed\",\n      id,\n      message:\n        error instanceof Error\n          ? error.message\n          : \"Markdown document worker payload is not structured-clone safe.\",\n    });\n  }\n};\n\nfunction assertStructuredCloneable(value: unknown) {\n  if (typeof structuredClone === \"function\") {\n    structuredClone(value);\n    return;\n  }\n\n  if (typeof MessageChannel === \"function\") {\n    const channel = new MessageChannel();\n    try {\n      channel.port2.postMessage(value);\n    } finally {\n      channel.port1.close();\n      channel.port2.close();\n    }\n    return;\n  }\n\n  throw new Error(\"Structured clone probe is unavailable.\");\n}\n\nfunction postMarkdownDocumentWorkerError({\n  failure,\n  id,\n  message,\n}: {\n  failure: \"clone_failed\" | \"parse_failed\";\n  id: number;\n  message: string;\n}) {\n  self.postMessage({\n    failure,\n    id,\n    message,\n    ok: false,\n    type: \"result\",\n  } satisfies MarkdownDocumentWorkerResponse);\n}\n",
      "type": "registry:ui",
      "target": "@ui/markdown-greenfield-document.worker.ts"
    },
    {
      "path": "registry/new-york-v4/ui/markdown-greenfield-layout.ts",
      "content": "\"use client\";\n\nimport type {\n  MarkdownGreenfieldBlock,\n  MarkdownGreenfieldChunk,\n  MarkdownGreenfieldDocument,\n} from \"./markdown-greenfield-document\";\n\n// The chunk reading column's max inline size (the old max-w-4xl, 56rem at\n// the 16px root). Shared with the surface motion resolver: the align\n// translate that hides the close-leg recenter must agree with the markup\n// about where the column's margin clamps.\nexport const MARKDOWN_GREENFIELD_CHUNK_MAX_INLINE_SIZE = 896;\nconst CHUNK_PADDING_X = 32;\nconst DOCUMENT_PADDING_TOP = CHUNK_PADDING_X;\nconst DOCUMENT_PADDING_BOTTOM = CHUNK_PADDING_X;\nconst MIN_CHUNK_HEIGHT = 64;\n// Tuned to the rendered typography (15.5px body at leading-relaxed ≈ 25px,\n// 14px mono code at leading-5 = 20px) so estimates track measured heights and\n// scroll correction stays minimal.\nconst BODY_LINE_HEIGHT = 25;\nconst MONO_LINE_HEIGHT = 20;\nconst APPROX_BODY_CHAR_WIDTH = 7.9;\nconst APPROX_MONO_CHAR_WIDTH = 7.8;\nexport const MARKDOWN_GREENFIELD_LAYOUT_POLICY_VERSION =\n  \"greenfield-layout-rich-blocks-v7\";\nconst MARKDOWN_TABLE_VIRTUALIZATION_ROW_THRESHOLD = 80;\nconst MARKDOWN_TABLE_VIRTUALIZED_HEIGHT = 640;\nconst LAYOUT_CACHE_LIMIT = 64;\nconst ESTIMATED_HEIGHT_CACHE_LIMIT = 12;\n\nconst markdownGreenfieldLayoutDocumentIds = new WeakMap<\n  MarkdownGreenfieldDocument,\n  number\n>();\nconst markdownGreenfieldLayoutCache = new Map<\n  string,\n  {\n    document: MarkdownGreenfieldDocument;\n    frame: MarkdownGreenfieldFrame;\n  }\n>();\nconst markdownGreenfieldEstimatedHeightCaches = new WeakMap<\n  MarkdownGreenfieldDocument,\n  Map<string, readonly number[]>\n>();\nlet nextMarkdownGreenfieldLayoutDocumentId = 1;\n\nexport type MarkdownGreenfieldFrame = {\n  chunks: MarkdownGreenfieldChunkFrame[];\n  totalHeight: number;\n  width: number;\n};\n\nexport type MarkdownGreenfieldChunkFrame = {\n  bottom: number;\n  height: number;\n  id: string;\n  index: number;\n  measuredHeight: number | null;\n  sourceEndLine: number;\n  sourceStartLine: number;\n  top: number;\n};\n\nexport type MarkdownGreenfieldMeasuredHeights = {\n  /** Compact revision key for the current measured-height snapshot. */\n  cacheKey?: string;\n  get(\n    chunk: MarkdownGreenfieldChunk,\n    context: MarkdownGreenfieldMeasurementContext,\n  ): number | undefined;\n};\n\nexport type MarkdownGreenfieldMeasurementContext = {\n  fontScale: number;\n  policyVersion: string;\n  width: number;\n};\n\nexport function layoutMarkdownGreenfieldDocument({\n  contentWidth,\n  document,\n  fontScale,\n  measuredHeights,\n}: {\n  contentWidth: number;\n  document: MarkdownGreenfieldDocument;\n  fontScale: number;\n  measuredHeights?: MarkdownGreenfieldMeasuredHeights;\n}): MarkdownGreenfieldFrame {\n  const width = Math.max(1, contentWidth);\n  const context = {\n    fontScale,\n    policyVersion: MARKDOWN_GREENFIELD_LAYOUT_POLICY_VERSION,\n    width,\n  };\n  const measuredHeightsCacheKey = measuredHeights?.cacheKey ?? null;\n  const shouldCacheFrame = !measuredHeights || measuredHeightsCacheKey != null;\n  const cacheKey = markdownGreenfieldLayoutCacheKey({\n    context,\n    document,\n    measuredHeightsCacheKey,\n  });\n  if (shouldCacheFrame) {\n    const cached = markdownGreenfieldLayoutCache.get(cacheKey);\n    if (cached?.document === document) {\n      markdownGreenfieldLayoutCache.delete(cacheKey);\n      markdownGreenfieldLayoutCache.set(cacheKey, cached);\n      return cached.frame;\n    }\n  }\n\n  const estimatedHeights = readMarkdownGreenfieldEstimatedHeights({\n    context,\n    document,\n  });\n  const chunks: MarkdownGreenfieldChunkFrame[] = [];\n  let y = DOCUMENT_PADDING_TOP;\n\n  for (const [chunkIndex, chunk] of document.chunks.entries()) {\n    const estimatedHeight = estimatedHeights[chunkIndex] ?? MIN_CHUNK_HEIGHT;\n    const measuredHeight = readMarkdownGreenfieldMeasuredHeight({\n      chunk,\n      context,\n      measuredHeights,\n    });\n    const height = Math.max(\n      MIN_CHUNK_HEIGHT,\n      measuredHeight == null ? estimatedHeight : measuredHeight,\n    );\n    chunks.push({\n      bottom: y + height,\n      height,\n      id: chunk.id,\n      index: chunk.index,\n      measuredHeight,\n      sourceEndLine: chunk.sourceEndLine,\n      sourceStartLine: chunk.sourceStartLine,\n      top: y,\n    });\n    y += height;\n  }\n\n  const frame = freezeMarkdownGreenfieldFrame({\n    chunks,\n    totalHeight: chunks.length ? y + DOCUMENT_PADDING_BOTTOM : 0,\n    width,\n  });\n  if (shouldCacheFrame) {\n    markdownGreenfieldLayoutCache.set(cacheKey, { document, frame });\n    while (markdownGreenfieldLayoutCache.size > LAYOUT_CACHE_LIMIT) {\n      const oldestKey = markdownGreenfieldLayoutCache.keys().next().value;\n      if (!oldestKey) break;\n      markdownGreenfieldLayoutCache.delete(oldestKey);\n    }\n  }\n  return frame;\n}\n\nfunction readMarkdownGreenfieldEstimatedHeights({\n  context,\n  document,\n}: {\n  context: MarkdownGreenfieldMeasurementContext;\n  document: MarkdownGreenfieldDocument;\n}) {\n  const cacheKey = markdownGreenfieldEstimateCacheKey(context);\n  const cache = markdownGreenfieldEstimateCacheForDocument(document);\n  const cached = cache.get(cacheKey);\n  if (cached) {\n    cache.delete(cacheKey);\n    cache.set(cacheKey, cached);\n    return cached;\n  }\n\n  const blocksById = new Map(document.blocks.map((block) => [block.id, block]));\n  const estimatedHeights = Object.freeze(\n    document.chunks.map((chunk) =>\n      estimateMarkdownGreenfieldChunkHeight({\n        blocks: chunk.blockIds\n          .map((blockId) => blocksById.get(blockId))\n          .filter((block): block is MarkdownGreenfieldBlock => Boolean(block)),\n        fontScale: context.fontScale,\n        width: context.width,\n      }),\n    ),\n  );\n  cache.set(cacheKey, estimatedHeights);\n  while (cache.size > ESTIMATED_HEIGHT_CACHE_LIMIT) {\n    const oldestKey = cache.keys().next().value;\n    if (!oldestKey) break;\n    cache.delete(oldestKey);\n  }\n  return estimatedHeights;\n}\n\nfunction estimateMarkdownGreenfieldChunkHeight({\n  blocks,\n  fontScale,\n  width,\n}: {\n  blocks: readonly MarkdownGreenfieldBlock[];\n  fontScale: number;\n  width: number;\n}) {\n  const textWidth = Math.max(1, width - CHUNK_PADDING_X * 2);\n  const height = blocks.reduce(\n    (sum, block) =>\n      sum +\n      estimateMarkdownGreenfieldBlockHeight({\n        block,\n        fontScale,\n        textWidth,\n      }),\n    0,\n  );\n  return Math.max(MIN_CHUNK_HEIGHT, height);\n}\n\nfunction estimateMarkdownGreenfieldBlockHeight({\n  block,\n  fontScale,\n  textWidth,\n}: {\n  block: MarkdownGreenfieldBlock;\n  fontScale: number;\n  textWidth: number;\n}) {\n  if (block.kind === \"thematicBreak\") return 36 * fontScale;\n  if (block.kind === \"frontmatter\") {\n    const lines = Math.max(1, block.sourceLineCount);\n    return (72 + lines * MONO_LINE_HEIGHT) * fontScale;\n  }\n  if (block.kind === \"footnotes\")\n    return estimateTextBlock(block, textWidth, fontScale, 0.9);\n  if (block.kind === \"heading\") {\n    return estimateTextBlock(block, textWidth, fontScale, 1.3) + 18 * fontScale;\n  }\n  if (block.kind === \"diagram\") return 360 * fontScale;\n  if (block.kind === \"math\") return 120 * fontScale;\n  if (block.kind === \"component\") return 160 * fontScale;\n  if (block.kind === \"code\") {\n    const lines = Math.max(1, block.sourceLineCount);\n    return 48 * fontScale + lines * MONO_LINE_HEIGHT * fontScale;\n  }\n  if (block.kind === \"table\") {\n    const rows = Math.max(2, block.sourceLineCount);\n    if (rows >= MARKDOWN_TABLE_VIRTUALIZATION_ROW_THRESHOLD) {\n      return MARKDOWN_TABLE_VIRTUALIZED_HEIGHT * fontScale;\n    }\n    return (48 + rows * 36) * fontScale;\n  }\n  if (block.kind === \"image\") return 280 * fontScale;\n  if (block.isHostile) return 420 * fontScale;\n  return estimateTextBlock(block, textWidth, fontScale, 1);\n}\n\nfunction estimateTextBlock(\n  block: MarkdownGreenfieldBlock,\n  textWidth: number,\n  fontScale: number,\n  multiplier: number,\n) {\n  const charWidth =\n    block.kind === \"code\" ? APPROX_MONO_CHAR_WIDTH : APPROX_BODY_CHAR_WIDTH;\n  const columns = Math.max(12, Math.floor(textWidth / (charWidth * fontScale)));\n  const visualLines = block.sourceLineLengths.reduce(\n    (sum, lineLength) => sum + Math.max(1, Math.ceil(lineLength / columns)),\n    0,\n  );\n  return (\n    16 * fontScale + visualLines * BODY_LINE_HEIGHT * fontScale * multiplier\n  );\n}\n\nfunction readMarkdownGreenfieldMeasuredHeight({\n  chunk,\n  context,\n  measuredHeights,\n}: {\n  chunk: MarkdownGreenfieldChunk;\n  context: MarkdownGreenfieldMeasurementContext;\n  measuredHeights?: MarkdownGreenfieldMeasuredHeights;\n}) {\n  if (!measuredHeights) return null;\n  const measuredHeight = measuredHeights.get(chunk, context);\n  if (\n    typeof measuredHeight === \"number\" &&\n    Number.isFinite(measuredHeight) &&\n    measuredHeight > 0\n  ) {\n    return measuredHeight;\n  }\n  return null;\n}\n\nfunction markdownGreenfieldLayoutCacheKey({\n  context,\n  document,\n  measuredHeightsCacheKey,\n}: {\n  context: MarkdownGreenfieldMeasurementContext;\n  document: MarkdownGreenfieldDocument;\n  measuredHeightsCacheKey: string | null;\n}) {\n  return [\n    markdownGreenfieldLayoutDocumentId(document),\n    Math.round(context.width * 100) / 100,\n    context.fontScale.toFixed(4),\n    context.policyVersion,\n    measuredHeightsCacheKey ?? \"unmeasured\",\n  ].join(\":\");\n}\n\nfunction markdownGreenfieldEstimateCacheKey(\n  context: MarkdownGreenfieldMeasurementContext,\n) {\n  return [\n    Math.round(context.width * 100) / 100,\n    context.fontScale.toFixed(4),\n    context.policyVersion,\n  ].join(\":\");\n}\n\nfunction markdownGreenfieldEstimateCacheForDocument(\n  document: MarkdownGreenfieldDocument,\n) {\n  let cache = markdownGreenfieldEstimatedHeightCaches.get(document);\n  if (cache) return cache;\n  cache = new Map();\n  markdownGreenfieldEstimatedHeightCaches.set(document, cache);\n  return cache;\n}\n\nfunction markdownGreenfieldLayoutDocumentId(\n  document: MarkdownGreenfieldDocument,\n) {\n  const existing = markdownGreenfieldLayoutDocumentIds.get(document);\n  if (existing) return existing;\n  const next = nextMarkdownGreenfieldLayoutDocumentId++;\n  markdownGreenfieldLayoutDocumentIds.set(document, next);\n  return next;\n}\n\nfunction freezeMarkdownGreenfieldFrame(frame: MarkdownGreenfieldFrame) {\n  for (const chunk of frame.chunks) Object.freeze(chunk);\n  Object.freeze(frame.chunks);\n  return Object.freeze(frame);\n}\n",
      "type": "registry:ui",
      "target": "@ui/markdown-greenfield-layout.ts"
    },
    {
      "path": "registry/new-york-v4/ui/markdown-greenfield-renderer.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Fragment, jsx, jsxs } from \"react/jsx-runtime\";\nimport { toJsxRuntime } from \"hast-util-to-jsx-runtime\";\nimport {\n  BadgeAlert,\n  Check,\n  CircleAlert,\n  Copy,\n  ExternalLink,\n  Info,\n  Lightbulb,\n  Link2,\n  TriangleAlert,\n} from \"lucide-react\";\n\nimport {\n  diffLineKind,\n  isSafeHighlightedCodeLine,\n  MARKDOWN_CODE_HIGHLIGHT_STYLES,\n  normalizeCodeLanguage,\n  renderCodeLine,\n  useMarkdownCodeLineHtml,\n} from \"./markdown-greenfield-code-highlight\";\nimport { MarkdownGreenfieldDiagram } from \"./markdown-greenfield-diagram\";\nimport type { MarkdownGreenfieldChunk } from \"./markdown-greenfield-document\";\nimport type {\n  MarkdownHastElement,\n  MarkdownHastNode,\n  MarkdownHastRoot,\n} from \"./markdown-hast-types\";\nimport {\n  sanitizeMarkdownImageUrl,\n  sanitizeMarkdownMediaUrl,\n  sanitizeMarkdownUrl,\n} from \"./markdown-url-policy\";\nimport { useKeyedLayoutEffect } from \"@/hooks/use-keyed-layout-effect\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nconst MarkdownContentReadyContext = React.createContext<(() => void) | null>(\n  null,\n);\n\n// The rendered body size at 100% zoom. Every other size (headings, code,\n// tables, footnotes) is authored in `em` relative to this, so scaling this one\n// value with the zoom `fontScale` resizes the whole document as one system.\nexport const MARKDOWN_GREENFIELD_BASE_FONT_PX = 15.5;\n// Tailwind v4's default spacing unit (0.25rem). Scaling it with the zoom\n// fontScale keeps padding/margins proportional to the body text.\nexport const MARKDOWN_GREENFIELD_BASE_SPACING_REM = 0.25;\nconst MARKDOWN_CODE_VIRTUALIZATION_LINE_THRESHOLD = 100;\nconst MARKDOWN_CODE_VIRTUALIZED_OVERSCAN_LINES = 12;\nconst MARKDOWN_CODE_VIRTUALIZED_VIEWPORT_HEIGHT_PX = 512;\nconst MARKDOWN_CODE_VIRTUALIZED_LINE_HEIGHT_FALLBACK_PX = 24;\nconst MARKDOWN_TABLE_VIRTUALIZATION_ROW_THRESHOLD = 80;\nconst MARKDOWN_TABLE_VIRTUALIZATION_CELL_THRESHOLD = 600;\nconst MARKDOWN_TABLE_VIRTUALIZED_ROW_HEIGHT_PX = 36;\nconst MARKDOWN_TABLE_VIRTUALIZED_VIEWPORT_HEIGHT_PX = 560;\nconst MARKDOWN_TABLE_VIRTUALIZED_OVERSCAN_ROWS = 8;\nconst MARKDOWN_TABLE_NATIVE_FIND_ROWS_PER_ENTRY = 8;\nconst MARKDOWN_RENDERED_CHUNK_CACHE_LIMIT = 96;\n\nconst markdownRenderedChunkCache = new Map<string, React.ReactNode>();\n\nexport const MarkdownGreenfieldChunkRenderer = React.memo(\n  function MarkdownGreenfieldChunkRenderer({\n    activeMatchOccurrence,\n    chunk,\n    fontScale = 1,\n    onContentReady,\n    searchQuery,\n    urlFragmentNavigation = true,\n  }: {\n    activeMatchOccurrence?: number;\n    chunk: MarkdownGreenfieldChunk;\n    fontScale?: number;\n    onContentReady?: () => void;\n    searchQuery?: string;\n    urlFragmentNavigation?: boolean;\n  }) {\n    const ref = React.useRef<HTMLDivElement | null>(null);\n    const notifyContentReady = React.useCallback(() => {\n      onContentReady?.();\n    }, [onContentReady]);\n    const renderedChildren = React.useMemo(\n      () =>\n        renderCachedHastChunkChildren(\n          chunk,\n          searchQuery,\n          activeMatchOccurrence,\n          { urlFragmentNavigation },\n        ),\n      [activeMatchOccurrence, chunk, searchQuery, urlFragmentNavigation],\n    );\n\n    useKeyedLayoutEffect(joinEffectKey([chunk.id, notifyContentReady]), () => {\n      notifyContentReady();\n      const element = ref.current;\n      if (!element || typeof ResizeObserver === \"undefined\") return;\n      const observer = new ResizeObserver(notifyContentReady);\n      observer.observe(element);\n      return () => observer.disconnect();\n    });\n\n    if (chunk.isHostile) {\n      return <MarkdownGreenfieldHostileChunk chunk={chunk} />;\n    }\n\n    return (\n      <MarkdownContentReadyContext.Provider value={notifyContentReady}>\n        <div\n          ref={ref}\n          className=\"markdown-greenfield-content text-foreground min-w-0 leading-relaxed\"\n          data-slot=\"markdown-greenfield-content\"\n          // Scale both the font and the spacing scale with zoom so vertical\n          // rhythm tracks the type size. Tailwind v4 spacing utilities resolve to\n          // calc(var(--spacing) * n), so overriding --spacing here scales every\n          // margin/padding/gap inside the document at once.\n          style={\n            {\n              \"--spacing\": `${(MARKDOWN_GREENFIELD_BASE_SPACING_REM * fontScale).toFixed(5)}rem`,\n              fontSize: `${MARKDOWN_GREENFIELD_BASE_FONT_PX * fontScale}px`,\n            } as React.CSSProperties\n          }\n        >\n          {renderedChildren}\n        </div>\n      </MarkdownContentReadyContext.Provider>\n    );\n  },\n);\n\nfunction renderCachedHastChunkChildren(\n  chunk: MarkdownGreenfieldChunk,\n  searchQuery?: string,\n  activeMatchOccurrence?: number,\n  options: { urlFragmentNavigation: boolean } = {\n    urlFragmentNavigation: true,\n  },\n) {\n  const normalizedQuery = normalizeMarkdownSearchQuery(searchQuery);\n  const cacheKey = joinEffectKey([\n    \"markdown-rendered-chunk\",\n    chunk,\n    normalizedQuery,\n    normalizedQuery ? (activeMatchOccurrence ?? -1) : -1,\n    options.urlFragmentNavigation,\n  ]);\n  const cached = readRenderedChunkCache(cacheKey);\n  if (cached !== undefined || markdownRenderedChunkCache.has(cacheKey)) {\n    return cached;\n  }\n\n  const rendered = renderHastChildrenUncached({\n    activeMatchOccurrence,\n    children: chunk.hastChildren,\n    normalizedQuery,\n    urlFragmentNavigation: options.urlFragmentNavigation,\n  });\n  writeRenderedChunkCache(cacheKey, rendered);\n  return rendered;\n}\n\nfunction renderHastChildren(\n  children: readonly MarkdownHastNode[],\n  searchQuery?: string,\n  activeMatchOccurrence?: number,\n  options: { urlFragmentNavigation: boolean } = {\n    urlFragmentNavigation: true,\n  },\n) {\n  return renderHastChildrenUncached({\n    activeMatchOccurrence,\n    children,\n    normalizedQuery: normalizeMarkdownSearchQuery(searchQuery),\n    urlFragmentNavigation: options.urlFragmentNavigation,\n  });\n}\n\nfunction renderHastChildrenUncached({\n  activeMatchOccurrence,\n  children,\n  normalizedQuery,\n  urlFragmentNavigation,\n}: {\n  activeMatchOccurrence?: number;\n  children: readonly MarkdownHastNode[];\n  normalizedQuery: string;\n  urlFragmentNavigation: boolean;\n}) {\n  const root: MarkdownHastRoot = {\n    type: \"root\",\n    children: children.map(cloneHastNode),\n  };\n  if (!urlFragmentNavigation) {\n    suppressDomFragmentIds(root.children);\n  }\n\n  if (normalizedQuery) {\n    // The counter tracks rendered occurrences in document order so the one at\n    // activeMatchOccurrence (the chunk-local index of the toolbar's current\n    // match) can be marked active and styled distinctly from the rest.\n    highlightMarkdownSearchMatches(root.children, normalizedQuery, {\n      count: 0,\n      active: activeMatchOccurrence ?? -1,\n    });\n  }\n\n  return toJsxRuntime(root as never, {\n    Fragment,\n    components: markdownComponents,\n    ignoreInvalidStyle: true,\n    jsx,\n    jsxs,\n    passKeys: true,\n    passNode: true,\n  });\n}\n\nfunction readRenderedChunkCache(key: string) {\n  if (!markdownRenderedChunkCache.has(key)) return undefined;\n  const rendered = markdownRenderedChunkCache.get(key);\n  markdownRenderedChunkCache.delete(key);\n  markdownRenderedChunkCache.set(key, rendered);\n  return rendered;\n}\n\nfunction writeRenderedChunkCache(key: string, rendered: React.ReactNode) {\n  markdownRenderedChunkCache.set(key, rendered);\n  while (\n    markdownRenderedChunkCache.size > MARKDOWN_RENDERED_CHUNK_CACHE_LIMIT\n  ) {\n    const oldestKey = markdownRenderedChunkCache.keys().next().value;\n    if (!oldestKey) break;\n    markdownRenderedChunkCache.delete(oldestKey);\n  }\n}\n\nfunction normalizeMarkdownSearchQuery(searchQuery: string | undefined) {\n  return searchQuery?.trim().toLowerCase() ?? \"\";\n}\n\nconst markdownComponents = {\n  a: ({\n    children,\n    href,\n    node: _node,\n    rel: _rel,\n    target: _target,\n    ...props\n  }: any) => {\n    const safeHref = sanitizeMarkdownUrl(href ?? \"\");\n    if (!safeHref) return <span>{children}</span>;\n    const text = reactNodeText(children);\n    const kind = linkKindForHref(safeHref);\n    const form = linkFormForHref({\n      href: safeHref,\n      text,\n    });\n    const external = kind === \"external\";\n    return (\n      <a\n        {...props}\n        className={[\n          \"text-primary decoration-muted-foreground/50 visited:text-muted-foreground font-medium [overflow-wrap:anywhere] underline underline-offset-4 hover:decoration-current\",\n          kind === \"fragment\" ? \"decoration-dotted\" : \"\",\n          form !== \"inline\" ? \"font-mono\" : \"\",\n        ]\n          .filter(Boolean)\n          .join(\" \")}\n        data-pretext-link-form={form}\n        data-pretext-link-kind={kind}\n        href={safeHref}\n        aria-label={footnoteLabelForLink({\n          href: safeHref,\n          label: props[\"aria-label\"],\n          text,\n        })}\n        rel={external ? \"noopener noreferrer\" : undefined}\n        target={external ? \"_blank\" : undefined}\n      >\n        {children}\n        {external ? (\n          <ExternalLink className=\"ml-1 inline size-3\" aria-hidden=\"true\" />\n        ) : null}\n      </a>\n    );\n  },\n  br: ({ node: _node, ...props }: any) => (\n    <br {...props} data-pretext-line-break=\"soft\" />\n  ),\n  abbr: ({ node: _node, ...props }: any) => (\n    <abbr\n      {...props}\n      className=\"cursor-help underline decoration-dotted\"\n      data-pretext-raw-inline=\"\"\n    />\n  ),\n  blockquote: ({ children, node, ...props }: any) => {\n    const alertKind = readDataProperty(node, \"dataPretextAlertKind\");\n    const alertTitle = readDataProperty(node, \"dataPretextAlertTitle\");\n    if (alertKind) {\n      const label = String(alertTitle || alertKind);\n      const Icon = alertIconForKind(alertKind);\n      const alertProps = withoutPretextAlertMetadata(props);\n      return (\n        <aside\n          {...alertProps}\n          aria-label={label}\n          className=\"bg-muted/35 my-5 rounded-md border px-4 py-3\"\n          data-pretext-alert-kind={alertKind}\n          role=\"note\"\n        >\n          <div\n            className=\"text-foreground mb-2 flex items-center gap-2 text-[0.9em] font-semibold\"\n            data-pretext-alert-title=\"\"\n          >\n            <Icon className=\"size-[1.15em]\" aria-hidden=\"true\" />\n            {label}\n          </div>\n          <div\n            className=\"[&>*:first-child]:mt-0 [&>*:last-child]:mb-0\"\n            data-pretext-alert-body=\"\"\n          >\n            {children}\n          </div>\n        </aside>\n      );\n    }\n\n    return (\n      <blockquote\n        {...props}\n        className=\"border-border text-muted-foreground my-4 border-l-2 pl-4 italic [&_blockquote]:my-3 [&_ol]:list-[lower-alpha] [&>ul]:my-2\"\n      >\n        {children}\n      </blockquote>\n    );\n  },\n  code: ({ children, className, node: _node, ...props }: any) => (\n    <code\n      {...props}\n      className={[\n        \"bg-muted rounded px-1 py-0.5 font-mono text-[0.88em]\",\n        className,\n      ]\n        .filter(Boolean)\n        .join(\" \")}\n    >\n      {children}\n    </code>\n  ),\n  caption: ({ node: _node, ...props }: any) => (\n    <caption\n      {...props}\n      className=\"text-muted-foreground caption-top px-3 py-2 text-left text-[0.85em] font-medium\"\n    />\n  ),\n  del: ({ node: _node, ...props }: any) => (\n    <del\n      {...props}\n      className=\"text-muted-foreground decoration-muted-foreground/70 decoration-2\"\n      data-pretext-strikethrough=\"\"\n    />\n  ),\n  details: ({ node: _node, ...props }: any) => (\n    <details {...props} className=\"bg-muted/25 my-4 rounded-md border p-3\" />\n  ),\n  dl: ({ node: _node, ...props }: any) => (\n    <dl {...props} className=\"my-4 space-y-2\" data-pretext-definition-list=\"\" />\n  ),\n  dt: ({ node: _node, ...props }: any) => (\n    <dt {...props} className=\"font-semibold\" data-pretext-definition-term=\"\" />\n  ),\n  dd: ({ node: _node, ...props }: any) => (\n    <dd\n      {...props}\n      className=\"text-muted-foreground ml-4\"\n      data-pretext-definition-description=\"\"\n    />\n  ),\n  div: ({ children, node, ...props }: any) => {\n    const calloutKind = readTrustedDataProperty(node, \"dataPretextCalloutKind\");\n    if (calloutKind) {\n      const title =\n        readTrustedDataProperty(node, \"dataPretextCalloutTitle\") ||\n        calloutTitle(calloutKind);\n      return (\n        <aside\n          aria-label={title}\n          className=\"bg-muted/35 my-5 rounded-md border px-4 py-3\"\n          data-pretext-callout-kind={calloutKind}\n          role=\"note\"\n        >\n          <div className=\"mb-2 text-[0.9em] font-semibold\">{title}</div>\n          <div className=\"[&>*:first-child]:mt-0 [&>*:last-child]:mb-0\">\n            {children}\n          </div>\n        </aside>\n      );\n    }\n\n    const componentName = readDataProperty(node, \"dataPretextComponentName\");\n    const trusted = isTrustedPretextComponentNode(node);\n    if (trusted && hasDataProperty(node, \"dataPretextComponentFallback\")) {\n      return (\n        <div\n          data-pretext-component-fallback=\"\"\n          data-pretext-component-fallback-name={readDataProperty(\n            node,\n            \"dataPretextComponentFallbackName\",\n          )}\n          data-pretext-component-fallback-reason={readDataProperty(\n            node,\n            \"dataPretextComponentFallbackReason\",\n          )}\n          data-pretext-component-fallback-source={readDataProperty(\n            node,\n            \"dataPretextComponentFallbackSource\",\n          )}\n        >\n          {children}\n        </div>\n      );\n    }\n    if (trusted && componentName === \"Metric\") {\n      const componentProps = readComponentProps(\n        readDataProperty(node, \"dataPretextComponentProps\"),\n      );\n      return (\n        <div\n          className=\"bg-muted/25 my-4 w-fit max-w-full min-w-0 rounded-md border px-4 py-3\"\n          data-pretext-component=\"Metric\"\n        >\n          <div className=\"text-muted-foreground text-[0.9em] [overflow-wrap:anywhere]\">\n            {readOptionalString(componentProps.label)}\n          </div>\n          <div className=\"text-[1.55em] font-semibold [overflow-wrap:anywhere]\">\n            {readOptionalString(componentProps.value)}\n          </div>\n        </div>\n      );\n    }\n    if (trusted && componentName === \"Badge\") {\n      const componentProps = readComponentProps(\n        readDataProperty(node, \"dataPretextComponentProps\"),\n      );\n      return (\n        <span\n          className=\"bg-muted/35 inline-flex max-w-full items-center rounded-md border px-2 py-0.5 text-[0.9em] font-medium [overflow-wrap:anywhere]\"\n          data-pretext-component=\"Badge\"\n        >\n          {readOptionalString(componentProps.label)}\n        </span>\n      );\n    }\n    if (trusted && componentName === \"Callout\") {\n      const componentProps = readComponentProps(\n        readDataProperty(node, \"dataPretextComponentProps\"),\n      );\n      const kind = readOptionalString(componentProps.kind) ?? \"note\";\n      const title =\n        readOptionalString(componentProps.title) ?? calloutTitle(kind);\n      return (\n        <aside\n          aria-label={title}\n          className=\"bg-muted/35 my-5 rounded-md border px-4 py-3\"\n          data-pretext-callout-kind={kind}\n          data-pretext-component=\"Callout\"\n          role=\"note\"\n        >\n          <div className=\"mb-2 text-[0.9em] font-semibold\">{title}</div>\n          <div className=\"[&>*:first-child]:mt-0 [&>*:last-child]:mb-0\">\n            {children}\n          </div>\n        </aside>\n      );\n    }\n    if (trusted && componentName === \"Accordion\") {\n      const componentProps = readComponentProps(\n        readDataProperty(node, \"dataPretextComponentProps\"),\n      );\n      return (\n        <details\n          className=\"bg-muted/25 my-4 rounded-md border p-3\"\n          data-pretext-component=\"Accordion\"\n        >\n          <summary className=\"cursor-pointer font-medium\">\n            {readOptionalString(componentProps.title)}\n          </summary>\n          <div className=\"mt-3 [&>*:first-child]:mt-0 [&>*:last-child]:mb-0\">\n            {children}\n          </div>\n        </details>\n      );\n    }\n    if (trusted && componentName === \"Tabs\") {\n      return <MarkdownTabs node={readHastElement(node)} />;\n    }\n    if (trusted && componentName === \"Image\") {\n      const componentProps = readComponentProps(\n        readDataProperty(node, \"dataPretextComponentProps\"),\n      );\n      return (\n        <MarkdownImageSurface\n          alt={readOptionalString(componentProps.alt) ?? \"\"}\n          componentName=\"Image\"\n          height={readOptionalNumber(componentProps.height)}\n          src={readOptionalString(componentProps.src) ?? \"\"}\n          title={readOptionalString(componentProps.title)}\n          width={readOptionalNumber(componentProps.width)}\n        />\n      );\n    }\n    if (trusted && componentName === \"Video\") {\n      const componentProps = readComponentProps(\n        readDataProperty(node, \"dataPretextComponentProps\"),\n      );\n      return (\n        <MarkdownVideoSurface\n          controls={readOptionalBoolean(componentProps.controls) ?? true}\n          label={readOptionalString(componentProps.label) ?? \"Video\"}\n          loop={readOptionalBoolean(componentProps.loop) ?? false}\n          muted={readOptionalBoolean(componentProps.muted) ?? false}\n          src={readOptionalString(componentProps.src) ?? \"\"}\n          title={readOptionalString(componentProps.title)}\n        />\n      );\n    }\n    if (trusted && componentName === \"Diagram\") {\n      const componentProps = readComponentProps(\n        readDataProperty(node, \"dataPretextComponentProps\"),\n      );\n      if (\n        componentProps.type === \"mermaid\" &&\n        typeof componentProps.source === \"string\"\n      ) {\n        return (\n          <MarkdownMeasuredDiagram\n            caption={readOptionalString(componentProps.caption)}\n            componentName=\"Diagram\"\n            source={componentProps.source}\n            title={readOptionalString(componentProps.title)}\n          />\n        );\n      }\n    }\n\n    return <div {...withoutInternalPretextMetadata(props)}>{children}</div>;\n  },\n  h1: headingComponent(\n    \"h1\",\n    \"mt-6 mb-3 first:mt-0\",\n    \"text-[1.55em] leading-tight font-semibold tracking-tight\",\n  ),\n  h2: headingComponent(\n    \"h2\",\n    \"mt-7 mb-3 first:mt-0\",\n    \"text-[1.3em] leading-snug font-semibold tracking-tight\",\n  ),\n  h3: headingComponent(\n    \"h3\",\n    \"mt-5 mb-2 first:mt-0\",\n    \"text-[1.1em] leading-snug font-semibold\",\n  ),\n  h4: headingComponent(\n    \"h4\",\n    \"mt-4 mb-2 first:mt-0\",\n    \"text-[1em] leading-snug font-semibold\",\n  ),\n  h5: headingComponent(\n    \"h5\",\n    \"mt-4 mb-1.5 first:mt-0\",\n    \"text-[0.95em] leading-snug font-semibold\",\n  ),\n  h6: headingComponent(\n    \"h6\",\n    \"mt-4 mb-1.5 first:mt-0\",\n    \"text-[0.9em] leading-snug font-semibold text-muted-foreground\",\n  ),\n  hr: ({ node: _node, ...props }: any) => (\n    <hr\n      {...props}\n      className=\"border-border my-10 border-0 border-t\"\n      data-pretext-thematic-break=\"\"\n    />\n  ),\n  kbd: ({ node: _node, ...props }: any) => (\n    <kbd\n      {...props}\n      className=\"bg-muted rounded border px-1.5 py-0.5 font-mono text-[0.85em]\"\n      data-pretext-raw-inline=\"\"\n    />\n  ),\n  mark: ({ node: _node, ...props }: any) => {\n    const isActiveMatch = \"data-pretext-search-match-active\" in props;\n    return (\n      <mark\n        {...props}\n        aria-current={isActiveMatch ? \"true\" : undefined}\n        className={[\n          \"text-foreground rounded px-1\",\n          isActiveMatch\n            ? \"bg-amber-400 ring-1 ring-amber-500/70 dark:bg-amber-500/70\"\n            : \"bg-yellow-200/70 dark:bg-yellow-400/30\",\n        ].join(\" \")}\n        data-pretext-raw-inline=\"\"\n      />\n    );\n  },\n  img: ({ alt, height, node, src, title, width }: any) => {\n    if (!hasDataProperty(node, \"dataPretextMarkdownImage\")) return null;\n    return (\n      <MarkdownImageSurface\n        alt={alt ?? \"\"}\n        height={readOptionalNumber(height)}\n        src={src ?? \"\"}\n        title={title}\n        width={readOptionalNumber(width)}\n      />\n    );\n  },\n  input: ({ checked, node: _node, type, ...props }: any) => {\n    if (type !== \"checkbox\") return null;\n    return (\n      <input\n        {...props}\n        aria-label={checked ? \"Completed task\" : \"Incomplete task\"}\n        aria-readonly=\"true\"\n        checked={checked}\n        className=\"border-border accent-primary mr-2 size-3.5 rounded align-[-0.15em]\"\n        data-pretext-task-checkbox={checked ? \"checked\" : \"unchecked\"}\n        disabled\n        readOnly\n        type=\"checkbox\"\n      />\n    );\n  },\n  li: ({ children, className, node, ...props }: any) => {\n    const isTask = hasDescendantElement(readHastElement(node), \"input\");\n    return (\n      <li\n        {...props}\n        className={[\n          \"leading-relaxed\",\n          isTask ? \"list-none pl-0\" : \"\",\n          \"[&>p]:my-1\",\n          className,\n        ]\n          .filter(Boolean)\n          .join(\" \")}\n        data-pretext-task-list-item={isTask ? \"\" : undefined}\n      >\n        {children}\n      </li>\n    );\n  },\n  ol: ({ className, node: _node, ...props }: any) => (\n    <ol\n      {...props}\n      className={[\n        \"my-3 ml-5 list-decimal space-y-1 [&_ol]:list-[lower-alpha]\",\n        className,\n      ]\n        .filter(Boolean)\n        .join(\" \")}\n    />\n  ),\n  p: ({ node, ...props }: any) => {\n    const element = readHastElement(node);\n    const onlyImage =\n      element?.children.filter((child) => !isWhitespaceText(child)).length ===\n        1 &&\n      readHastElement(\n        element.children.find((child) => !isWhitespaceText(child)),\n      )?.tagName === \"img\";\n    if (onlyImage) {\n      return (\n        <div\n          {...props}\n          className=\"my-3 min-w-0 leading-relaxed [overflow-wrap:anywhere]\"\n        />\n      );\n    }\n    return (\n      <p\n        {...props}\n        className=\"my-4 min-w-0 leading-7 [overflow-wrap:anywhere]\"\n      />\n    );\n  },\n  span: ({ className, node: _node, ...props }: any) => {\n    const classes = String(className ?? \"\");\n    if (classes.includes(\"katex-display\")) {\n      return (\n        <span\n          {...props}\n          aria-label=\"Math block\"\n          className={[classes, \"block overflow-x-auto\"]\n            .filter(Boolean)\n            .join(\" \")}\n          data-pretext-math-block=\"\"\n          role=\"region\"\n          tabIndex={0}\n          onKeyDown={handleHorizontalScrollKeyDown}\n        />\n      );\n    }\n    return (\n      <span\n        {...props}\n        className={classes || undefined}\n        data-pretext-math-inline={classes.includes(\"katex\") ? \"\" : undefined}\n      />\n    );\n  },\n  pre: ({ children, node, ...props }: any) => {\n    const code = readPreCodeElement(node);\n    if (\n      hasDataProperty(node, \"dataMarkdownFrontmatterSource\") ||\n      hasDataProperty(code, \"dataMarkdownFrontmatterSource\")\n    ) {\n      return (\n        <pre\n          {...props}\n          className=\"bg-muted/25 my-4 overflow-x-auto rounded-md border p-4 font-mono text-[0.9em] leading-[1.7] whitespace-pre\"\n          data-markdown-frontmatter-source=\"\"\n          role=\"region\"\n          tabIndex={0}\n          onKeyDown={handleHorizontalScrollKeyDown}\n        >\n          <code>{extractHastNodeText(code).replace(/\\n$/, \"\")}</code>\n        </pre>\n      );\n    }\n\n    const language = normalizeCodeLanguage(readCodeLanguage(code));\n    if (language === \"mermaid\") {\n      const metadata = readCodeMetadata(code);\n      return (\n        <MarkdownMeasuredDiagram\n          caption={metadata.caption}\n          source={extractHastNodeText(code).replace(/\\n$/, \"\")}\n          title={metadata.title}\n        />\n      );\n    }\n\n    return (\n      <MarkdownCodeBlock\n        language={language ?? \"text\"}\n        metadata={readCodeMetadata(code)}\n        source={extractHastNodeText(code).replace(/\\n$/, \"\")}\n      >\n        {children}\n      </MarkdownCodeBlock>\n    );\n  },\n  section: ({ children, className, node, ...props }: any) => {\n    const isFootnotes = hasDataProperty(node, \"dataFootnotes\");\n    return (\n      <section\n        {...props}\n        aria-label={isFootnotes ? \"Footnotes\" : props[\"aria-label\"]}\n        className={[\n          isFootnotes\n            ? \"text-muted-foreground mt-10 border-t pt-5 text-[0.9em]\"\n            : \"my-5\",\n          className,\n        ]\n          .filter(Boolean)\n          .join(\" \")}\n      >\n        {children}\n      </section>\n    );\n  },\n  strong: ({ node: _node, ...props }: any) => (\n    <strong {...props} className=\"font-semibold\" />\n  ),\n  summary: ({ node: _node, ...props }: any) => (\n    <summary {...props} className=\"cursor-pointer font-medium\" />\n  ),\n  q: ({ cite, node: _node, ...props }: any) => (\n    <q\n      {...props}\n      cite={sanitizeMarkdownUrl(cite ?? \"\") || undefined}\n      className=\"italic\"\n      data-pretext-raw-inline=\"\"\n    />\n  ),\n  ins: ({ cite, node: _node, ...props }: any) => (\n    <ins\n      {...props}\n      cite={sanitizeMarkdownUrl(cite ?? \"\") || undefined}\n      className=\"underline decoration-green-600/60\"\n      data-pretext-raw-inline=\"\"\n    />\n  ),\n  cite: ({ node: _node, ...props }: any) => (\n    <cite {...props} className=\"italic\" data-pretext-raw-inline=\"\" />\n  ),\n  dfn: ({ node: _node, ...props }: any) => (\n    <dfn {...props} className=\"italic\" data-pretext-raw-inline=\"\" />\n  ),\n  samp: ({ node: _node, ...props }: any) => (\n    <samp {...props} className=\"font-mono\" data-pretext-raw-inline=\"\" />\n  ),\n  small: ({ node: _node, ...props }: any) => (\n    <small\n      {...props}\n      className=\"text-muted-foreground text-[0.85em]\"\n      data-pretext-raw-inline=\"\"\n    />\n  ),\n  sub: ({ node: _node, ...props }: any) => (\n    <sub {...props} className=\"align-sub\" data-pretext-raw-inline=\"\" />\n  ),\n  sup: ({ node: _node, ...props }: any) => (\n    <sup {...props} className=\"align-super\" data-pretext-raw-inline=\"\" />\n  ),\n  time: ({ node: _node, ...props }: any) => (\n    <time {...props} data-pretext-raw-inline=\"\" />\n  ),\n  var: ({ node: _node, ...props }: any) => (\n    <var {...props} className=\"font-mono italic\" data-pretext-raw-inline=\"\" />\n  ),\n  table: MarkdownTable,\n  tbody: ({ node: _node, ...props }: any) => <tbody {...props} />,\n  td: ({ align, node, ...props }: any) => {\n    const resolvedAlign = align ?? readHastElement(node)?.properties?.align;\n    return (\n      <td\n        {...props}\n        align={typeof resolvedAlign === \"string\" ? resolvedAlign : undefined}\n        className=\"border-border border-t px-3 py-1.5 align-top [overflow-wrap:break-word] [&[align=center]]:text-center [&[align=right]]:text-right [&[align=right]]:tabular-nums\"\n      />\n    );\n  },\n  th: ({ align, node, ...props }: any) => {\n    const resolvedAlign = align ?? readHastElement(node)?.properties?.align;\n    return (\n      <th\n        {...props}\n        align={typeof resolvedAlign === \"string\" ? resolvedAlign : undefined}\n        className=\"border-border bg-muted/55 border-b px-3 py-1.5 text-left align-top font-medium [overflow-wrap:break-word] [&[align=center]]:text-center [&[align=right]]:text-right [&[align=right]]:tabular-nums\"\n        scope=\"col\"\n      />\n    );\n  },\n  thead: ({ node: _node, ...props }: any) => <thead {...props} />,\n  tr: ({ node: _node, ...props }: any) => <tr {...props} />,\n  ul: ({ className, node: _node, ...props }: any) => (\n    <ul\n      {...props}\n      className={[\n        \"my-3 ml-5 list-disc space-y-1 [&_ul]:list-[circle]\",\n        className,\n      ]\n        .filter(Boolean)\n        .join(\" \")}\n    />\n  ),\n};\n\nfunction headingComponent(\n  Tag: \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\",\n  blockClassName: string,\n  textClassName: string,\n) {\n  return function Heading({ children, node, ...props }: any) {\n    const id =\n      typeof props.id === \"string\"\n        ? props.id\n        : readDataProperty(node, \"dataPretextFragmentId\");\n    const text =\n      extractHastText(readHastElement(node)) || reactNodeText(children);\n    delete props.dataPretextFragmentId;\n    delete props[\"data-pretext-fragment-id\"];\n    if (!id) {\n      return (\n        <Tag {...props} className={`${blockClassName} ${textClassName}`}>\n          {children}\n        </Tag>\n      );\n    }\n    return (\n      <div className={`group/heading relative ${blockClassName}`}>\n        <Tag {...props} className={textClassName}>\n          {children}\n        </Tag>\n        <HeadingAnchor id={id} text={text} />\n      </div>\n    );\n  };\n}\n\n// A GitHub-style anchor that appears in the left gutter on hover/focus and\n// copies a deep link to the heading. Kept as a sibling of the heading (not a\n// child) so it never leaks into the heading's accessible name.\nfunction HeadingAnchor({ id, text }: { id: string; text: string }) {\n  const [copied, setCopied] = React.useState(false);\n  return (\n    <button\n      aria-label={`Copy link to ${text}`}\n      className=\"text-muted-foreground hover:text-foreground absolute top-1/2 -left-7 inline-flex size-6 -translate-y-1/2 items-center justify-center rounded opacity-0 transition-opacity group-hover/heading:opacity-100 focus-visible:opacity-100\"\n      type=\"button\"\n      onClick={() => {\n        copyHeadingLink(id);\n        setCopied(true);\n        window.setTimeout(() => setCopied(false), 1200);\n      }}\n    >\n      {copied ? (\n        <Check className=\"size-4\" aria-hidden=\"true\" />\n      ) : (\n        <Link2 className=\"size-4\" aria-hidden=\"true\" />\n      )}\n    </button>\n  );\n}\n\nfunction MarkdownGreenfieldHostileChunk({\n  chunk,\n}: {\n  chunk: MarkdownGreenfieldChunk;\n}) {\n  const sourceLines = React.useMemo(\n    () => chunk.sourceText.split(/\\r\\n|[\\n\\r\\u2028\\u2029]/),\n    [chunk.sourceText],\n  );\n  const [scrollTop, setScrollTop] = React.useState(0);\n  const lineHeight = 24;\n  const viewportHeight = 576;\n  const start = Math.max(0, Math.floor(scrollTop / lineHeight) - 12);\n  const end = Math.min(\n    sourceLines.length,\n    Math.ceil((scrollTop + viewportHeight) / lineHeight) + 12,\n  );\n  const mountedLines = sourceLines.slice(start, end);\n  const omittedLines = Math.max(0, sourceLines.length - mountedLines.length);\n\n  return (\n    <section\n      aria-label=\"Large Markdown block\"\n      className=\"bg-muted/25 text-muted-foreground overflow-hidden rounded-md border text-sm\"\n      data-markdown-hostile-fallback=\"\"\n      data-markdown-hostile-line-count={sourceLines.length}\n      data-markdown-hostile-mounted-lines={mountedLines.length}\n      data-markdown-hostile-omitted-lines={omittedLines}\n      data-markdown-hostile-virtualized=\"\"\n    >\n      <div className=\"bg-muted/55 text-foreground flex items-center justify-between gap-3 border-b px-3 py-2 font-medium\">\n        Large Markdown block\n        <button\n          aria-label=\"Copy large Markdown block source\"\n          className=\"text-muted-foreground text-xs underline underline-offset-4\"\n          type=\"button\"\n          onClick={() => void navigator.clipboard?.writeText(chunk.sourceText)}\n        >\n          Copy\n        </button>\n      </div>\n      <pre\n        aria-label=\"Large Markdown source preview\"\n        className=\"bg-background/60 max-h-[36rem] overflow-auto font-mono text-[13px] whitespace-pre\"\n        data-markdown-hostile-preview=\"\"\n        role=\"region\"\n        tabIndex={0}\n        onScroll={(event) => setScrollTop(event.currentTarget.scrollTop)}\n      >\n        <code\n          className=\"relative block min-w-max\"\n          data-markdown-hostile-scroll-canvas=\"\"\n          style={{ height: Math.max(sourceLines.length, 1) * lineHeight }}\n        >\n          {mountedLines.map((line, index) => {\n            const lineNumber = start + index + 1;\n            return (\n              <span\n                key={lineNumber}\n                className=\"absolute inset-x-0 grid grid-cols-[4rem_minmax(0,1fr)] px-4\"\n                data-markdown-hostile-line={lineNumber}\n                style={{\n                  height: lineHeight,\n                  lineHeight: `${lineHeight}px`,\n                  top: (lineNumber - 1) * lineHeight,\n                }}\n              >\n                <span\n                  aria-hidden=\"true\"\n                  className=\"text-muted-foreground pr-4 text-right select-none\"\n                >\n                  {lineNumber}\n                </span>\n                <span>{line || \" \"}</span>\n              </span>\n            );\n          })}\n        </code>\n      </pre>\n    </section>\n  );\n}\n\nfunction MarkdownCodeBlock({\n  children: _children,\n  language,\n  metadata,\n  source,\n}: {\n  children: React.ReactNode;\n  language: string;\n  metadata: ReturnType<typeof readCodeMetadata>;\n  source: string;\n}) {\n  const [copyFailed, setCopyFailed] = React.useState(false);\n  const title = metadata.title || language;\n  const sourceLines = React.useMemo(() => source.split(\"\\n\"), [source]);\n  const lineNumberStart = metadata.lineNumberStart ?? 1;\n  const lineNumberMaxDigits = String(\n    lineNumberStart + Math.max(0, sourceLines.length - 1),\n  ).length;\n\n  const copy = React.useCallback(\n    async (selectedOnly: boolean) => {\n      const selection = window.getSelection();\n      const selectedText =\n        selectedOnly && selection?.rangeCount\n          ? selection.toString().trimEnd()\n          : \"\";\n      try {\n        await navigator.clipboard?.writeText(selectedText || source);\n        setCopyFailed(false);\n      } catch {\n        setCopyFailed(true);\n      }\n    },\n    [source],\n  );\n\n  return (\n    <figure\n      aria-label={`${title} code block`}\n      // text-[1em] keeps the code block on the document's scaling em cascade:\n      // a host stylesheet (e.g. the demo's rehype-pretty-code rule) may pin\n      // [data-rehype-pretty-code-figure] to a fixed font-size, which would stop\n      // the block from following the zoom control. A utility-layer size wins.\n      className=\"bg-muted/25 my-5 overflow-hidden rounded-md border text-[1em]\"\n      data-pretext-code-language={language}\n      data-rehype-pretty-code-figure=\"\"\n      role=\"group\"\n    >\n      <figcaption className=\"bg-muted/55 flex items-center justify-between gap-3 border-b px-3 py-2 text-sm\">\n        <span\n          className=\"text-muted-foreground font-mono text-xs font-medium\"\n          data-pretext-code-title={metadata.title || undefined}\n        >\n          {title}\n        </span>\n        <span className=\"flex items-center gap-2\">\n          {copyFailed ? (\n            <span aria-label=\"Copy failed\" className=\"text-destructive text-xs\">\n              Copy failed\n            </span>\n          ) : null}\n          <button\n            aria-label=\"Copy code block\"\n            className=\"text-muted-foreground text-xs underline underline-offset-4\"\n            type=\"button\"\n            onClick={() => void copy(false)}\n          >\n            Copy\n          </button>\n          <button\n            aria-label=\"Copy selected code or block\"\n            className=\"sr-only\"\n            type=\"button\"\n            onClick={() => void copy(true)}\n          />\n        </span>\n      </figcaption>\n      <MarkdownCodeSource\n        language={language}\n        lineNumberMaxDigits={lineNumberMaxDigits}\n        lineNumberStart={lineNumberStart}\n        metadata={metadata}\n        sourceLines={sourceLines}\n      />\n      {metadata.caption ? (\n        <figcaption\n          className=\"text-muted-foreground border-t px-3 py-2 text-[0.9em]\"\n          data-pretext-code-caption=\"\"\n        >\n          {metadata.caption}\n        </figcaption>\n      ) : null}\n    </figure>\n  );\n}\n\ntype MarkdownCodeMetadata = ReturnType<typeof readCodeMetadata>;\n\nconst MarkdownCodeSource = React.memo(function MarkdownCodeSource({\n  language,\n  lineNumberMaxDigits,\n  lineNumberStart,\n  metadata,\n  sourceLines,\n}: {\n  language: string;\n  lineNumberMaxDigits: number;\n  lineNumberStart: number;\n  metadata: MarkdownCodeMetadata;\n  sourceLines: readonly string[];\n}) {\n  const isVirtualized =\n    sourceLines.length > MARKDOWN_CODE_VIRTUALIZATION_LINE_THRESHOLD;\n  const lineHtmlByIndex = useMarkdownCodeLineHtml({\n    end: isVirtualized ? 0 : sourceLines.length,\n    highlightPattern: metadata.highlightPattern,\n    language,\n    sourceLines,\n    start: 0,\n  });\n\n  if (isVirtualized) {\n    return (\n      <MarkdownVirtualizedCodeSource\n        language={language}\n        lineNumberMaxDigits={lineNumberMaxDigits}\n        lineNumberStart={lineNumberStart}\n        metadata={metadata}\n        sourceLines={sourceLines}\n      />\n    );\n  }\n\n  return (\n    <>\n      <style data-pretext-code-highlight-styles=\"\">\n        {MARKDOWN_CODE_HIGHLIGHT_STYLES}\n      </style>\n      <pre\n        aria-label={`${language} code source`}\n        className=\"overflow-x-auto p-3 [overflow-wrap:normal] [&_code]:min-w-max\"\n        data-pretext-code-source=\"\"\n        role=\"region\"\n        tabIndex={0}\n        onKeyDown={handleHorizontalScrollKeyDown}\n      >\n        <code\n          {...markdownCodeElementProps({\n            language,\n            lineNumberMaxDigits,\n            lineNumberStart,\n            metadata,\n          })}\n        >\n          {sourceLines.map((line, index) => (\n            <MarkdownCodeLine\n              key={index}\n              index={index}\n              language={language}\n              line={line}\n              lineHtml={lineHtmlByIndex.get(index)}\n              lineNumberStart={lineNumberStart}\n              metadata={metadata}\n            />\n          ))}\n        </code>\n      </pre>\n    </>\n  );\n});\n\nfunction MarkdownVirtualizedCodeSource({\n  language,\n  lineNumberMaxDigits,\n  lineNumberStart,\n  metadata,\n  sourceLines,\n}: {\n  language: string;\n  lineNumberMaxDigits: number;\n  lineNumberStart: number;\n  metadata: MarkdownCodeMetadata;\n  sourceLines: readonly string[];\n}) {\n  const preRef = React.useRef<HTMLPreElement | null>(null);\n  const codeRef = React.useRef<HTMLElement | null>(null);\n  const [scrollTop, setScrollTop] = React.useState(0);\n  const [lineHeight, setLineHeight] = React.useState(\n    MARKDOWN_CODE_VIRTUALIZED_LINE_HEIGHT_FALLBACK_PX,\n  );\n  const [viewportHeight, setViewportHeight] = React.useState(\n    MARKDOWN_CODE_VIRTUALIZED_VIEWPORT_HEIGHT_PX,\n  );\n  const start = Math.max(\n    0,\n    Math.floor(scrollTop / lineHeight) -\n      MARKDOWN_CODE_VIRTUALIZED_OVERSCAN_LINES,\n  );\n  const end = Math.min(\n    sourceLines.length,\n    Math.ceil((scrollTop + viewportHeight) / lineHeight) +\n      MARKDOWN_CODE_VIRTUALIZED_OVERSCAN_LINES,\n  );\n  const mountedLines = sourceLines.slice(start, end);\n  const lineWidthCh = React.useMemo(\n    () => widestMarkdownCodeLineWidthCh(sourceLines),\n    [sourceLines],\n  );\n  const lineHtmlByIndex = useMarkdownCodeLineHtml({\n    end,\n    highlightPattern: metadata.highlightPattern,\n    language,\n    sourceLines,\n    start,\n  });\n\n  useKeyedLayoutEffect(joinEffectKey([language, sourceLines.length]), () => {\n    const updateMetrics = () => {\n      const measuredLineHeight = measureMarkdownCodeLineHeight(codeRef.current);\n      if (measuredLineHeight > 0) {\n        setLineHeight((current) =>\n          Math.abs(current - measuredLineHeight) > 0.5\n            ? measuredLineHeight\n            : current,\n        );\n      }\n      const measuredViewportHeight = preRef.current?.clientHeight ?? 0;\n      if (measuredViewportHeight > 0) {\n        setViewportHeight((current) =>\n          Math.abs(current - measuredViewportHeight) > 0.5\n            ? measuredViewportHeight\n            : current,\n        );\n      }\n    };\n\n    updateMetrics();\n    if (typeof ResizeObserver === \"undefined\") return;\n    const observer = new ResizeObserver(updateMetrics);\n    if (preRef.current) observer.observe(preRef.current);\n    if (codeRef.current) observer.observe(codeRef.current);\n    return () => observer.disconnect();\n  });\n\n  return (\n    <>\n      <style data-pretext-code-highlight-styles=\"\">\n        {MARKDOWN_CODE_HIGHLIGHT_STYLES}\n      </style>\n      <pre\n        ref={preRef}\n        aria-label={`${language} code source`}\n        className=\"max-h-[32rem] overflow-auto overflow-x-auto p-3 [overflow-wrap:normal] [&_code]:min-w-max\"\n        data-pretext-code-line-count={sourceLines.length}\n        data-pretext-code-mounted-lines={mountedLines.length}\n        data-pretext-code-source=\"\"\n        data-pretext-code-virtualized=\"\"\n        role=\"region\"\n        tabIndex={0}\n        onKeyDown={handleHorizontalScrollKeyDown}\n        onScroll={(event) => setScrollTop(event.currentTarget.scrollTop)}\n      >\n        <code\n          ref={codeRef}\n          {...markdownCodeElementProps({\n            language,\n            lineNumberMaxDigits,\n            lineNumberStart,\n            metadata,\n            start,\n            virtualized: true,\n          })}\n          style={markdownVirtualizedCodeCanvasStyle({\n            language,\n            lineNumberStart,\n            lineWidthCh,\n            metadata,\n            start,\n            totalLines: sourceLines.length,\n          })}\n        >\n          {mountedLines.map((line, offset) => {\n            const index = start + offset;\n            return (\n              <MarkdownCodeLine\n                key={index}\n                index={index}\n                language={language}\n                line={line}\n                lineHtml={lineHtmlByIndex.get(index)}\n                lineNumberStart={lineNumberStart}\n                metadata={metadata}\n                totalLines={sourceLines.length}\n                virtualized\n              />\n            );\n          })}\n        </code>\n      </pre>\n    </>\n  );\n}\n\nfunction MarkdownCodeLine({\n  index,\n  language,\n  line,\n  lineHtml,\n  lineNumberStart,\n  metadata,\n  totalLines,\n  virtualized = false,\n}: {\n  index: number;\n  language: string;\n  line: string;\n  lineHtml: string | undefined;\n  lineNumberStart: number;\n  metadata: MarkdownCodeMetadata;\n  totalLines?: number;\n  virtualized?: boolean;\n}) {\n  const lineNumber = lineNumberStart + index;\n  const diffKind = diffLineKind(line);\n  return (\n    <span\n      aria-label={metadata.showLineNumbers ? `Line ${lineNumber}` : undefined}\n      aria-posinset={\n        metadata.showLineNumbers && virtualized ? index + 1 : undefined\n      }\n      aria-setsize={\n        metadata.showLineNumbers && virtualized ? totalLines : undefined\n      }\n      className={[\n        markdownCodeLineClassName(diffKind),\n        virtualized ? \"absolute right-0 left-0\" : \"\",\n      ]\n        .filter(Boolean)\n        .join(\" \")}\n      data-highlighted-line={\n        metadata.highlightedLines.has(index + 1) ? \"\" : undefined\n      }\n      data-line=\"\"\n      data-pretext-code-diff-line={diffKind ?? undefined}\n      data-pretext-code-line-number={\n        metadata.showLineNumbers ? lineNumber : undefined\n      }\n      role={metadata.showLineNumbers ? \"listitem\" : undefined}\n      style={\n        virtualized\n          ? {\n              height: \"var(--markdown-code-line-height)\",\n              top: `calc(${index} * var(--markdown-code-line-height))`,\n            }\n          : undefined\n      }\n    >\n      {renderCodeLine({\n        fallbackLanguage: language,\n        line,\n        lineHtml,\n        pattern: metadata.highlightPattern,\n      })}\n    </span>\n  );\n}\n\nfunction markdownCodeElementProps({\n  language,\n  lineNumberMaxDigits,\n  lineNumberStart,\n  metadata,\n  start = 0,\n  virtualized = false,\n}: {\n  language: string;\n  lineNumberMaxDigits: number;\n  lineNumberStart: number;\n  metadata: MarkdownCodeMetadata;\n  start?: number;\n  virtualized?: boolean;\n}) {\n  return {\n    \"aria-label\": metadata.showLineNumbers\n      ? `${language} numbered code lines`\n      : undefined,\n    className: [\n      \"block font-mono text-[0.9em] leading-[1.45]\",\n      virtualized ? \"relative\" : \"\",\n      metadata.showLineNumbers\n        ? \"[counter-reset:line] before:content-[counter(line)]\"\n        : \"\",\n      metadata.highlightedLines.size\n        ? \"[&>[data-highlighted-line]]:bg-primary/10\"\n        : \"\",\n      metadata.highlightPattern\n        ? \"[&_[data-highlighted-chars]]:bg-primary/20 [&_[data-highlighted-chars]]:rounded\"\n        : \"\",\n    ]\n      .filter(Boolean)\n      .join(\" \"),\n    \"data-language\": language,\n    \"data-line-numbers\": metadata.showLineNumbers ? \"\" : undefined,\n    \"data-line-numbers-max-digits\": metadata.showLineNumbers\n      ? lineNumberMaxDigits\n      : undefined,\n    \"data-pretext-code-virtualized\": virtualized ? \"\" : undefined,\n    role: metadata.showLineNumbers ? \"list\" : undefined,\n    style: metadata.showLineNumbers\n      ? { counterSet: `line ${lineNumberStart + start - 1}` }\n      : undefined,\n  };\n}\n\nfunction markdownVirtualizedCodeCanvasStyle({\n  language,\n  lineNumberStart,\n  lineWidthCh,\n  metadata,\n  start,\n  totalLines,\n}: {\n  language: string;\n  lineNumberStart: number;\n  lineWidthCh: number;\n  metadata: MarkdownCodeMetadata;\n  start: number;\n  totalLines: number;\n}): React.CSSProperties {\n  return {\n    \"--markdown-code-line-height\": markdownCodeLineHeightCss(language),\n    counterSet: metadata.showLineNumbers\n      ? `line ${lineNumberStart + start - 1}`\n      : undefined,\n    height: `calc(${Math.max(totalLines, 1)} * var(--markdown-code-line-height))`,\n    minWidth: markdownCodeMinWidth(lineWidthCh, metadata.showLineNumbers),\n  } as React.CSSProperties;\n}\n\nfunction markdownCodeLineClassName(diffKind: ReturnType<typeof diffLineKind>) {\n  return [\n    \"block min-h-5 box-border whitespace-pre\",\n    diffKind === \"add\" ? \"bg-emerald-500/10\" : \"\",\n    diffKind === \"remove\" ? \"bg-red-500/10\" : \"\",\n  ]\n    .filter(Boolean)\n    .join(\" \");\n}\n\nfunction markdownCodeLineHeightCss(language: string) {\n  return language === \"text\" || language === \"plaintext\"\n    ? \"0.95em\"\n    : \"calc(1.45em + var(--spacing) * 1)\";\n}\n\nfunction markdownCodeMinWidth(lineWidthCh: number, showLineNumbers: boolean) {\n  const contentWidth = `${Math.max(1, lineWidthCh)}ch`;\n  return showLineNumbers\n    ? `max(100%, calc(${contentWidth} + var(--spacing) * 22))`\n    : `max(100%, ${contentWidth})`;\n}\n\nfunction widestMarkdownCodeLineWidthCh(lines: readonly string[]) {\n  return lines.reduce(\n    (widest, line) => Math.max(widest, markdownCodeLineWidthCh(line)),\n    1,\n  );\n}\n\nfunction markdownCodeLineWidthCh(line: string) {\n  let width = 0;\n  for (const character of line) {\n    if (character === \"\\t\") {\n      width += 4 - (width % 4);\n    } else {\n      width += 1;\n    }\n  }\n  return width;\n}\n\nfunction measureMarkdownCodeLineHeight(code: HTMLElement | null) {\n  if (!code) return 0;\n  const sample = document.createElement(\"span\");\n  sample.className = markdownCodeLineClassName(null);\n  sample.dataset.line = \"\";\n  sample.style.position = \"absolute\";\n  sample.style.visibility = \"hidden\";\n  sample.textContent = \" \";\n  code.appendChild(sample);\n  const height = sample.getBoundingClientRect().height || sample.offsetHeight;\n  sample.remove();\n  return height;\n}\n\nfunction MarkdownMeasuredDiagram({\n  caption,\n  componentName,\n  source,\n  title,\n}: {\n  caption?: string;\n  componentName?: string;\n  source: string;\n  title?: string;\n}) {\n  const notifyContentReady = React.useContext(MarkdownContentReadyContext);\n  return (\n    <MarkdownGreenfieldDiagram\n      caption={caption}\n      componentName={componentName}\n      onContentReady={notifyContentReady ?? undefined}\n      source={source}\n      title={title}\n    />\n  );\n}\n\nfunction MarkdownImageSurface({\n  alt,\n  componentName,\n  height,\n  src,\n  title,\n  width,\n}: {\n  alt: string;\n  componentName?: string;\n  height?: number;\n  src: string;\n  title?: string;\n  width?: number;\n}) {\n  const notifyContentReady = React.useContext(MarkdownContentReadyContext);\n  const safeSrc = sanitizeMarkdownImageUrl(src);\n  const explicitAspectRatio =\n    width && height ? `${width} / ${height}` : undefined;\n  const [state, setState] = React.useState<\n    \"blocked\" | \"failed\" | \"loading\" | \"ready\"\n  >(safeSrc ? \"loading\" : \"blocked\");\n  const [aspectRatio, setAspectRatio] = React.useState(\n    () => explicitAspectRatio ?? \"\",\n  );\n  const captionId = React.useId();\n\n  // Reset load state when the source/aspect-ratio inputs change by adjusting\n  // state during render (React's prop-change pattern) instead of in an effect.\n  const sourceResetKey = `${safeSrc}::${explicitAspectRatio ?? \"\"}`;\n  const [prevSourceResetKey, setPrevSourceResetKey] =\n    React.useState(sourceResetKey);\n  if (sourceResetKey !== prevSourceResetKey) {\n    setPrevSourceResetKey(sourceResetKey);\n    setState(safeSrc ? \"loading\" : \"blocked\");\n    setAspectRatio(explicitAspectRatio ?? \"\");\n  }\n\n  useKeyedLayoutEffect(\n    joinEffectKey([aspectRatio, notifyContentReady, state]),\n    () => {\n      notifyContentReady?.();\n    },\n  );\n\n  return (\n    <figure\n      aria-label={\n        state === \"failed\" ? `Image failed: ${alt}` : alt || title || \"Image\"\n      }\n      className=\"my-5 w-fit max-w-full\"\n      data-pretext-component={componentName}\n      data-pretext-image-height={height}\n      data-pretext-image-state={state}\n      data-pretext-image-src={safeSrc || undefined}\n      data-pretext-image-width={width}\n      role=\"group\"\n      style={aspectRatio ? { aspectRatio } : undefined}\n    >\n      {safeSrc ? (\n        <div\n          className=\"bg-muted/25 relative flex min-h-48 max-w-full items-center justify-center overflow-hidden rounded-md border\"\n          data-pretext-image-frame=\"\"\n          style={aspectRatio ? { aspectRatio } : undefined}\n        >\n          {state === \"loading\" ? (\n            <span className=\"text-muted-foreground absolute inset-x-4 top-1/2 -translate-y-1/2 text-center text-sm\">\n              Loading image\n            </span>\n          ) : null}\n          {state === \"failed\" ? (\n            <div\n              className=\"text-muted-foreground absolute inset-0 flex items-center justify-center px-4 text-center text-sm\"\n              role=\"alert\"\n            >\n              Could not load image{alt ? `: ${alt}` : \"\"}\n            </div>\n          ) : null}\n          {state === \"failed\" ? (\n            <div\n              aria-label={alt || \"Image\"}\n              className=\"text-muted-foreground text-sm\"\n              data-pretext-image-state=\"failed\"\n              role=\"img\"\n            >\n              Image failed to load: {alt}\n              <button\n                className=\"ml-2 text-xs underline underline-offset-4\"\n                type=\"button\"\n                onClick={() => setState(\"loading\")}\n              >\n                Retry image\n              </button>\n            </div>\n          ) : (\n            <img\n              alt={alt}\n              aria-describedby={title ? captionId : undefined}\n              className={[\n                \"block max-h-[70vh] max-w-full object-contain transition-opacity\",\n                state === \"loading\" ? \"opacity-0\" : \"\",\n              ]\n                .filter(Boolean)\n                .join(\" \")}\n              decoding=\"async\"\n              loading=\"lazy\"\n              src={safeSrc}\n              title={title}\n              onError={(event) => {\n                event.currentTarget.setAttribute(\n                  \"data-pretext-image-state\",\n                  \"failed\",\n                );\n                setState(\"failed\");\n              }}\n              onLoad={(event) => {\n                const image = event.currentTarget;\n                if (image.naturalWidth && image.naturalHeight) {\n                  setAspectRatio(\n                    `${image.naturalWidth} / ${image.naturalHeight}`,\n                  );\n                }\n                setState(\"ready\");\n              }}\n            />\n          )}\n        </div>\n      ) : (\n        <span\n          aria-label={alt || \"Blocked image\"}\n          className=\"bg-muted/35 text-muted-foreground flex min-h-24 items-center rounded-md border border-dashed px-4 text-sm\"\n          role=\"img\"\n        >\n          {alt || \"Blocked image\"}\n        </span>\n      )}\n      {title ? (\n        <figcaption\n          id={captionId}\n          className=\"text-muted-foreground mt-2 text-[0.9em]\"\n          data-pretext-image-caption=\"\"\n        >\n          {title}\n        </figcaption>\n      ) : null}\n    </figure>\n  );\n}\n\nfunction MarkdownVideoSurface({\n  controls = true,\n  label,\n  loop = false,\n  muted = false,\n  src,\n  title,\n}: {\n  controls?: boolean;\n  label: string;\n  loop?: boolean;\n  muted?: boolean;\n  src: string;\n  title?: string;\n}) {\n  const notifyContentReady = React.useContext(MarkdownContentReadyContext);\n  const safeSrc = sanitizeMarkdownMediaUrl(src);\n  const [failed, setFailed] = React.useState(false);\n  useKeyedLayoutEffect(\n    joinEffectKey([failed, notifyContentReady, safeSrc]),\n    () => {\n      notifyContentReady?.();\n    },\n  );\n  if (!safeSrc) {\n    return (\n      <div\n        aria-label={`Video blocked: ${label}`}\n        className=\"bg-muted/35 text-muted-foreground my-5 rounded-md border border-dashed p-4 text-sm\"\n        data-pretext-component=\"Video\"\n        data-pretext-video-state=\"blocked\"\n        role=\"group\"\n      >\n        {label}\n      </div>\n    );\n  }\n  return (\n    <figure\n      aria-label={failed ? `Video failed to load: ${label}` : label}\n      className=\"my-5 max-w-full\"\n      data-pretext-component=\"Video\"\n      data-pretext-video-state={failed ? \"failed\" : \"ready\"}\n      role=\"group\"\n    >\n      <video\n        className=\"bg-muted block max-h-[70vh] max-w-full rounded-md border\"\n        controls={controls}\n        loop={loop}\n        muted={muted}\n        preload=\"metadata\"\n        src={safeSrc}\n        title={title}\n        onError={() => setFailed(true)}\n      />\n      {title ? (\n        <figcaption className=\"text-muted-foreground mt-2 text-[0.9em]\">\n          {title}\n        </figcaption>\n      ) : null}\n    </figure>\n  );\n}\n\nfunction MarkdownTabs({ node }: { node: MarkdownHastElement | null }) {\n  const props = readComponentProps(\n    readDataProperty(node, \"dataPretextComponentProps\"),\n  );\n  const tabs = (node?.children ?? [])\n    .map(readHastElement)\n    .filter((child): child is MarkdownHastElement => {\n      return (\n        child?.tagName === \"div\" &&\n        readDataProperty(child, \"dataPretextComponentName\") === \"Tab\"\n      );\n    });\n  const [selected, setSelected] = React.useState(0);\n  const baseId = React.useId();\n  const label = readOptionalString(props.label) ?? \"Tabs\";\n\n  const select = (index: number) =>\n    setSelected((index + tabs.length) % tabs.length);\n\n  return (\n    <div className=\"my-5\" data-pretext-component=\"Tabs\">\n      <div aria-label={label} className=\"flex gap-1 border-b\" role=\"tablist\">\n        {tabs.map((tab, index) => {\n          const tabProps = readComponentProps(\n            readDataProperty(tab, \"dataPretextComponentProps\"),\n          );\n          const title =\n            readOptionalString(tabProps.title) ?? `Tab ${index + 1}`;\n          const active = selected === index;\n          return (\n            <button\n              key={index}\n              aria-controls={`${baseId}-panel-${index}`}\n              aria-selected={active}\n              className=\"px-3 py-2 text-sm font-medium\"\n              id={`${baseId}-tab-${index}`}\n              role=\"tab\"\n              tabIndex={active ? 0 : -1}\n              type=\"button\"\n              onClick={() => select(index)}\n              onKeyDown={(event) => {\n                if (event.key === \"ArrowRight\") {\n                  event.preventDefault();\n                  const nextIndex = (index + 1) % tabs.length;\n                  select(nextIndex);\n                  document\n                    .getElementById(`${baseId}-tab-${nextIndex}`)\n                    ?.focus();\n                } else if (event.key === \"End\") {\n                  event.preventDefault();\n                  select(tabs.length - 1);\n                  document\n                    .getElementById(`${baseId}-tab-${tabs.length - 1}`)\n                    ?.focus();\n                } else if (event.key === \"Home\") {\n                  event.preventDefault();\n                  select(0);\n                  document.getElementById(`${baseId}-tab-0`)?.focus();\n                }\n              }}\n            >\n              {title}\n            </button>\n          );\n        })}\n      </div>\n      {tabs.map((tab, index) => (\n        <div\n          key={index}\n          aria-labelledby={`${baseId}-tab-${index}`}\n          hidden={selected !== index}\n          id={`${baseId}-panel-${index}`}\n          className=\"pt-3\"\n          role=\"tabpanel\"\n        >\n          {renderHastChildren(tab.children)}\n        </div>\n      ))}\n    </div>\n  );\n}\n\n// Tags whose text is verbatim source (code) or already a highlight; wrapping a\n// match inside them would corrupt the rendered output, so they are skipped.\nconst MARKDOWN_SEARCH_SKIP_TAGS = new Set([\n  \"code\",\n  \"pre\",\n  \"mark\",\n  \"script\",\n  \"style\",\n  \"textarea\",\n]);\n\n// Browser-find-style highlighting for the in-app search: wraps every case-\n// insensitive occurrence of the active query in a <mark> so matches are visible\n// where the search navigates. Mutates the freshly cloned chunk tree in place,\n// matching the same trimmed-substring semantics as the toolbar match count.\ntype MarkdownSearchHighlightContext = { active: number; count: number };\n\nfunction highlightMarkdownSearchMatches(\n  nodes: MarkdownHastNode[],\n  lowerQuery: string,\n  context: MarkdownSearchHighlightContext,\n) {\n  for (let index = 0; index < nodes.length; index += 1) {\n    const node = nodes[index];\n    if (node.type === \"text\" && typeof node.value === \"string\") {\n      const replacement = splitMarkdownTextForSearch(\n        node.value,\n        lowerQuery,\n        context,\n      );\n      if (replacement) {\n        nodes.splice(index, 1, ...replacement);\n        index += replacement.length - 1;\n      }\n      continue;\n    }\n    if (node.type === \"element\" && Array.isArray(node.children)) {\n      const tagName = (node as MarkdownHastElement).tagName.toLowerCase();\n      if (MARKDOWN_SEARCH_SKIP_TAGS.has(tagName)) continue;\n      highlightMarkdownSearchMatches(node.children, lowerQuery, context);\n    }\n  }\n}\n\nfunction splitMarkdownTextForSearch(\n  value: string,\n  lowerQuery: string,\n  context: MarkdownSearchHighlightContext,\n): MarkdownHastNode[] | null {\n  const lowerValue = value.toLowerCase();\n  let matchStart = lowerValue.indexOf(lowerQuery);\n  if (matchStart === -1) return null;\n\n  const out: MarkdownHastNode[] = [];\n  let cursor = 0;\n  while (matchStart !== -1) {\n    if (matchStart > cursor) {\n      out.push({ type: \"text\", value: value.slice(cursor, matchStart) });\n    }\n    const matchEnd = matchStart + lowerQuery.length;\n    const isActive = context.count === context.active;\n    context.count += 1;\n    out.push({\n      type: \"element\",\n      tagName: \"mark\",\n      properties: isActive\n        ? { dataPretextSearchMatch: \"\", dataPretextSearchMatchActive: \"\" }\n        : { dataPretextSearchMatch: \"\" },\n      children: [{ type: \"text\", value: value.slice(matchStart, matchEnd) }],\n    });\n    cursor = matchEnd;\n    matchStart = lowerValue.indexOf(lowerQuery, cursor);\n  }\n  if (cursor < value.length) {\n    out.push({ type: \"text\", value: value.slice(cursor) });\n  }\n  return out;\n}\n\nfunction cloneHastNode<T extends MarkdownHastNode>(node: T): T {\n  if (!(\"children\" in node) || !Array.isArray(node.children)) {\n    return { ...node };\n  }\n\n  return {\n    ...node,\n    children: node.children.map((child) =>\n      cloneHastNode(child as MarkdownHastNode),\n    ),\n    properties: readHastElement(node)?.properties\n      ? { ...readHastElement(node)!.properties }\n      : undefined,\n  } as T;\n}\n\nfunction suppressDomFragmentIds(nodes: MarkdownHastNode[]) {\n  for (const node of nodes) {\n    const element = readHastElement(node);\n    if (!element) continue;\n\n    const id = element.properties?.id;\n    if (\n      typeof id === \"string\" &&\n      id &&\n      shouldSuppressDomFragmentId(element, id)\n    ) {\n      element.properties = {\n        ...element.properties,\n        dataPretextFragmentId: id,\n      };\n      delete element.properties.id;\n    }\n\n    suppressDomFragmentIds(element.children);\n  }\n}\n\nfunction shouldSuppressDomFragmentId(element: MarkdownHastElement, id: string) {\n  return /^h[1-6]$/.test(element.tagName) || /^user-content-fn/.test(id);\n}\n\nfunction readDataProperty(node: unknown, property: string) {\n  const element = readHastElement(node);\n  const value =\n    element?.properties?.[property] ?? element?.properties?.[toKebab(property)];\n  return typeof value === \"string\" ? value : \"\";\n}\n\nfunction readStringProperty(value: unknown) {\n  return typeof value === \"string\" ? value : null;\n}\n\nfunction readTrustedDataProperty(node: unknown, property: string) {\n  if (!isTrustedPretextComponentNode(node)) return \"\";\n  return readDataProperty(node, property);\n}\n\nfunction isTrustedPretextComponentNode(node: unknown) {\n  return readHastElement(node)?.properties?.pretextComponentTrusted === true;\n}\n\nfunction hasDataProperty(node: unknown, property: string) {\n  const element = readHastElement(node);\n  return (\n    element?.properties != null &&\n    (Object.hasOwn(element.properties, property) ||\n      Object.hasOwn(element.properties, toKebab(property)))\n  );\n}\n\nfunction withoutPretextAlertMetadata(props: Record<string, unknown>) {\n  const next = { ...props };\n  delete next.dataPretextAlertKind;\n  delete next.dataPretextAlertTitle;\n  delete next[\"data-pretext-alert-kind\"];\n  delete next[\"data-pretext-alert-title\"];\n  return next;\n}\n\nfunction withoutInternalPretextMetadata(props: Record<string, unknown>) {\n  const next = { ...props };\n  for (const key of Object.keys(next)) {\n    if (/^dataPretext(?:Component|Callout|Heading)/.test(key)) {\n      delete next[key];\n    }\n    if (/^data-pretext-(?:component|callout|heading)/.test(key)) {\n      delete next[key];\n    }\n    if (key === \"pretextComponentTrusted\") {\n      delete next[key];\n    }\n  }\n  return next;\n}\n\nfunction readHastElement(node: unknown): MarkdownHastElement | null {\n  return node &&\n    typeof node === \"object\" &&\n    (node as MarkdownHastElement).type === \"element\"\n    ? (node as MarkdownHastElement)\n    : null;\n}\n\nfunction readPreCodeElement(node: unknown): MarkdownHastElement | null {\n  const pre = readHastElement(node);\n  if (pre?.tagName !== \"pre\") return null;\n  const code =\n    pre.children\n      .map(readHastElement)\n      .find((child): child is MarkdownHastElement =>\n        Boolean(child && child.tagName === \"code\"),\n      ) ?? null;\n  return code;\n}\n\nfunction readCodeLanguage(code: MarkdownHastElement | null) {\n  const className = code?.properties?.className;\n  const classes = Array.isArray(className) ? className : [className];\n  const languageClass = classes.find(\n    (value): value is string =>\n      typeof value === \"string\" && value.startsWith(\"language-\"),\n  );\n  const language = languageClass?.slice(\"language-\".length).toLowerCase();\n  if (language === \"mmd\" || language === \"mermaid-js\") return \"mermaid\";\n  return language ?? null;\n}\n\nfunction readCodeMetadata(code: MarkdownHastElement | null) {\n  return parseCodeMetadata(readDataProperty(code, \"dataPretextCodeMeta\"));\n}\n\nfunction parseCodeMetadata(meta: string) {\n  const result: {\n    caption?: string;\n    highlightedLines: Set<number>;\n    highlightPattern: string;\n    lineNumberStart?: number;\n    showLineNumbers: boolean;\n    title?: string;\n  } = {\n    highlightedLines: new Set<number>(),\n    highlightPattern: \"\",\n    showLineNumbers: false,\n  };\n  for (const match of meta.matchAll(\n    /(?:^|\\s)(title|caption)=(?:\"([^\"]*)\"|'([^']*)'|([^\\s]+))/g,\n  )) {\n    const key = match[1] as \"caption\" | \"title\";\n    result[key] = match[2] ?? match[3] ?? match[4] ?? \"\";\n  }\n  const lineNumbers = /(?:^|\\s)showLineNumbers(?:\\{(\\d+)\\})?(?=\\s|$)/i.exec(\n    meta,\n  );\n  if (lineNumbers) {\n    result.showLineNumbers = true;\n    result.lineNumberStart = lineNumbers[1] ? Number(lineNumbers[1]) : 1;\n  }\n  for (const match of meta.matchAll(/\\{(\\d+(?:-\\d+)?(?:,\\d+(?:-\\d+)?)*)\\}/g)) {\n    for (const value of match[1]!.split(\",\")) {\n      addHighlightedCodeLineSpec(result.highlightedLines, value);\n    }\n  }\n  const highlightPattern = /\\/([^/\\n]+)\\//.exec(meta);\n  result.highlightPattern = highlightPattern?.[1] ?? \"\";\n  return result;\n}\n\nfunction addHighlightedCodeLineSpec(lines: Set<number>, spec: string) {\n  const range = /^(\\d+)-(\\d+)$/.exec(spec);\n  if (range) {\n    const start = Number(range[1]);\n    const end = Number(range[2]);\n    if (!isSafeHighlightedCodeLine(start) || !isSafeHighlightedCodeLine(end)) {\n      return;\n    }\n    if (end < start || end - start > 500) return;\n    for (let line = start; line <= end; line += 1) lines.add(line);\n    return;\n  }\n\n  const line = Number(spec);\n  if (isSafeHighlightedCodeLine(line)) lines.add(line);\n}\n\nfunction toKebab(value: string) {\n  return value.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);\n}\n\nfunction alertIconForKind(kind: string) {\n  switch (kind) {\n    case \"note\":\n      return Info;\n    case \"tip\":\n      return Lightbulb;\n    case \"warning\":\n      return TriangleAlert;\n    case \"caution\":\n      return CircleAlert;\n    default:\n      return BadgeAlert;\n  }\n}\n\nfunction calloutTitle(kind: string) {\n  const titles: Record<string, string> = {\n    caution: \"Caution\",\n    important: \"Important\",\n    note: \"Note\",\n    tip: \"Tip\",\n    warning: \"Warning\",\n  };\n  return titles[kind] ?? \"Note\";\n}\n\nfunction linkKindForHref(href: string) {\n  if (href.startsWith(\"#\")) return \"fragment\";\n  if (href.startsWith(\"/\")) return \"root\";\n  if (/^mailto:/i.test(href)) return \"email\";\n  if (/^(?:https?:)?\\/\\//i.test(href)) return \"external\";\n  return \"relative\";\n}\n\nfunction linkFormForHref({ href, text }: { href: string; text: string }) {\n  if (/^mailto:/i.test(href) && href.slice(\"mailto:\".length) === text) {\n    return \"email-autolink\";\n  }\n  if (\n    /^https?:\\/\\/www\\./i.test(href) &&\n    href.replace(/^https?:\\/\\//i, \"\") === text\n  ) {\n    return \"autolink\";\n  }\n  if (href === text && /^(?:https?:)?\\/\\//i.test(href)) return \"autolink\";\n  return \"inline\";\n}\n\nfunction footnoteLabelForLink({\n  href,\n  label,\n  text,\n}: {\n  href: string;\n  label: unknown;\n  text: string;\n}) {\n  const display = footnoteDisplayForHref(href, text);\n  if (/^#(?:user-content-)?fnref-/i.test(href)) {\n    return \"Back to footnote reference ↩\";\n  }\n  if (/^#(?:user-content-)?fn-/i.test(href)) {\n    return `Footnote${display ? ` ${display}` : \"\"}`;\n  }\n  if (typeof label === \"string\" && label) return label;\n  return undefined;\n}\n\nfunction footnoteDisplayForHref(href: string, text: string) {\n  const visibleText = text.trim().replace(/^\\[|\\]$/g, \"\");\n  if (/^\\d+[a-z]?$/i.test(visibleText)) return visibleText;\n\n  const match = /^#(?:user-content-)?fn(?:ref)?-([A-Za-z0-9_-]+)/i.exec(href);\n  if (!match) return \"\";\n  return match[1]!.replace(/-/g, \" \");\n}\n\nfunction reactNodeText(node: React.ReactNode): string {\n  if (typeof node === \"string\" || typeof node === \"number\") return String(node);\n  if (Array.isArray(node)) return node.map(reactNodeText).join(\"\");\n  return \"\";\n}\n\nfunction hasDescendantElement(\n  element: MarkdownHastElement | null,\n  tagName: string,\n): boolean {\n  if (!element) return false;\n  return element.children.some((child) => {\n    const childElement = readHastElement(child);\n    return (\n      childElement?.tagName === tagName ||\n      hasDescendantElement(childElement, tagName)\n    );\n  });\n}\n\nfunction isWhitespaceText(node: MarkdownHastNode) {\n  return (\n    node.type === \"text\" &&\n    typeof node.value === \"string\" &&\n    node.value.trim() === \"\"\n  );\n}\n\nfunction extractHastText(element: MarkdownHastElement | null): string {\n  return extractHastNodeText(element);\n}\n\nfunction extractHastNodeText(\n  node: MarkdownHastNode | null | undefined,\n): string {\n  if (!node) return \"\";\n  if (node.type === \"text\" && typeof node.value === \"string\") return node.value;\n  const element = readHastElement(node);\n  if (!element) return \"\";\n  return element.children.map(extractHastNodeText).join(\"\");\n}\n\ntype MarkdownTableModel = {\n  caption: MarkdownHastElement | null;\n  captionText: string;\n  cellCount: number;\n  columnCount: number;\n  rows: MarkdownTableRowModel[];\n  tsv: string;\n};\n\ntype MarkdownTableRowModel = {\n  cells: MarkdownTableCellModel[];\n  key: string;\n  rowIndex: number;\n};\n\ntype MarkdownTableCellModel = {\n  align: MarkdownTableCellAlign | null;\n  children: readonly MarkdownHastNode[];\n  columnIndex: number;\n  headerId: string | null;\n  headers: string | null;\n  isHeader: boolean;\n  key: string;\n  text: string;\n};\n\ntype MarkdownTableCellAlign = \"center\" | \"char\" | \"justify\" | \"left\" | \"right\";\n\ntype MarkdownTableFindEntry = {\n  key: string;\n  rowIndex: number;\n  text: string;\n};\n\nfunction MarkdownTable({ node, style, ...props }: any) {\n  const table = readHastElement(node);\n  const model = React.useMemo(() => readMarkdownTableModel(table), [table]);\n  if (model && shouldVirtualizeMarkdownTable(model)) {\n    return <MarkdownVirtualTable model={model} style={style} />;\n  }\n\n  const ariaColumnCount = model?.columnCount ?? tableColumnCount(table);\n  const columnCount = ariaColumnCount ?? 0;\n  return (\n    <div\n      aria-label=\"Markdown table\"\n      className=\"group relative my-4 overflow-hidden rounded-lg border\"\n      data-markdown-table-region=\"\"\n      role=\"region\"\n      tabIndex={0}\n      onKeyDown={handleHorizontalScrollKeyDown}\n    >\n      <TableCopyButton copyText={model?.tsv} />\n      <div className=\"overflow-x-auto\" data-markdown-table-scroll=\"\">\n        <table\n          {...props}\n          aria-colcount={ariaColumnCount}\n          aria-rowcount={model?.rows.length ?? tableRowCount(table)}\n          className=\"w-full border-collapse text-[0.85em]\"\n          data-markdown-table=\"\"\n          style={{\n            ...style,\n            minWidth: markdownTableMinWidth(columnCount, style?.minWidth),\n          }}\n        />\n      </div>\n    </div>\n  );\n}\n\nfunction MarkdownVirtualTable({\n  model,\n  style,\n}: {\n  model: MarkdownTableModel;\n  style?: React.CSSProperties;\n}) {\n  const scrollRef = React.useRef<HTMLDivElement | null>(null);\n  const scrollFrameRef = React.useRef<number | null>(null);\n  const [viewport, setViewport] = React.useState({\n    height: MARKDOWN_TABLE_VIRTUALIZED_VIEWPORT_HEIGHT_PX,\n    scrollTop: 0,\n  });\n  const visibleWindow = React.useMemo(\n    () =>\n      markdownVirtualTableVisibleWindow({\n        rowCount: model.rows.length,\n        scrollTop: viewport.scrollTop,\n        viewportHeight: viewport.height,\n      }),\n    [model.rows.length, viewport.height, viewport.scrollTop],\n  );\n  const visibleRows = React.useMemo(\n    () => model.rows.slice(visibleWindow.start, visibleWindow.end),\n    [model.rows, visibleWindow.end, visibleWindow.start],\n  );\n  const findEntries = React.useMemo(\n    () => markdownTableFindEntries(model.rows),\n    [model.rows],\n  );\n  const updateViewport = React.useCallback(() => {\n    const element = scrollRef.current;\n    if (!element) return;\n    setViewport({\n      height:\n        element.clientHeight || MARKDOWN_TABLE_VIRTUALIZED_VIEWPORT_HEIGHT_PX,\n      scrollTop: element.scrollTop,\n    });\n  }, []);\n  const handleScroll = React.useCallback(() => {\n    if (scrollFrameRef.current != null) return;\n    scrollFrameRef.current = requestAnimationFrame(() => {\n      scrollFrameRef.current = null;\n      updateViewport();\n    });\n  }, [updateViewport]);\n  const revealRow = React.useCallback((rowIndex: number) => {\n    const top = rowIndex * MARKDOWN_TABLE_VIRTUALIZED_ROW_HEIGHT_PX;\n    scrollRef.current?.scrollTo?.({ behavior: \"auto\", top });\n    if (scrollRef.current) scrollRef.current.scrollTop = top;\n    setViewport((current) => ({\n      height: scrollRef.current?.clientHeight || current.height,\n      scrollTop: top,\n    }));\n  }, []);\n\n  useKeyedLayoutEffect(\n    joinEffectKey([\"markdown-virtual-table-viewport\", model.rows.length]),\n    () => {\n      updateViewport();\n      return () => {\n        if (scrollFrameRef.current != null) {\n          cancelAnimationFrame(scrollFrameRef.current);\n        }\n      };\n    },\n  );\n\n  return (\n    <div\n      aria-label=\"Markdown table\"\n      className=\"group relative my-4 overflow-hidden rounded-lg border\"\n      data-markdown-table-region=\"\"\n      data-markdown-table-virtualized-region=\"\"\n      role=\"region\"\n      tabIndex={0}\n      onKeyDown={handleHorizontalScrollKeyDown}\n    >\n      <TableCopyButton copyText={model.tsv} />\n      <div\n        ref={scrollRef}\n        className=\"max-h-[560px] overflow-auto\"\n        data-markdown-table-scroll=\"\"\n        onScroll={handleScroll}\n      >\n        <table\n          aria-colcount={model.columnCount}\n          aria-rowcount={model.rows.length}\n          className=\"w-full border-collapse text-[0.85em]\"\n          data-markdown-table=\"\"\n          data-markdown-table-mounted-rows={visibleRows.length}\n          data-markdown-table-total-rows={model.rows.length}\n          data-markdown-table-virtualized=\"\"\n          style={{\n            ...style,\n            minWidth: markdownTableMinWidth(model.columnCount, style?.minWidth),\n          }}\n        >\n          {model.caption ? (\n            <caption className=\"text-muted-foreground bg-background sticky top-0 z-20 caption-top px-3 py-2 text-left font-medium\">\n              {renderHastChildren(model.caption.children)}\n            </caption>\n          ) : null}\n          <tbody>\n            {visibleWindow.start > 0 ? (\n              <MarkdownVirtualTableSpacerRow\n                columnCount={model.columnCount}\n                height={\n                  visibleWindow.start * MARKDOWN_TABLE_VIRTUALIZED_ROW_HEIGHT_PX\n                }\n              />\n            ) : null}\n            {visibleRows.map((row) => (\n              <MarkdownVirtualTableRow key={row.key} row={row} />\n            ))}\n            {visibleWindow.end < model.rows.length ? (\n              <MarkdownVirtualTableSpacerRow\n                columnCount={model.columnCount}\n                height={\n                  (model.rows.length - visibleWindow.end) *\n                  MARKDOWN_TABLE_VIRTUALIZED_ROW_HEIGHT_PX\n                }\n              />\n            ) : null}\n          </tbody>\n        </table>\n        <MarkdownVirtualTableNativeFindIndex\n          entries={findEntries}\n          revealRow={revealRow}\n        />\n      </div>\n    </div>\n  );\n}\n\nfunction MarkdownVirtualTableRow({ row }: { row: MarkdownTableRowModel }) {\n  return (\n    <tr\n      aria-rowindex={row.rowIndex + 1}\n      className=\"border-b\"\n      data-markdown-table-row=\"\"\n      data-pretext-table-row-index={row.rowIndex + 1}\n      style={{\n        height: MARKDOWN_TABLE_VIRTUALIZED_ROW_HEIGHT_PX,\n      }}\n    >\n      {row.cells.map((cell) => (\n        <MarkdownVirtualTableCell key={cell.key} cell={cell} />\n      ))}\n    </tr>\n  );\n}\n\nfunction MarkdownVirtualTableCell({ cell }: { cell: MarkdownTableCellModel }) {\n  const Cell = cell.isHeader ? \"th\" : \"td\";\n  return (\n    <Cell\n      align={cell.align ?? undefined}\n      aria-colindex={cell.columnIndex + 1}\n      className={markdownTableCellClassName({\n        align: cell.align,\n        isHeader: cell.isHeader,\n      })}\n      data-markdown-table-cell=\"\"\n      data-pretext-table-column-index={cell.columnIndex + 1}\n      headers={cell.isHeader ? undefined : (cell.headers ?? undefined)}\n      id={cell.headerId ?? undefined}\n      scope={cell.isHeader ? \"col\" : undefined}\n    >\n      <span className=\"block truncate\">\n        {renderHastChildren(cell.children)}\n      </span>\n    </Cell>\n  );\n}\n\nfunction MarkdownVirtualTableSpacerRow({\n  columnCount,\n  height,\n}: {\n  columnCount: number;\n  height: number;\n}) {\n  return (\n    <tr aria-hidden=\"true\" data-markdown-table-spacer-row=\"\">\n      <td colSpan={columnCount} style={{ height, padding: 0 }} />\n    </tr>\n  );\n}\n\nfunction MarkdownVirtualTableNativeFindIndex({\n  entries,\n  revealRow,\n}: {\n  entries: readonly MarkdownTableFindEntry[];\n  revealRow: (rowIndex: number) => void;\n}) {\n  return (\n    <div\n      aria-hidden=\"true\"\n      className=\"pointer-events-none absolute top-0 left-0 h-px w-px overflow-hidden opacity-0\"\n      data-markdown-table-native-find-index=\"\"\n      data-markdown-table-native-find-entries={entries.length}\n    >\n      {entries.map((entry) => (\n        <MarkdownVirtualTableNativeFindEntry\n          key={entry.key}\n          entry={entry}\n          revealRow={revealRow}\n        />\n      ))}\n    </div>\n  );\n}\n\nfunction MarkdownVirtualTableNativeFindEntry({\n  entry,\n  revealRow,\n}: {\n  entry: MarkdownTableFindEntry;\n  revealRow: (rowIndex: number) => void;\n}) {\n  const ref = React.useRef<HTMLSpanElement | null>(null);\n\n  useKeyedLayoutEffect(joinEffectKey([entry.key, entry.rowIndex]), () => {\n    const element = ref.current;\n    if (!element) return;\n    element.setAttribute(\"hidden\", \"until-found\");\n\n    const handleBeforeMatch = () => {\n      revealRow(entry.rowIndex);\n      requestAnimationFrame(() => {\n        element.setAttribute(\"hidden\", \"until-found\");\n      });\n    };\n\n    element.addEventListener(\"beforematch\", handleBeforeMatch);\n    return () => {\n      element.removeEventListener(\"beforematch\", handleBeforeMatch);\n    };\n  });\n\n  return (\n    <span\n      ref={ref}\n      className=\"absolute top-0 left-0 block h-px w-px overflow-hidden whitespace-pre\"\n      data-markdown-table-native-find-entry=\"\"\n      data-markdown-table-native-find-row={entry.rowIndex + 1}\n    >\n      {entry.text || \" \"}\n    </span>\n  );\n}\n\nfunction readMarkdownTableModel(\n  table: MarkdownHastElement | null,\n): MarkdownTableModel | null {\n  if (!table) return null;\n  const caption = table.children\n    .map(readHastElement)\n    .find(\n      (child): child is MarkdownHastElement => child?.tagName === \"caption\",\n    );\n  const rows = tableRows(table).map((row, rowIndex) =>\n    readMarkdownTableRowModel(row, rowIndex),\n  );\n  if (!rows.length) return null;\n\n  const columnCount = rows.reduce(\n    (max, row) => Math.max(max, row.cells.length),\n    0,\n  );\n  const cellCount = rows.reduce((sum, row) => sum + row.cells.length, 0);\n  return {\n    caption: caption ?? null,\n    captionText: normalizeTableCellText(extractHastNodeText(caption)),\n    cellCount,\n    columnCount,\n    rows,\n    tsv: serializeMarkdownTableModelAsTsv(rows),\n  };\n}\n\nfunction readMarkdownTableRowModel(\n  row: MarkdownHastElement,\n  rowIndex: number,\n): MarkdownTableRowModel {\n  return {\n    cells: tableCells(row).map((cell, columnIndex) =>\n      readMarkdownTableCellModel(cell, rowIndex, columnIndex),\n    ),\n    key: `row-${rowIndex}`,\n    rowIndex,\n  };\n}\n\nfunction readMarkdownTableCellModel(\n  cell: MarkdownHastElement,\n  rowIndex: number,\n  columnIndex: number,\n): MarkdownTableCellModel {\n  return {\n    align: readMarkdownTableCellAlign(cell.properties?.align),\n    children: cell.children,\n    columnIndex,\n    headerId: readStringProperty(cell.properties?.id),\n    headers: readStringProperty(cell.properties?.headers),\n    isHeader: cell.tagName === \"th\",\n    key: `cell-${rowIndex}-${columnIndex}`,\n    text: normalizeTableCellText(extractHastNodeText(cell)),\n  };\n}\n\nfunction readMarkdownTableCellAlign(\n  value: unknown,\n): MarkdownTableCellAlign | null {\n  return value === \"center\" ||\n    value === \"char\" ||\n    value === \"justify\" ||\n    value === \"left\" ||\n    value === \"right\"\n    ? value\n    : null;\n}\n\nfunction shouldVirtualizeMarkdownTable(model: MarkdownTableModel) {\n  return (\n    model.rows.length >= MARKDOWN_TABLE_VIRTUALIZATION_ROW_THRESHOLD ||\n    model.cellCount >= MARKDOWN_TABLE_VIRTUALIZATION_CELL_THRESHOLD\n  );\n}\n\nfunction markdownVirtualTableVisibleWindow({\n  rowCount,\n  scrollTop,\n  viewportHeight,\n}: {\n  rowCount: number;\n  scrollTop: number;\n  viewportHeight: number;\n}) {\n  const visibleStart = Math.max(\n    0,\n    Math.floor(scrollTop / MARKDOWN_TABLE_VIRTUALIZED_ROW_HEIGHT_PX) -\n      MARKDOWN_TABLE_VIRTUALIZED_OVERSCAN_ROWS,\n  );\n  const visibleCount =\n    Math.ceil(viewportHeight / MARKDOWN_TABLE_VIRTUALIZED_ROW_HEIGHT_PX) +\n    MARKDOWN_TABLE_VIRTUALIZED_OVERSCAN_ROWS * 2;\n  return {\n    end: Math.min(rowCount, visibleStart + Math.max(1, visibleCount)),\n    start: visibleStart,\n  };\n}\n\nfunction markdownTableFindEntries(\n  rows: readonly MarkdownTableRowModel[],\n): MarkdownTableFindEntry[] {\n  const entries: MarkdownTableFindEntry[] = [];\n  for (\n    let rowIndex = 0;\n    rowIndex < rows.length;\n    rowIndex += MARKDOWN_TABLE_NATIVE_FIND_ROWS_PER_ENTRY\n  ) {\n    const chunkRows = rows.slice(\n      rowIndex,\n      rowIndex + MARKDOWN_TABLE_NATIVE_FIND_ROWS_PER_ENTRY,\n    );\n    entries.push({\n      key: `table-find-${rowIndex}`,\n      rowIndex,\n      text: chunkRows\n        .map((row) => row.cells.map((cell) => cell.text).join(\"\\t\"))\n        .join(\"\\n\"),\n    });\n  }\n  return entries;\n}\n\nfunction serializeMarkdownTableModelAsTsv(\n  rows: readonly MarkdownTableRowModel[],\n) {\n  return rows\n    .map((row) => row.cells.map((cell) => cell.text).join(\"\\t\"))\n    .join(\"\\n\");\n}\n\nfunction markdownTableMinWidth(\n  columnCount: number,\n  fallback: React.CSSProperties[\"minWidth\"],\n) {\n  return columnCount >= 4 ? `${Math.max(640, columnCount * 160)}px` : fallback;\n}\n\nfunction markdownTableCellClassName({\n  align,\n  isHeader,\n}: {\n  align: string | null;\n  isHeader: boolean;\n}) {\n  return [\n    \"min-w-0 border-r px-3 py-1.5 last:border-r-0\",\n    isHeader\n      ? \"border-b bg-muted/55 text-left font-medium\"\n      : \"border-t align-top\",\n    align === \"center\" ? \"text-center\" : \"\",\n    align === \"right\" ? \"text-right tabular-nums\" : \"\",\n  ]\n    .filter(Boolean)\n    .join(\" \");\n}\n\nfunction handleHorizontalScrollKeyDown(\n  event: React.KeyboardEvent<HTMLElement>,\n) {\n  const element =\n    event.currentTarget.querySelector<HTMLElement>(\n      \"[data-markdown-table-scroll]\",\n    ) ?? event.currentTarget;\n  if (event.key === \"ArrowRight\") {\n    element.scrollLeft += 50;\n    event.preventDefault();\n  } else if (event.key === \"ArrowLeft\") {\n    element.scrollLeft -= 50;\n    event.preventDefault();\n  } else if (event.key === \"End\") {\n    element.scrollLeft = Math.max(0, element.scrollWidth - element.clientWidth);\n    event.preventDefault();\n  } else if (event.key === \"Home\") {\n    element.scrollLeft = 0;\n    event.preventDefault();\n  }\n}\n\nfunction tableRowCount(table: MarkdownHastElement | null) {\n  if (!table) return undefined;\n  return countDescendantElements(table, \"tr\");\n}\n\nfunction tableColumnCount(table: MarkdownHastElement | null) {\n  const firstRow = findDescendantElement(table, \"tr\");\n  if (!firstRow) return undefined;\n  return firstRow.children.filter((child) => {\n    const element = readHastElement(child);\n    return element?.tagName === \"td\" || element?.tagName === \"th\";\n  }).length;\n}\n\nfunction tableRows(element: MarkdownHastElement) {\n  const rows: MarkdownHastElement[] = [];\n  for (const child of element.children) {\n    const childElement = readHastElement(child);\n    if (!childElement) continue;\n    if (childElement.tagName === \"tr\") {\n      rows.push(childElement);\n    } else if (\n      childElement.tagName === \"thead\" ||\n      childElement.tagName === \"tbody\" ||\n      childElement.tagName === \"tfoot\"\n    ) {\n      rows.push(...tableRows(childElement));\n    }\n  }\n  return rows;\n}\n\nfunction tableCells(row: MarkdownHastElement) {\n  return row.children\n    .map(readHastElement)\n    .filter(\n      (child): child is MarkdownHastElement =>\n        child?.tagName === \"td\" || child?.tagName === \"th\",\n    );\n}\n\nfunction countDescendantElements(\n  element: MarkdownHastElement,\n  tagName: string,\n): number {\n  return element.children.reduce((sum, child) => {\n    const childElement = readHastElement(child);\n    if (!childElement) return sum;\n    return (\n      sum +\n      (childElement.tagName === tagName ? 1 : 0) +\n      countDescendantElements(childElement, tagName)\n    );\n  }, 0);\n}\n\nfunction findDescendantElement(\n  element: MarkdownHastElement | null,\n  tagName: string,\n): MarkdownHastElement | null {\n  if (!element) return null;\n  for (const child of element.children) {\n    const childElement = readHastElement(child);\n    if (!childElement) continue;\n    if (childElement.tagName === tagName) return childElement;\n    const found = findDescendantElement(childElement, tagName);\n    if (found) return found;\n  }\n  return null;\n}\n\n// A subtle hover copy affordance in the table's top-right corner, replacing the\n// persistent chrome bar so the table reads as a clean document table.\nfunction TableCopyButton({ copyText }: { copyText?: string }) {\n  const [copied, setCopied] = React.useState(false);\n  return (\n    <button\n      aria-label=\"Copy table as TSV\"\n      className=\"bg-background/90 text-muted-foreground hover:text-foreground absolute top-2 right-2 z-10 inline-flex items-center gap-1 rounded-md border px-2 py-1 text-xs font-medium opacity-0 shadow-sm backdrop-blur-sm transition-opacity group-hover:opacity-100 focus-visible:opacity-100\"\n      type=\"button\"\n      onClick={(event) => {\n        copyTable(event.currentTarget, copyText);\n        setCopied(true);\n        window.setTimeout(() => setCopied(false), 1200);\n      }}\n    >\n      {copied ? (\n        <Check className=\"size-3.5\" aria-hidden=\"true\" />\n      ) : (\n        <Copy className=\"size-3.5\" aria-hidden=\"true\" />\n      )}\n      {copied ? \"Copied\" : \"Copy\"}\n    </button>\n  );\n}\n\nfunction copyTable(button: HTMLButtonElement, copyText?: string) {\n  const region = button.closest('[role=\"region\"]');\n\n  const selection = window.getSelection();\n  const selectedText =\n    selection &&\n    selection.rangeCount > 0 &&\n    region?.contains(selection.anchorNode)\n      ? selection.toString()\n      : \"\";\n  if (selectedText.trim()) {\n    void navigator.clipboard?.writeText(selectedText.trim());\n    return;\n  }\n\n  if (copyText != null) {\n    void navigator.clipboard?.writeText(copyText);\n    return;\n  }\n\n  const table = region?.querySelector(\"table\");\n  if (!table) return;\n  void navigator.clipboard?.writeText(serializeTableAsTsv(table));\n}\n\nfunction serializeTableAsTsv(table: HTMLTableElement) {\n  return Array.from(table.querySelectorAll(\"tr\"))\n    .map((row) =>\n      Array.from(row.querySelectorAll(\"th,td\"))\n        .map((cell) => normalizeTableCellText(cell.textContent ?? \"\"))\n        .join(\"\\t\"),\n    )\n    .join(\"\\n\");\n}\n\nfunction normalizeTableCellText(value: string) {\n  return value.trim().replace(/[\\t\\r\\n ]+/g, \" \");\n}\n\nfunction copyHeadingLink(id: string) {\n  const base = `${window.location.origin}${window.location.pathname}${window.location.search}`;\n  void navigator.clipboard?.writeText(`${base}#${id}`);\n}\n\nfunction readComponentProps(value: string): Record<string, unknown> {\n  try {\n    const parsed = JSON.parse(value);\n    return parsed && typeof parsed === \"object\"\n      ? (parsed as Record<string, unknown>)\n      : {};\n  } catch {\n    return {};\n  }\n}\n\nfunction readOptionalString(value: unknown) {\n  return typeof value === \"string\" && value ? value : undefined;\n}\n\nfunction readOptionalNumber(value: unknown) {\n  const parsed =\n    typeof value === \"number\"\n      ? value\n      : typeof value === \"string\"\n        ? Number(value)\n        : NaN;\n  return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;\n}\n\nfunction readOptionalBoolean(value: unknown) {\n  if (typeof value === \"boolean\") return value;\n  if (typeof value !== \"string\") return undefined;\n  if (value === \"true\") return true;\n  if (value === \"false\") return false;\n  return undefined;\n}\n",
      "type": "registry:ui",
      "target": "@ui/markdown-greenfield-renderer.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/markdown-greenfield-virtualizer.ts",
      "content": "\"use client\";\n\nimport type { MarkdownGreenfieldChunk } from \"./markdown-greenfield-document\";\nimport type { MarkdownGreenfieldChunkFrame } from \"./markdown-greenfield-layout\";\n\nexport type MarkdownGreenfieldScrollAnchor = {\n  chunkId: string;\n  /** Negative when the viewport is in document padding before the chunk. */\n  offsetWithinChunkPx: number;\n  chunkHeightPx: number;\n};\n\nexport type MarkdownGreenfieldVisibleRange = {\n  start: number;\n  end: number;\n};\n\nexport type MarkdownGreenfieldVisibleProjection = {\n  frameIds: readonly string[];\n  range: MarkdownGreenfieldVisibleRange;\n};\n\nexport function getMarkdownGreenfieldVisibleRange({\n  frames,\n  overscanPx,\n  scrollTop,\n  viewportHeight,\n}: {\n  frames: readonly MarkdownGreenfieldChunkFrame[];\n  overscanPx: number;\n  scrollTop: number;\n  viewportHeight: number;\n}): MarkdownGreenfieldVisibleRange {\n  if (!frames.length) return { start: 0, end: 0 };\n  const start = firstFrameWithBottomAfter(\n    frames,\n    Math.max(0, scrollTop - overscanPx),\n  );\n  const end = firstFrameWithTopAtOrAfter(\n    frames,\n    scrollTop + viewportHeight + overscanPx,\n  );\n  return { start, end: Math.max(start + 1, end) };\n}\n\nexport function createMarkdownGreenfieldVisibleProjection({\n  frames,\n  range,\n}: {\n  frames: readonly MarkdownGreenfieldChunkFrame[];\n  range: MarkdownGreenfieldVisibleRange;\n}): MarkdownGreenfieldVisibleProjection {\n  return {\n    frameIds: frames.slice(range.start, range.end).map((frame) => frame.id),\n    range,\n  };\n}\n\nexport function getMarkdownGreenfieldVisibleProjection({\n  frames,\n  overscanPx,\n  scrollTop,\n  viewportHeight,\n}: {\n  frames: readonly MarkdownGreenfieldChunkFrame[];\n  overscanPx: number;\n  scrollTop: number;\n  viewportHeight: number;\n}): MarkdownGreenfieldVisibleProjection {\n  return createMarkdownGreenfieldVisibleProjection({\n    frames,\n    range: getMarkdownGreenfieldVisibleRange({\n      frames,\n      overscanPx,\n      scrollTop,\n      viewportHeight,\n    }),\n  });\n}\n\nexport function getMarkdownGreenfieldProjectedVisibleFrames({\n  frames,\n  projection,\n}: {\n  frames: readonly MarkdownGreenfieldChunkFrame[];\n  projection: MarkdownGreenfieldVisibleProjection;\n}) {\n  const rangedFrames = frames.slice(\n    projection.range.start,\n    projection.range.end,\n  );\n  if (frameIdsEqual(rangedFrames, projection.frameIds)) return rangedFrames;\n\n  const framesById = new Map(frames.map((frame) => [frame.id, frame]));\n  return projection.frameIds.flatMap((frameId) => {\n    const frame = framesById.get(frameId);\n    return frame ? [frame] : [];\n  });\n}\n\nexport function isMarkdownGreenfieldVisibleProjectionSameWindow({\n  frames,\n  projection,\n  range,\n}: {\n  frames: readonly MarkdownGreenfieldChunkFrame[];\n  projection: MarkdownGreenfieldVisibleProjection;\n  range: MarkdownGreenfieldVisibleRange;\n}) {\n  const nextLength = range.end - range.start;\n  if (projection.frameIds.length !== nextLength) return false;\n  for (let offset = 0; offset < nextLength; offset += 1) {\n    if (frames[range.start + offset]?.id !== projection.frameIds[offset]) {\n      return false;\n    }\n  }\n  return true;\n}\n\nexport function getMarkdownGreenfieldVisibleFrames({\n  frames,\n  overscanPx,\n  scrollTop,\n  viewportHeight,\n}: {\n  frames: readonly MarkdownGreenfieldChunkFrame[];\n  overscanPx: number;\n  scrollTop: number;\n  viewportHeight: number;\n}) {\n  const range = getMarkdownGreenfieldVisibleRange({\n    frames,\n    overscanPx,\n    scrollTop,\n    viewportHeight,\n  });\n  return frames.slice(range.start, range.end);\n}\n\nexport function getMarkdownGreenfieldScrollAnchor({\n  frames,\n  scrollTop,\n}: {\n  frames: readonly MarkdownGreenfieldChunkFrame[];\n  scrollTop: number;\n}): MarkdownGreenfieldScrollAnchor | null {\n  if (!frames.length) return null;\n  const frame =\n    frames[\n      Math.min(firstFrameWithBottomAfter(frames, scrollTop), frames.length - 1)\n    ];\n  if (!frame) return null;\n\n  return {\n    chunkId: frame.id,\n    chunkHeightPx: frame.height,\n    offsetWithinChunkPx: scrollTop - frame.top,\n  };\n}\n\nexport function resolveMarkdownGreenfieldScrollAnchor({\n  anchor,\n  frames,\n}: {\n  anchor: MarkdownGreenfieldScrollAnchor;\n  frames: readonly MarkdownGreenfieldChunkFrame[];\n}) {\n  const frame = frames.find((item) => item.id === anchor.chunkId);\n  if (!frame) return null;\n  if (anchor.offsetWithinChunkPx < 0) {\n    return Math.max(0, frame.top + anchor.offsetWithinChunkPx);\n  }\n  // Preserve the position *within* the anchored chunk proportionally. When the\n  // chunk keeps its height this reduces to the original pixel offset; when an\n  // over-estimated chunk shrinks after measurement, the viewport stays at the\n  // same relative content instead of snapping toward the chunk's top (which is\n  // what `min(offset, height - 1)` did, yanking the reader upward).\n  const fraction =\n    anchor.chunkHeightPx > 0\n      ? Math.min(\n          1,\n          Math.max(0, anchor.offsetWithinChunkPx / anchor.chunkHeightPx),\n        )\n      : 0;\n  return frame.top + fraction * frame.height;\n}\n\nexport function getMarkdownGreenfieldScrollTopForLineRange({\n  chunks,\n  frames,\n  preferredChunkId,\n  range,\n  viewportHeight,\n}: {\n  chunks: readonly Pick<\n    MarkdownGreenfieldChunk,\n    \"id\" | \"index\" | \"sourceEndLine\" | \"sourceStartLine\"\n  >[];\n  frames: readonly MarkdownGreenfieldChunkFrame[];\n  preferredChunkId?: string | null;\n  range: { end: number; start: number } | null;\n  viewportHeight: number;\n}) {\n  if (!range) return null;\n  const preferredChunk =\n    preferredChunkId == null\n      ? null\n      : chunks.find((item) => item.id === preferredChunkId);\n  const chunk =\n    preferredChunk ??\n    chunks.find(\n      (item) =>\n        item.sourceStartLine <= range.start &&\n        item.sourceEndLine >= range.start,\n    ) ??\n    chunks[0];\n  const frame = chunk\n    ? frames.find((candidate) => candidate.index === chunk.index)\n    : null;\n  if (!frame) return null;\n\n  const lineCount = Math.max(\n    1,\n    frame.sourceEndLine - frame.sourceStartLine + 1,\n  );\n  const lineOffset =\n    (Math.max(0, range.start - frame.sourceStartLine) / lineCount) *\n      frame.height || 0;\n  return Math.max(0, frame.top + lineOffset - viewportHeight * 0.25);\n}\n\nexport function getMarkdownGreenfieldSourceLineForScrollTop({\n  chunks,\n  frames,\n  scrollTop,\n}: {\n  chunks: readonly Pick<\n    MarkdownGreenfieldChunk,\n    \"index\" | \"sourceEndLine\" | \"sourceStartLine\"\n  >[];\n  frames: readonly MarkdownGreenfieldChunkFrame[];\n  scrollTop: number;\n}) {\n  if (!frames.length) return 1;\n  const frame =\n    frames[\n      Math.min(firstFrameWithBottomAfter(frames, scrollTop), frames.length - 1)\n    ];\n  if (!frame) return 1;\n  const chunk = chunks.find((item) => item.index === frame.index);\n  if (!chunk) return frame.sourceStartLine;\n  const lineCount = Math.max(\n    1,\n    chunk.sourceEndLine - chunk.sourceStartLine + 1,\n  );\n  const ratio = Math.max(\n    0,\n    Math.min(1, (scrollTop - frame.top) / frame.height),\n  );\n  return (\n    chunk.sourceStartLine +\n    Math.min(lineCount - 1, Math.floor(ratio * lineCount))\n  );\n}\n\nfunction firstFrameWithBottomAfter(\n  frames: readonly MarkdownGreenfieldChunkFrame[],\n  y: number,\n) {\n  let low = 0;\n  let high = frames.length;\n  while (low < high) {\n    const mid = Math.floor((low + high) / 2);\n    if (frames[mid]!.bottom > y) high = mid;\n    else low = mid + 1;\n  }\n  return low;\n}\n\nfunction firstFrameWithTopAtOrAfter(\n  frames: readonly MarkdownGreenfieldChunkFrame[],\n  y: number,\n) {\n  let low = 0;\n  let high = frames.length;\n  while (low < high) {\n    const mid = Math.floor((low + high) / 2);\n    if (frames[mid]!.top >= y) high = mid;\n    else low = mid + 1;\n  }\n  return low;\n}\n\nfunction frameIdsEqual(\n  frames: readonly MarkdownGreenfieldChunkFrame[],\n  frameIds: readonly string[],\n) {\n  if (frames.length !== frameIds.length) return false;\n  for (let index = 0; index < frames.length; index += 1) {\n    if (frames[index]?.id !== frameIds[index]) return false;\n  }\n  return true;\n}\n",
      "type": "registry:ui",
      "target": "@ui/markdown-greenfield-virtualizer.ts"
    },
    {
      "path": "registry/new-york-v4/ui/markdown-hast-types.ts",
      "content": "\"use client\";\n\nexport type MarkdownPoint = {\n  column?: number;\n  line?: number;\n  offset?: number;\n};\n\nexport type MarkdownPosition = {\n  end?: MarkdownPoint;\n  start?: MarkdownPoint;\n};\n\nexport type MarkdownHastText = {\n  position?: MarkdownPosition;\n  type: \"text\";\n  value: string;\n};\n\nexport type MarkdownHastElement = {\n  children: MarkdownHastNode[];\n  position?: MarkdownPosition;\n  properties?: Record<string, unknown>;\n  tagName: string;\n  type: \"element\";\n};\n\nexport type MarkdownHastRoot = {\n  children: MarkdownHastNode[];\n  type: \"root\";\n};\n\nexport type MarkdownHastNode =\n  | MarkdownHastElement\n  | MarkdownHastText\n  | {\n      children?: MarkdownHastNode[];\n      position?: MarkdownPosition;\n      type: string;\n      value?: unknown;\n    };\n",
      "type": "registry:ui",
      "target": "@ui/markdown-hast-types.ts"
    },
    {
      "path": "registry/new-york-v4/ui/markdown-source-map.ts",
      "content": "\"use client\";\n\nimport type { MarkdownPosition } from \"./markdown-hast-types\";\n\nexport interface MarkdownSourceRange {\n  endLine: number;\n  endOffset: number;\n  startLine: number;\n  startOffset: number;\n}\n\nexport interface MarkdownSourceMap {\n  lineCount: number;\n  lineStarts: readonly number[];\n  text: string;\n}\n\nexport function createMarkdownSourceMap(text: string): MarkdownSourceMap {\n  const lineStarts = [0];\n  for (const match of text.matchAll(/\\r\\n|[\\n\\r\\u2028\\u2029]/g)) {\n    lineStarts.push(match.index + match[0].length);\n  }\n\n  return {\n    lineCount: Math.max(1, lineStarts.length),\n    lineStarts,\n    text,\n  };\n}\n\nexport function markdownSourceRangeFromPosition({\n  position,\n  sourceMap,\n}: {\n  position: MarkdownPosition | null | undefined;\n  sourceMap: MarkdownSourceMap;\n}): MarkdownSourceRange | null {\n  if (!position?.start || !position.end) return null;\n\n  const startLine = clampLine(position.start.line, sourceMap);\n  const endLine = clampLine(position.end.line, sourceMap);\n  const startOffset =\n    typeof position.start.offset === \"number\"\n      ? clampOffset(position.start.offset, sourceMap.text.length)\n      : offsetFromLineColumn({\n          column: position.start.column,\n          line: startLine,\n          sourceMap,\n        });\n  const endOffset =\n    typeof position.end.offset === \"number\"\n      ? clampOffset(position.end.offset, sourceMap.text.length)\n      : offsetFromLineColumn({\n          column: position.end.column,\n          line: endLine,\n          sourceMap,\n        });\n\n  return {\n    endLine: Math.max(startLine, endLine),\n    endOffset: Math.max(startOffset, endOffset),\n    startLine,\n    startOffset,\n  };\n}\n\nexport function markdownSourceTextForRange({\n  range,\n  sourceMap,\n}: {\n  range: MarkdownSourceRange | null;\n  sourceMap: MarkdownSourceMap;\n}) {\n  if (!range) return \"\";\n  return sourceMap.text.slice(range.startOffset, range.endOffset);\n}\n\nexport function markdownRangesIntersect({\n  a,\n  b,\n}: {\n  a: MarkdownSourceRange | null;\n  b: MarkdownSourceRange | null;\n}) {\n  if (!a || !b) return false;\n  return a.startOffset < b.endOffset && a.endOffset > b.startOffset;\n}\n\nexport function markdownLineRangeIntersects({\n  endLine,\n  range,\n  startLine,\n}: {\n  endLine: number;\n  range: { end: number; start: number } | null;\n  startLine: number;\n}) {\n  if (!range) return false;\n  return startLine <= range.end && endLine >= range.start;\n}\n\nfunction offsetFromLineColumn({\n  column,\n  line,\n  sourceMap,\n}: {\n  column: number | undefined;\n  line: number;\n  sourceMap: MarkdownSourceMap;\n}) {\n  const lineStart = sourceMap.lineStarts[line - 1] ?? 0;\n  return clampOffset(\n    lineStart + Math.max(0, (column ?? 1) - 1),\n    sourceMap.text.length,\n  );\n}\n\nfunction clampLine(line: number | undefined, sourceMap: MarkdownSourceMap) {\n  if (!Number.isFinite(line)) return 1;\n  return Math.max(1, Math.min(sourceMap.lineCount, Math.trunc(line ?? 1)));\n}\n\nfunction clampOffset(offset: number, textLength: number) {\n  return Math.max(0, Math.min(textLength, offset));\n}\n",
      "type": "registry:ui",
      "target": "@ui/markdown-source-map.ts"
    },
    {
      "path": "registry/new-york-v4/ui/markdown-unified-pipeline.ts",
      "content": "\"use client\";\n\nimport rehypeKatex from \"rehype-katex\";\nimport rehypeRaw from \"rehype-raw\";\nimport rehypeSanitize, { defaultSchema } from \"rehype-sanitize\";\nimport rehypeSlug from \"rehype-slug\";\nimport remarkBreaks from \"remark-breaks\";\nimport remarkDirective from \"remark-directive\";\nimport remarkGemoji from \"remark-gemoji\";\nimport remarkGfm from \"remark-gfm\";\nimport remarkMath from \"remark-math\";\nimport remarkParse from \"remark-parse\";\nimport remarkRehype from \"remark-rehype\";\nimport remarkSmartypants from \"remark-smartypants\";\nimport { unified } from \"unified\";\nimport { VFile } from \"vfile\";\n\nimport type {\n  MarkdownHastElement,\n  MarkdownHastNode,\n  MarkdownHastRoot,\n} from \"./markdown-hast-types\";\nimport { createMarkdownSourceMap } from \"./markdown-source-map\";\n\nexport type MarkdownUnifiedDocument = {\n  hast: MarkdownHastRoot;\n  mdast: MarkdownMdastRoot;\n  messages: MarkdownUnifiedMessage[];\n  sourceMap: ReturnType<typeof createMarkdownSourceMap>;\n};\n\nexport type MarkdownUnifiedMessage = {\n  column?: number;\n  fatal?: boolean | null;\n  line?: number;\n  reason: string;\n  ruleId?: string | null;\n  source?: string | null;\n};\n\nexport type MarkdownUnifiedOptions = {\n  gfm?: MarkdownGfmOptions;\n};\n\nexport const MARKDOWN_REMARK_PLUGINS = [\n  \"remark-parse\",\n  \"remark-directive\",\n  \"remark-gfm\",\n  \"remark-breaks\",\n  \"remark-math\",\n  \"remark-gemoji\",\n  \"remark-markdown-prose-transforms\",\n  \"remark-smartypants\",\n  \"remark-markdown-github-alerts\",\n  \"remark-markdown-definition-lists\",\n  \"remark-markdown-components\",\n  \"remark-markdown-code-metadata\",\n  \"remark-markdown-trusted-images\",\n] as const;\n\nexport const MARKDOWN_REHYPE_PLUGINS = [\n  \"remark-rehype\",\n  \"rehype-raw\",\n  \"rehype-slug\",\n  \"rehype-sanitize\",\n  \"rehype-markdown-trusted-metadata\",\n  \"rehype-markdown-safe-inputs\",\n  \"rehype-katex\",\n] as const;\n\ntype MarkdownGfmOptions = {\n  singleTilde?: boolean;\n  stringLength?: (value: string) => number;\n  tableCellPadding?: boolean;\n  tablePipeAlign?: boolean;\n};\n\ntype MarkdownMdastNode = {\n  children?: MarkdownMdastNode[];\n  data?: {\n    hProperties?: Record<string, unknown>;\n    [key: string]: unknown;\n  };\n  lang?: string;\n  meta?: string;\n  position?: {\n    end?: {\n      column?: number;\n      line?: number;\n      offset?: number;\n    };\n    start?: {\n      column?: number;\n      line?: number;\n      offset?: number;\n    };\n  };\n  type: string;\n  value?: string;\n};\n\ntype MarkdownMdastRoot = MarkdownMdastNode & {\n  children: MarkdownMdastNode[];\n  type: \"root\";\n};\n\ntype MarkdownMdastParagraph = MarkdownMdastNode & {\n  children: MarkdownMdastNode[];\n  type: \"paragraph\";\n};\n\ntype MarkdownComponent = {\n  children?: MarkdownMdastNode[];\n  name: string;\n  props: Record<string, string>;\n};\n\nconst GITHUB_ALERT_LABELS = {\n  caution: \"Caution\",\n  important: \"Important\",\n  note: \"Note\",\n  tip: \"Tip\",\n  warning: \"Warning\",\n} as const;\n\nconst MARKDOWN_KATEX_OPTIONS = {\n  maxExpand: 1000,\n  maxSize: 10,\n  strict: \"ignore\",\n  trust: false,\n} as const;\nconst MARKDOWN_KATEX_RENDER_CACHE_LIMIT = 512;\nconst MARKDOWN_KATEX_RENDER_CACHE_RENDERER = \"rehype-katex@7.0.1\";\nconst MARKDOWN_KATEX_RENDER_CACHE_CONFIG = markdownKatexConfigKey(\n  MARKDOWN_KATEX_OPTIONS,\n);\n\ntype MarkdownHastParent = MarkdownHastElement | MarkdownHastRoot;\n\ntype MarkdownKatexCacheMode = \"display\" | \"inline\";\n\ntype MarkdownKatexCacheEntry = {\n  message?: MarkdownKatexCacheMessage;\n  nodes: MarkdownHastNode[];\n};\n\ntype MarkdownKatexCacheMessage = {\n  reason: string;\n  ruleId?: string | null;\n  source?: string | null;\n};\n\ntype MarkdownKatexMatch = {\n  displayMode: boolean;\n  messagePosition?: MarkdownHastElement[\"position\"];\n  mode: MarkdownKatexCacheMode;\n  scope: MarkdownHastElement;\n  source: string;\n};\n\ntype MarkdownKatexRenderMiss = {\n  endMarkerId: string;\n  key: string;\n  messagePosition?: MarkdownHastElement[\"position\"];\n  startMarkerId: string;\n};\n\ntype MarkdownKatexRenderPlaceholder = {\n  key: string;\n  messagePosition?: MarkdownHastElement[\"position\"];\n  placeholderId: string;\n};\n\ntype MarkdownKatexCachedMessageReplay = {\n  message: MarkdownKatexCacheMessage;\n  messagePosition?: MarkdownHastElement[\"position\"];\n};\n\ntype MarkdownKatexRenderState = {\n  cachedMessages: MarkdownKatexCachedMessageReplay[];\n  firstMissByKey: Map<string, MarkdownKatexRenderMiss>;\n  firstMisses: MarkdownKatexRenderMiss[];\n  nextMarkerId: number;\n  placeholders: MarkdownKatexRenderPlaceholder[];\n  renderedByKey: Map<string, MarkdownKatexCacheEntry>;\n};\n\ntype MarkdownMdastProcessor = ReturnType<typeof createMarkdownMdastProcessor>;\ntype MarkdownHastProcessor = ReturnType<typeof createMarkdownHastProcessor>;\n\nlet defaultMarkdownMdastProcessor: MarkdownMdastProcessor | null = null;\nlet defaultMarkdownHastProcessor: MarkdownHastProcessor | null = null;\nlet markdownUnifiedSanitizeSchema: ReturnType<\n  typeof createUncachedMarkdownUnifiedSanitizeSchema\n> | null = null;\nconst markdownKatexRenderCache = new Map<string, MarkdownKatexCacheEntry>();\nconst markdownKatexRenderCacheStats = {\n  hits: 0,\n  misses: 0,\n  sameDocumentHits: 0,\n  writes: 0,\n};\n\nexport function createMarkdownUnifiedDocument(\n  markdown: string,\n  options: MarkdownUnifiedOptions = {},\n): MarkdownUnifiedDocument {\n  const file = new VFile({ value: markdown });\n  const mdastProcessor = getMarkdownMdastProcessor(options);\n  const parsedMdast = mdastProcessor.parse(file) as MarkdownMdastRoot;\n  const mdast = mdastProcessor.runSync(\n    parsedMdast as never,\n    file,\n  ) as MarkdownMdastRoot;\n  const hastProcessor = getMarkdownHastProcessor();\n  const hast = hastProcessor.runSync(mdast as never, file) as MarkdownHastRoot;\n  const sourceMap = createMarkdownSourceMap(markdown);\n  injectMarkdownFrontmatter(hast, sourceMap.text);\n\n  return {\n    hast,\n    mdast,\n    messages: file.messages.map(markdownUnifiedMessageFromVFileMessage),\n    sourceMap,\n  };\n}\n\nfunction getMarkdownMdastProcessor(\n  options: MarkdownUnifiedOptions,\n): MarkdownMdastProcessor {\n  if (options.gfm) return createMarkdownMdastProcessor(options);\n  if (!defaultMarkdownMdastProcessor) {\n    defaultMarkdownMdastProcessor = createMarkdownMdastProcessor({});\n  }\n  return defaultMarkdownMdastProcessor;\n}\n\nfunction createMarkdownMdastProcessor(options: MarkdownUnifiedOptions) {\n  return unified()\n    .use(remarkParse)\n    .use(remarkDirective)\n    .use(remarkGfm, options.gfm)\n    .use(remarkBreaks)\n    .use(remarkMath)\n    .use(remarkGemoji)\n    .use(remarkMarkdownProseTransforms)\n    .use(remarkSmartypants)\n    .use(remarkMarkdownGithubAlerts)\n    .use(remarkMarkdownDefinitionLists)\n    .use(remarkMarkdownComponents)\n    .use(remarkMarkdownCodeMetadata)\n    .use(remarkMarkdownTrustedImages);\n}\n\nfunction getMarkdownHastProcessor(): MarkdownHastProcessor {\n  if (!defaultMarkdownHastProcessor) {\n    defaultMarkdownHastProcessor = createMarkdownHastProcessor();\n  }\n  return defaultMarkdownHastProcessor;\n}\n\nfunction createMarkdownHastProcessor() {\n  return unified()\n    .use(remarkRehype, { allowDangerousHtml: true })\n    .use(rehypeRaw)\n    .use(rehypeSlug)\n    .use(rehypeSanitize, getMarkdownUnifiedSanitizeSchema())\n    .use(rehypeMarkdownTrustedMetadata)\n    .use(rehypeMarkdownSafeInputs)\n    .use(rehypeMarkdownCachedKatex, MARKDOWN_KATEX_OPTIONS);\n}\n\nfunction getMarkdownUnifiedSanitizeSchema() {\n  if (!markdownUnifiedSanitizeSchema) {\n    markdownUnifiedSanitizeSchema =\n      createUncachedMarkdownUnifiedSanitizeSchema();\n  }\n  return markdownUnifiedSanitizeSchema;\n}\n\nfunction createUncachedMarkdownUnifiedSanitizeSchema() {\n  return {\n    ...defaultSchema,\n    clobberPrefix: \"user-content-\",\n    attributes: {\n      ...defaultSchema.attributes,\n      \"*\": [\n        ...(defaultSchema.attributes?.[\"*\"] ?? []),\n        \"ariaDescribedBy\",\n        \"ariaHidden\",\n        \"ariaLabel\",\n        \"ariaLabelledBy\",\n        \"className\",\n        \"dataFootnoteBackref\",\n        \"dataFootnoteRef\",\n        \"dataFootnotes\",\n        \"dataPretextComponentFallback\",\n        \"dataPretextComponentFallbackName\",\n        \"dataPretextComponentFallbackReason\",\n        \"dataPretextComponentFallbackSource\",\n        \"dataPretextComponentName\",\n        \"dataPretextComponentProps\",\n        \"dataPretextMarkdownImage\",\n        \"dataPretextAlertKind\",\n        \"dataPretextAlertTitle\",\n        \"dataPretextCalloutKind\",\n        \"dataPretextCalloutTitle\",\n        \"id\",\n      ],\n      a: [\n        ...(defaultSchema.attributes?.a ?? []),\n        \"ariaDescribedBy\",\n        \"dataFootnoteBackref\",\n        \"dataFootnoteRef\",\n        \"href\",\n        \"id\",\n        \"title\",\n      ],\n      code: [\n        ...(defaultSchema.attributes?.code ?? []),\n        \"className\",\n        \"dataPretextCodeMeta\",\n      ],\n      img: [\n        ...(defaultSchema.attributes?.img ?? []),\n        \"dataPretextMarkdownImage\",\n      ],\n      ins: [...(defaultSchema.attributes?.ins ?? []), \"cite\"],\n      input: [\"checked\", \"disabled\", \"type\"],\n      li: [...(defaultSchema.attributes?.li ?? []), \"className\"],\n      ol: [...(defaultSchema.attributes?.ol ?? []), \"start\"],\n      section: [\n        ...(defaultSchema.attributes?.section ?? []),\n        \"className\",\n        \"dataFootnotes\",\n      ],\n      time: [...(defaultSchema.attributes?.time ?? []), \"dateTime\"],\n      sup: [...(defaultSchema.attributes?.sup ?? []), \"id\"],\n      td: [...(defaultSchema.attributes?.td ?? []), \"align\"],\n      th: [...(defaultSchema.attributes?.th ?? []), \"align\"],\n      q: [...(defaultSchema.attributes?.q ?? []), \"cite\"],\n    },\n    tagNames: [\n      ...(defaultSchema.tagNames ?? []),\n      \"abbr\",\n      \"caption\",\n      \"cite\",\n      \"dd\",\n      \"details\",\n      \"dfn\",\n      \"dl\",\n      \"dt\",\n      \"figcaption\",\n      \"figure\",\n      \"input\",\n      \"ins\",\n      \"kbd\",\n      \"mark\",\n      \"q\",\n      \"samp\",\n      \"section\",\n      \"small\",\n      \"summary\",\n      \"time\",\n      \"var\",\n    ],\n  };\n}\n\nfunction markdownUnifiedMessageFromVFileMessage(message: {\n  column?: number;\n  fatal?: boolean | null;\n  line?: number;\n  reason: string;\n  ruleId?: string | null;\n  source?: string | null;\n}): MarkdownUnifiedMessage {\n  return {\n    column: message.column,\n    fatal: message.fatal,\n    line: message.line,\n    reason: message.reason,\n    ruleId: message.ruleId,\n    source: message.source,\n  };\n}\n\nfunction injectMarkdownFrontmatter(hast: MarkdownHastRoot, markdown: string) {\n  const frontmatter = readMarkdownFrontmatter(markdown);\n  if (!frontmatter) return;\n  hast.children = [\n    createFrontmatterElement(frontmatter),\n    ...hast.children.filter((child) => {\n      const line = child.position?.start?.line ?? Number.POSITIVE_INFINITY;\n      return line > frontmatter.endLine;\n    }),\n  ];\n}\n\nfunction readMarkdownFrontmatter(markdown: string) {\n  const lines = markdown.split(/\\r\\n|[\\n\\r\\u2028\\u2029]/);\n  const first = lines[0]?.trim();\n  const kind = first === \"---\" ? \"yaml\" : first === \"+++\" ? \"toml\" : \"\";\n  if (!kind) return null;\n  const closeIndex = lines.findIndex(\n    (line, index) => index > 0 && line.trim() === first,\n  );\n  if (closeIndex <= 0) return null;\n  const body = lines.slice(1, closeIndex);\n  if (!body.some((line) => line.trim())) return null;\n  return {\n    body,\n    endLine: closeIndex + 1,\n    kind,\n    raw: lines.slice(0, closeIndex + 1).join(\"\\n\"),\n  };\n}\n\nfunction createFrontmatterElement(frontmatter: {\n  body: string[];\n  endLine: number;\n  kind: string;\n  raw: string;\n}): MarkdownHastElement {\n  const lastLine =\n    frontmatter.raw.split(/\\r\\n|[\\n\\r\\u2028\\u2029]/).at(-1) ?? \"\";\n  return {\n    type: \"element\",\n    tagName: \"div\",\n    position: {\n      start: { line: 1, column: 1, offset: 0 },\n      end: {\n        line: frontmatter.endLine,\n        column: lastLine.length + 1,\n        offset: frontmatter.raw.length,\n      },\n    },\n    properties: {\n      dataMarkdownFrontmatter: frontmatter.kind,\n    },\n    children: [\n      {\n        type: \"element\",\n        tagName: \"pre\",\n        properties: { dataMarkdownFrontmatterSource: \"\" },\n        children: [\n          {\n            type: \"element\",\n            tagName: \"code\",\n            properties: { dataMarkdownFrontmatterSource: \"\" },\n            children: [{ type: \"text\", value: frontmatter.raw }],\n          },\n        ],\n      },\n      {\n        type: \"element\",\n        tagName: \"dl\",\n        properties: { dataMarkdownFrontmatterMetadata: \"\" },\n        children: frontmatterEntries(frontmatter).flatMap(\n          ([key, value, kind]) => [\n            {\n              type: \"element\" as const,\n              tagName: \"dt\",\n              properties: {},\n              children: [{ type: \"text\" as const, value: key }],\n            },\n            {\n              type: \"element\" as const,\n              tagName: \"dd\",\n              properties: { dataFrontmatterValueKind: kind },\n              children: [{ type: \"text\" as const, value }],\n            },\n          ],\n        ),\n      },\n    ],\n  };\n}\n\nfunction frontmatterEntries(frontmatter: { body: string[]; kind: string }) {\n  return frontmatter.kind === \"toml\"\n    ? tomlFrontmatterEntries(frontmatter.body)\n    : yamlFrontmatterEntries(frontmatter.body);\n}\n\nfunction yamlFrontmatterEntries(\n  lines: string[],\n): Array<[string, string, string]> {\n  const entries: Array<[string, string, string]> = [];\n  for (let index = 0; index < lines.length; index += 1) {\n    const line = lines[index]!;\n    const match = /^([A-Za-z0-9_.-]+):\\s*(.*)$/.exec(line);\n    if (!match) continue;\n    const key = match[1]!;\n    const value = match[2]!.trim();\n    if (value.startsWith(\"{\") || value.startsWith(\"[{\")) continue;\n    if (/^\\[.*\\]$/.test(value)) {\n      entries.push([\n        key,\n        value\n          .slice(1, -1)\n          .split(\",\")\n          .map((item) => item.trim())\n          .join(\", \"),\n        \"list\",\n      ]);\n    } else if (value) {\n      entries.push([key, value.replace(/^[\"']|[\"']$/g, \"\"), \"scalar\"]);\n    } else {\n      const items: string[] = [];\n      while (/^\\s+-\\s+/.test(lines[index + 1] ?? \"\")) {\n        index += 1;\n        items.push((lines[index] ?? \"\").replace(/^\\s+-\\s+/, \"\").trim());\n      }\n      if (items.length) entries.push([key, items.join(\", \"), \"list\"]);\n    }\n  }\n  return entries;\n}\n\nfunction tomlFrontmatterEntries(\n  lines: string[],\n): Array<[string, string, string]> {\n  const entries: Array<[string, string, string]> = [];\n  let section = \"\";\n  for (const line of lines) {\n    const sectionMatch = /^\\[([^\\]]+)\\]$/.exec(line.trim());\n    if (sectionMatch) {\n      section = `${sectionMatch[1]}.`;\n      continue;\n    }\n    const match = /^([A-Za-z0-9_.-]+)\\s*=\\s*(.*)$/.exec(line.trim());\n    if (!match) continue;\n    const key = `${section}${match[1]}`;\n    const value = match[2]!.trim();\n    if (/^\\[.*\\]$/.test(value)) {\n      entries.push([\n        key,\n        value\n          .slice(1, -1)\n          .split(\",\")\n          .map((item) => item.trim().replace(/^[\"']|[\"']$/g, \"\"))\n          .join(\", \"),\n        \"list\",\n      ]);\n    } else {\n      entries.push([key, value.replace(/^[\"']|[\"']$/g, \"\"), \"scalar\"]);\n    }\n  }\n  return entries;\n}\n\nfunction remarkMarkdownGithubAlerts() {\n  return function transform(tree: unknown) {\n    for (const node of (tree as MarkdownMdastRoot).children) {\n      transformGithubAlertBlockquote(node);\n    }\n  };\n}\n\nfunction remarkMarkdownProseTransforms() {\n  return function transform(tree: unknown) {\n    visitMarkdownMdastNodes(tree as MarkdownMdastRoot, (node) => {\n      if (node.type !== \"text\" || typeof node.value !== \"string\") return;\n      node.value = node.value\n        .replace(/--/g, \"—\")\n        .replace(/\\.\\.\\./g, \"…\")\n        .replace(/->/g, \"→\")\n        .replace(/\\b1\\/2\\b/g, \"½\");\n    });\n  };\n}\n\nfunction transformGithubAlertBlockquote(node: MarkdownMdastNode) {\n  if (node.type !== \"blockquote\") return;\n\n  const blockquote = node;\n  const first = blockquote.children?.[0];\n  if (!first || first.type !== \"paragraph\") return;\n\n  const alert = readGithubAlertMarker(first as MarkdownMdastParagraph);\n  if (!alert) return;\n\n  blockquote.data = {\n    ...blockquote.data,\n    hProperties: {\n      ...(blockquote.data?.hProperties as Record<string, unknown> | undefined),\n      dataPretextAlertKind: alert.kind,\n      dataPretextAlertTitle: alert.title,\n    },\n  };\n}\n\nfunction remarkMarkdownCodeMetadata() {\n  return function transform(tree: unknown) {\n    visitMarkdownMdastNodes(tree as MarkdownMdastRoot, (node) => {\n      if (node.type !== \"code\" || !node.meta) return;\n      node.data = {\n        ...node.data,\n        hProperties: {\n          ...(node.data?.hProperties as Record<string, unknown> | undefined),\n          dataPretextCodeMeta: node.meta,\n        },\n      };\n    });\n  };\n}\n\nfunction remarkMarkdownTrustedImages() {\n  return function transform(tree: unknown) {\n    visitMarkdownMdastNodes(tree as MarkdownMdastRoot, (node) => {\n      if (node.type !== \"image\" && node.type !== \"imageReference\") return;\n      node.data = {\n        ...node.data,\n        hProperties: {\n          ...(node.data?.hProperties as Record<string, unknown> | undefined),\n          dataPretextMarkdownImage: \"\",\n        },\n      };\n    });\n  };\n}\n\nfunction visitMarkdownMdastNodes(\n  node: MarkdownMdastNode,\n  visitor: (node: MarkdownMdastNode) => void,\n) {\n  visitor(node);\n  for (const child of node.children ?? []) {\n    visitMarkdownMdastNodes(child, visitor);\n  }\n}\n\nfunction rehypeMarkdownCachedKatex(options: typeof MARKDOWN_KATEX_OPTIONS) {\n  const renderKatex = rehypeKatex(options);\n\n  return function transform(tree: MarkdownHastRoot, file: VFile) {\n    const state: MarkdownKatexRenderState = {\n      cachedMessages: [],\n      firstMissByKey: new Map(),\n      firstMisses: [],\n      nextMarkerId: 1,\n      placeholders: [],\n      renderedByKey: new Map(),\n    };\n    prepareMarkdownKatexRenderCache(tree, state);\n    const firstKatexMessageIndex = file.messages.length;\n    renderKatex(tree as never, file);\n    finishMarkdownKatexRenderCache(tree, file, state, firstKatexMessageIndex);\n  };\n}\n\nfunction prepareMarkdownKatexRenderCache(\n  parent: MarkdownHastParent,\n  state: MarkdownKatexRenderState,\n) {\n  let index = 0;\n  while (index < parent.children.length) {\n    const child = parent.children[index]!;\n    const element = readHastElement(child);\n    if (!element) {\n      index += 1;\n      continue;\n    }\n\n    const match = readMarkdownKatexMatch(element);\n    if (!match) {\n      prepareMarkdownKatexRenderCache(element, state);\n      index += 1;\n      continue;\n    }\n\n    const key = markdownKatexRenderCacheKey(match);\n    const cached = readMarkdownKatexRenderCache(key);\n    if (cached) {\n      parent.children.splice(index, 1, ...cloneMarkdownHastNodes(cached.nodes));\n      if (cached.message) {\n        state.cachedMessages.push({\n          message: cached.message,\n          messagePosition: match.messagePosition,\n        });\n      }\n      markdownKatexRenderCacheStats.hits += 1;\n      index += cached.nodes.length;\n      continue;\n    }\n\n    if (state.firstMissByKey.has(key)) {\n      const placeholder = createMarkdownKatexPlaceholder(state);\n      parent.children[index] = placeholder;\n      state.placeholders.push({\n        key,\n        messagePosition: match.messagePosition,\n        placeholderId: readStringProperty(\n          placeholder.properties?.dataPretextKatexCachePlaceholder,\n        ),\n      });\n      markdownKatexRenderCacheStats.sameDocumentHits += 1;\n      index += 1;\n      continue;\n    }\n\n    const startMarker = createMarkdownKatexMarker(state, \"start\");\n    const endMarker = createMarkdownKatexMarker(state, \"end\");\n    const miss: MarkdownKatexRenderMiss = {\n      endMarkerId: readStringProperty(\n        endMarker.properties?.dataPretextKatexCacheMarker,\n      ),\n      key,\n      messagePosition: match.messagePosition,\n      startMarkerId: readStringProperty(\n        startMarker.properties?.dataPretextKatexCacheMarker,\n      ),\n    };\n    state.firstMissByKey.set(key, miss);\n    state.firstMisses.push(miss);\n    markdownKatexRenderCacheStats.misses += 1;\n    parent.children.splice(index, 1, startMarker, match.scope, endMarker);\n    index += 3;\n  }\n}\n\nfunction finishMarkdownKatexRenderCache(\n  tree: MarkdownHastRoot,\n  file: VFile,\n  state: MarkdownKatexRenderState,\n  firstKatexMessageIndex: number,\n) {\n  const katexMessages = file.messages\n    .slice(firstKatexMessageIndex)\n    .filter((message) => message.source === \"rehype-katex\");\n  const usedMessageIndexes = new Set<number>();\n\n  for (const miss of state.firstMisses) {\n    const renderedNodes = readRenderedMarkdownKatexNodes(tree, miss);\n    if (!renderedNodes) continue;\n    const message = readMarkdownKatexMessageForMiss({\n      messages: katexMessages,\n      miss,\n      usedMessageIndexes,\n    });\n    const entry = {\n      message,\n      nodes: cloneMarkdownHastNodes(renderedNodes),\n    };\n    state.renderedByKey.set(miss.key, entry);\n    writeMarkdownKatexRenderCache(miss.key, entry);\n  }\n\n  for (const placeholder of state.placeholders) {\n    const cached =\n      state.renderedByKey.get(placeholder.key) ??\n      readMarkdownKatexRenderCache(placeholder.key);\n    if (!cached) continue;\n    replaceMarkdownKatexPlaceholder(\n      tree,\n      placeholder.placeholderId,\n      cloneMarkdownHastNodes(cached.nodes),\n    );\n    replayMarkdownKatexCacheMessage(file, placeholder, cached.message);\n  }\n\n  for (const cachedMessage of state.cachedMessages) {\n    replayMarkdownKatexCacheMessage(file, cachedMessage, cachedMessage.message);\n  }\n}\n\nfunction readMarkdownKatexMatch(\n  element: MarkdownHastElement,\n): MarkdownKatexMatch | null {\n  if (element.tagName === \"pre\") {\n    const code = element.children.map(readHastElement).find((child) => {\n      return (\n        child?.tagName === \"code\" && hasArrayClassName(child, \"language-math\")\n      );\n    });\n    if (code) {\n      return {\n        displayMode: true,\n        messagePosition: code.position,\n        mode: \"display\",\n        scope: element,\n        source: extractMarkdownKatexText(element),\n      };\n    }\n  }\n\n  const languageMath = hasArrayClassName(element, \"language-math\");\n  const mathDisplay = hasArrayClassName(element, \"math-display\");\n  const mathInline = hasArrayClassName(element, \"math-inline\");\n  if (!languageMath && !mathDisplay && !mathInline) return null;\n\n  return {\n    displayMode: mathDisplay,\n    messagePosition: element.position,\n    mode: mathDisplay ? \"display\" : \"inline\",\n    scope: element,\n    source: extractMarkdownKatexText(element),\n  };\n}\n\nfunction createMarkdownKatexMarker(\n  state: MarkdownKatexRenderState,\n  side: \"end\" | \"start\",\n): MarkdownHastElement {\n  const id = `${side}-${state.nextMarkerId}`;\n  state.nextMarkerId += 1;\n  return {\n    type: \"element\",\n    tagName: \"span\",\n    properties: {\n      dataPretextKatexCacheMarker: id,\n      hidden: true,\n    },\n    children: [],\n  };\n}\n\nfunction createMarkdownKatexPlaceholder(\n  state: MarkdownKatexRenderState,\n): MarkdownHastElement {\n  const id = `placeholder-${state.nextMarkerId}`;\n  state.nextMarkerId += 1;\n  return {\n    type: \"element\",\n    tagName: \"span\",\n    properties: {\n      dataPretextKatexCachePlaceholder: id,\n      hidden: true,\n    },\n    children: [],\n  };\n}\n\nfunction readRenderedMarkdownKatexNodes(\n  parent: MarkdownHastParent,\n  miss: MarkdownKatexRenderMiss,\n): MarkdownHastNode[] | null {\n  const startIndex = parent.children.findIndex((child) =>\n    isMarkdownKatexMarker(child, miss.startMarkerId),\n  );\n  if (startIndex >= 0) {\n    const endIndex = parent.children.findIndex((child, index) => {\n      return (\n        index > startIndex && isMarkdownKatexMarker(child, miss.endMarkerId)\n      );\n    });\n    if (endIndex < 0) return null;\n    const renderedNodes = parent.children.slice(startIndex + 1, endIndex);\n    parent.children.splice(\n      startIndex,\n      endIndex - startIndex + 1,\n      ...renderedNodes,\n    );\n    return renderedNodes;\n  }\n\n  for (const child of parent.children) {\n    const element = readHastElement(child);\n    if (!element) continue;\n    const renderedNodes = readRenderedMarkdownKatexNodes(element, miss);\n    if (renderedNodes) return renderedNodes;\n  }\n  return null;\n}\n\nfunction replaceMarkdownKatexPlaceholder(\n  parent: MarkdownHastParent,\n  placeholderId: string,\n  nodes: MarkdownHastNode[],\n): boolean {\n  const index = parent.children.findIndex((child) =>\n    isMarkdownKatexPlaceholder(child, placeholderId),\n  );\n  if (index >= 0) {\n    parent.children.splice(index, 1, ...nodes);\n    return true;\n  }\n\n  for (const child of parent.children) {\n    const element = readHastElement(child);\n    if (\n      element &&\n      replaceMarkdownKatexPlaceholder(element, placeholderId, nodes)\n    ) {\n      return true;\n    }\n  }\n  return false;\n}\n\nfunction readMarkdownKatexMessageForMiss({\n  messages,\n  miss,\n  usedMessageIndexes,\n}: {\n  messages: readonly VFile[\"messages\"][number][];\n  miss: MarkdownKatexRenderMiss;\n  usedMessageIndexes: Set<number>;\n}): MarkdownKatexCacheMessage | undefined {\n  const start = miss.messagePosition?.start;\n  const exactIndex = messages.findIndex((message, index) => {\n    return (\n      !usedMessageIndexes.has(index) &&\n      message.line === start?.line &&\n      (start?.column == null || message.column === start.column)\n    );\n  });\n  const hasPosition = start?.line != null || start?.column != null;\n  if (exactIndex < 0 && hasPosition) return undefined;\n  const fallbackIndex =\n    exactIndex >= 0\n      ? exactIndex\n      : messages.findIndex((_, index) => !usedMessageIndexes.has(index));\n  if (fallbackIndex < 0) return undefined;\n\n  usedMessageIndexes.add(fallbackIndex);\n  const message = messages[fallbackIndex]!;\n  return {\n    reason: message.reason,\n    ruleId: message.ruleId,\n    source: message.source,\n  };\n}\n\nfunction replayMarkdownKatexCacheMessage(\n  file: VFile,\n  target: { messagePosition?: MarkdownHastElement[\"position\"] },\n  message: MarkdownKatexCacheMessage | undefined,\n) {\n  if (!message) return;\n  file.message(message.reason, {\n    place: target.messagePosition as never,\n    ruleId: message.ruleId ?? undefined,\n    source: message.source ?? undefined,\n  });\n}\n\nfunction isMarkdownKatexMarker(node: MarkdownHastNode, id: string) {\n  return (\n    readStringProperty(\n      readHastElement(node)?.properties?.dataPretextKatexCacheMarker,\n    ) === id\n  );\n}\n\nfunction isMarkdownKatexPlaceholder(node: MarkdownHastNode, id: string) {\n  return (\n    readStringProperty(\n      readHastElement(node)?.properties?.dataPretextKatexCachePlaceholder,\n    ) === id\n  );\n}\n\nfunction readMarkdownKatexRenderCache(key: string) {\n  const entry = markdownKatexRenderCache.get(key);\n  if (!entry) return null;\n  markdownKatexRenderCache.delete(key);\n  markdownKatexRenderCache.set(key, entry);\n  return entry;\n}\n\nfunction writeMarkdownKatexRenderCache(\n  key: string,\n  entry: MarkdownKatexCacheEntry,\n) {\n  markdownKatexRenderCache.set(key, entry);\n  markdownKatexRenderCacheStats.writes += 1;\n  while (markdownKatexRenderCache.size > MARKDOWN_KATEX_RENDER_CACHE_LIMIT) {\n    const oldestKey = markdownKatexRenderCache.keys().next().value;\n    if (!oldestKey) break;\n    markdownKatexRenderCache.delete(oldestKey);\n  }\n}\n\nfunction markdownKatexRenderCacheKey(match: MarkdownKatexMatch) {\n  return JSON.stringify([\n    MARKDOWN_KATEX_RENDER_CACHE_RENDERER,\n    MARKDOWN_KATEX_RENDER_CACHE_CONFIG,\n    match.mode,\n    match.displayMode,\n    match.source,\n  ]);\n}\n\nfunction markdownKatexConfigKey(options: Record<string, unknown>) {\n  return JSON.stringify(\n    Object.fromEntries(\n      Object.entries(options).sort(([left], [right]) =>\n        left.localeCompare(right),\n      ),\n    ),\n  );\n}\n\nfunction extractMarkdownKatexText(node: MarkdownHastNode): string {\n  if (node.type === \"text\" && typeof node.value === \"string\") return node.value;\n  const element = readHastElement(node);\n  if (!element) return \"\";\n  return element.children.map(extractMarkdownKatexText).join(\"\");\n}\n\nfunction cloneMarkdownHastNodes(nodes: readonly MarkdownHastNode[]) {\n  return nodes.map(cloneMarkdownHastNode);\n}\n\nfunction cloneMarkdownHastNode(node: MarkdownHastNode): MarkdownHastNode {\n  if (node.type === \"text\") {\n    return {\n      ...node,\n      position: node.position\n        ? cloneMarkdownPosition(node.position)\n        : undefined,\n    };\n  }\n  const element = readHastElement(node);\n  if (element) {\n    return {\n      ...element,\n      children: cloneMarkdownHastNodes(element.children),\n      position: element.position\n        ? cloneMarkdownPosition(element.position)\n        : undefined,\n      properties: element.properties\n        ? cloneMarkdownProperties(element.properties)\n        : undefined,\n    };\n  }\n  const children = \"children\" in node ? node.children : undefined;\n  return {\n    ...node,\n    children: children ? cloneMarkdownHastNodes(children) : undefined,\n    position: node.position ? cloneMarkdownPosition(node.position) : undefined,\n  };\n}\n\nfunction cloneMarkdownPosition(\n  position: NonNullable<MarkdownHastNode[\"position\"]>,\n) {\n  return {\n    end: position.end ? { ...position.end } : undefined,\n    start: position.start ? { ...position.start } : undefined,\n  };\n}\n\nfunction cloneMarkdownProperties(properties: Record<string, unknown>) {\n  const next: Record<string, unknown> = {};\n  for (const [key, value] of Object.entries(properties)) {\n    next[key] = Array.isArray(value) ? [...value] : value;\n  }\n  return next;\n}\n\nfunction readHastElement(node: unknown): MarkdownHastElement | null {\n  return node &&\n    typeof node === \"object\" &&\n    (node as MarkdownHastElement).type === \"element\"\n    ? (node as MarkdownHastElement)\n    : null;\n}\n\nfunction hasArrayClassName(element: MarkdownHastElement, className: string) {\n  const value = element.properties?.className;\n  return Array.isArray(value) && value.includes(className);\n}\n\nfunction readStringProperty(value: unknown) {\n  if (typeof value === \"string\") return value;\n  if (Array.isArray(value)) return value.filter(Boolean).join(\" \");\n  return \"\";\n}\n\nexport function resetMarkdownMathRenderCacheForTests() {\n  markdownKatexRenderCache.clear();\n  markdownKatexRenderCacheStats.hits = 0;\n  markdownKatexRenderCacheStats.misses = 0;\n  markdownKatexRenderCacheStats.sameDocumentHits = 0;\n  markdownKatexRenderCacheStats.writes = 0;\n}\n\nexport function getMarkdownMathRenderCacheStatsForTests() {\n  return {\n    ...markdownKatexRenderCacheStats,\n    size: markdownKatexRenderCache.size,\n  };\n}\n\nfunction readGithubAlertMarker(paragraph: MarkdownMdastParagraph) {\n  const first = paragraph.children[0];\n  if (!first || first.type !== \"text\") return null;\n\n  const match =\n    /^\\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\\][\\t ]*(?:\\r?\\n)?/i.exec(\n      first.value ?? \"\",\n    );\n  if (!match) return null;\n\n  const kind = match[1]!.toLowerCase() as keyof typeof GITHUB_ALERT_LABELS;\n  first.value = (first.value ?? \"\").slice(match[0].length);\n  if (!first.value) {\n    paragraph.children.shift();\n  }\n\n  return {\n    kind,\n    title: GITHUB_ALERT_LABELS[kind],\n  };\n}\n\nfunction remarkMarkdownComponents() {\n  return function transform(tree: unknown, file: VFile) {\n    transformMarkdownComponentChildren(tree as MarkdownMdastRoot, file);\n  };\n}\n\nfunction remarkMarkdownDefinitionLists() {\n  return function transform(tree: unknown) {\n    const root = tree as MarkdownMdastRoot;\n    const nextChildren: MarkdownMdastNode[] = [];\n    for (const child of root.children) {\n      const definitionList = markdownDefinitionListFromParagraph(child);\n      nextChildren.push(definitionList ?? child);\n    }\n    root.children = nextChildren;\n  };\n}\n\nfunction markdownDefinitionListFromParagraph(\n  node: MarkdownMdastNode,\n): MarkdownMdastNode | null {\n  if (node.type !== \"paragraph\" || !node.children?.length) return null;\n\n  const lines = splitMdastParagraphLines(node.children);\n  if (lines.length < 2) return null;\n  const term = trimMdastLine(lines[0] ?? []);\n  const definitions = lines\n    .slice(1)\n    .map(trimDefinitionLine)\n    .filter(\n      (definition): definition is MarkdownMdastNode[] => definition != null,\n    );\n  if (!term.length || !definitions.length) return null;\n\n  return {\n    type: \"list\",\n    data: {\n      hName: \"dl\",\n      hProperties: {\n        dataPretextDefinitionList: \"\",\n      },\n    },\n    children: [\n      {\n        type: \"listItem\",\n        data: {\n          hName: \"dt\",\n          hProperties: {\n            dataPretextDefinitionTerm: \"\",\n          },\n        },\n        children: term,\n      },\n      ...definitions.map((definition) => ({\n        type: \"listItem\",\n        data: {\n          hName: \"dd\",\n          hProperties: {\n            dataPretextDefinitionDescription: \"\",\n          },\n        },\n        children: definition,\n      })),\n    ],\n  };\n}\n\nfunction splitMdastParagraphLines(children: MarkdownMdastNode[]) {\n  const lines: MarkdownMdastNode[][] = [[]];\n  for (const child of children) {\n    if (child.type === \"break\") {\n      lines.push([]);\n      continue;\n    }\n    if (child.type === \"text\" && typeof child.value === \"string\") {\n      const parts = child.value.split(/\\r?\\n/);\n      parts.forEach((part, index) => {\n        if (index > 0) lines.push([]);\n        if (part) lines[lines.length - 1]!.push({ ...child, value: part });\n      });\n      continue;\n    }\n    lines[lines.length - 1]!.push(child);\n  }\n  return lines;\n}\n\nfunction trimMdastLine(line: MarkdownMdastNode[]) {\n  return trimMdastLineEnd(trimMdastLineStart(line));\n}\n\nfunction trimDefinitionLine(line: MarkdownMdastNode[]) {\n  const trimmed = trimMdastLine(line);\n  const first = trimmed[0];\n  if (\n    !first ||\n    first.type !== \"text\" ||\n    typeof first.value !== \"string\" ||\n    !first.value.startsWith(\":\")\n  ) {\n    return null;\n  }\n  first.value = first.value.replace(/^:\\s*/, \"\");\n  return trimMdastLine(trimmed);\n}\n\nfunction trimMdastLineStart(line: MarkdownMdastNode[]) {\n  const next = line.map((node) => ({ ...node }));\n  while (\n    next[0]?.type === \"text\" &&\n    typeof next[0].value === \"string\" &&\n    !next[0].value.trim()\n  ) {\n    next.shift();\n  }\n  if (next[0]?.type === \"text\" && typeof next[0].value === \"string\") {\n    next[0].value = next[0].value.replace(/^\\s+/, \"\");\n    if (!next[0].value) next.shift();\n  }\n  return next;\n}\n\nfunction trimMdastLineEnd(line: MarkdownMdastNode[]) {\n  const next = line.map((node) => ({ ...node }));\n  while (\n    next.at(-1)?.type === \"text\" &&\n    typeof next.at(-1)?.value === \"string\" &&\n    !String(next.at(-1)?.value).trim()\n  ) {\n    next.pop();\n  }\n  const last = next.at(-1);\n  if (last?.type === \"text\" && typeof last.value === \"string\") {\n    last.value = last.value.replace(/\\s+$/, \"\");\n    if (!last.value) next.pop();\n  }\n  return next;\n}\n\nfunction rehypeMarkdownSafeInputs() {\n  return function transform(tree: MarkdownHastRoot) {\n    removeUnsafeInputChildren(tree);\n  };\n}\n\nfunction rehypeMarkdownTrustedMetadata() {\n  return function transform(tree: MarkdownHastRoot) {\n    trustGeneratedMarkdownMetadata(tree);\n  };\n}\n\nfunction trustGeneratedMarkdownMetadata(\n  node: MarkdownHastElement | MarkdownHastRoot,\n) {\n  for (const child of node.children) {\n    const element = child as MarkdownHastElement;\n    if (!element || element.type !== \"element\") continue;\n\n    if (hasMarkdownInternalMetadata(element)) {\n      if (element.position) {\n        stripMarkdownInternalMetadata(element);\n      } else if (hasMarkdownTrustedComponentMetadata(element)) {\n        element.properties ??= {};\n        element.properties.pretextComponentTrusted = true;\n      }\n    }\n\n    trustGeneratedMarkdownMetadata(element);\n  }\n}\n\nfunction hasMarkdownInternalMetadata(element: MarkdownHastElement) {\n  return Object.keys(element.properties ?? {}).some((key) =>\n    /^(?:dataPretextComponent|dataPretextCallout|dataFootnotes|dataFootnoteBackref|pretextComponentTrusted)/.test(\n      key,\n    ),\n  );\n}\n\nfunction hasMarkdownTrustedComponentMetadata(element: MarkdownHastElement) {\n  return Object.keys(element.properties ?? {}).some((key) =>\n    /^(?:dataPretextComponent|dataPretextCallout|pretextComponentTrusted)/.test(\n      key,\n    ),\n  );\n}\n\nfunction stripMarkdownInternalMetadata(element: MarkdownHastElement) {\n  for (const key of Object.keys(element.properties ?? {})) {\n    if (\n      /^(?:dataPretextComponent|data-pretext-component|dataPretextCallout|data-pretext-callout|dataFootnotes|data-footnotes|dataFootnoteBackref|data-footnote-backref|pretextComponentTrusted)/.test(\n        key,\n      )\n    ) {\n      delete element.properties?.[key];\n    }\n  }\n}\n\nfunction removeUnsafeInputChildren(\n  parent: MarkdownHastElement | MarkdownHastRoot,\n) {\n  if (!Array.isArray(parent.children)) return;\n  parent.children = parent.children.filter((child) => {\n    const element = child as MarkdownHastElement;\n    if (!element || element.type !== \"element\") return true;\n    if (element.tagName !== \"input\") return true;\n    return (\n      isHastElement(parent) &&\n      parent.tagName === \"li\" &&\n      hasClassName(parent, \"task-list-item\") &&\n      element.properties?.type === \"checkbox\"\n    );\n  });\n  for (const child of parent.children) {\n    const element = child as MarkdownHastElement;\n    if (element?.type === \"element\") removeUnsafeInputChildren(element);\n  }\n}\n\nfunction isHastElement(\n  node: MarkdownHastElement | MarkdownHastRoot,\n): node is MarkdownHastElement {\n  return node.type === \"element\";\n}\n\nfunction hasClassName(element: MarkdownHastElement, className: string) {\n  const value = element.properties?.className;\n  return Array.isArray(value) && value.includes(className);\n}\n\nfunction transformMarkdownComponentChildren(\n  parent: MarkdownMdastNode,\n  file: VFile,\n) {\n  const children = parent.children;\n  if (!children) return;\n  transformMarkdownHtmlContainers(parent, file);\n\n  for (let index = 0; index < children.length; index += 1) {\n    const child = children[index]!;\n\n    if (child.type === \"html\" && typeof child.value === \"string\") {\n      const component = parseMarkdownComponentHtml(child.value);\n      if (component) {\n        children[index] = createMarkdownComponentNode(component);\n        continue;\n      }\n      // CommonMark merges consecutive component tags (no blank line between)\n      // into one HTML block; split it so each tag renders as its own component\n      // (or its own fallback) instead of the whole run falling through to text.\n      const multiple = splitMarkdownComponentHtml(child.value, file);\n      if (multiple) {\n        children.splice(index, 1, ...multiple);\n        index += multiple.length - 1;\n        continue;\n      }\n      if (isMarkdownComponentHtml(child.value)) {\n        const reason = fallbackReasonForHtml(child.value);\n        emitMarkdownComponentFallbackMessage({\n          file,\n          node: child,\n          reason,\n        });\n        children[index] = createMarkdownComponentFallbackNode({\n          name: componentNameFromHtml(child.value) ?? \"Component\",\n          reason,\n          source: child.value.trim(),\n        });\n        continue;\n      }\n    }\n\n    const paragraphComponentSource = componentSourceFromParagraph(child, file);\n    if (paragraphComponentSource) {\n      const reason = fallbackReasonForHtml(paragraphComponentSource);\n      emitMarkdownComponentFallbackMessage({\n        file,\n        node: child,\n        reason,\n      });\n      children[index] = createMarkdownComponentFallbackNode({\n        name: componentNameFromHtml(paragraphComponentSource) ?? \"Component\",\n        reason,\n        source: paragraphComponentSource,\n      });\n      continue;\n    }\n\n    if (\n      child.type === \"containerDirective\" &&\n      isMarkdownContainerComponentName(readDirectiveName(child))\n    ) {\n      transformMarkdownComponentChildren(child, file);\n      const component = parseMarkdownDirectiveComponent(child);\n      if (component) {\n        children[index] = createMarkdownComponentNode(component);\n        continue;\n      }\n      if (isMarkdownContainerComponentName(readDirectiveName(child))) {\n        const reason = \"Unsupported component directive props\";\n        emitMarkdownComponentFallbackMessage({\n          file,\n          node: child,\n          reason,\n        });\n        children[index] = createMarkdownComponentFallbackNode({\n          name:\n            componentNameForDirective(readDirectiveName(child)) ?? \"Component\",\n          reason,\n          source: directiveSourceForUnsafeComponent(child),\n          children: child.children ?? [],\n        });\n        continue;\n      }\n    }\n\n    if (\n      child.type === \"containerDirective\" &&\n      isCalloutKind(readDirectiveName(child))\n    ) {\n      children[index] = createMarkdownCalloutNode(child);\n      continue;\n    }\n\n    if (child.type === \"leafDirective\" || child.type === \"textDirective\") {\n      const component = parseMarkdownDirectiveComponent(child);\n      if (component) {\n        children[index] = createMarkdownComponentNode(component);\n        continue;\n      }\n      if (componentNameForDirective(readDirectiveName(child))) {\n        const reason = \"Unsupported component directive props\";\n        emitMarkdownComponentFallbackMessage({\n          file,\n          node: child,\n          reason,\n        });\n        children[index] = createMarkdownComponentFallbackNode({\n          name:\n            componentNameForDirective(readDirectiveName(child)) ?? \"Component\",\n          reason,\n          source: directiveSourceForUnsafeComponent(child),\n          children:\n            child.type === \"textDirective\" ? (child.children ?? []) : [],\n        });\n        continue;\n      }\n    }\n\n    transformMarkdownComponentChildren(child, file);\n  }\n}\n\nfunction componentSourceFromParagraph(node: MarkdownMdastNode, file: VFile) {\n  if (node.type !== \"paragraph\") return null;\n  const nonWhitespaceChildren = (node.children ?? []).filter(\n    (child) => !(child.type === \"text\" && !String(child.value ?? \"\").trim()),\n  );\n  if (nonWhitespaceChildren.length !== 1) return null;\n  const onlyChild = nonWhitespaceChildren[0]!;\n  if (onlyChild.type !== \"text\" || typeof onlyChild.value !== \"string\") {\n    return null;\n  }\n\n  const source = sourceTextForMdastNode(node, file).trim() || onlyChild.value;\n  return isMarkdownComponentHtml(source) ? source : null;\n}\n\nfunction transformMarkdownHtmlContainers(\n  parent: MarkdownMdastNode,\n  file: VFile,\n) {\n  const children = parent.children;\n  if (!children) return;\n  for (let index = 0; index < children.length; index += 1) {\n    const child = children[index]!;\n    if (child.type !== \"html\" || typeof child.value !== \"string\") continue;\n    const start = /^<([A-Z][A-Za-z0-9]*)\\b([^>]*)>$/.exec(child.value.trim());\n    if (!start || ![\"Accordion\", \"Callout\"].includes(start[1]!)) continue;\n    const name = start[1]!;\n    const closeIndex = children.findIndex(\n      (candidate, candidateIndex) =>\n        candidateIndex > index &&\n        candidate.type === \"html\" &&\n        typeof candidate.value === \"string\" &&\n        candidate.value.trim() === `</${name}>`,\n    );\n    if (closeIndex < 0) continue;\n    const propsText = start[2]!;\n    const source = child.value.trim();\n    const inner = children.slice(index + 1, closeIndex);\n    const component = hasEventHandlerAttribute(propsText)\n      ? null\n      : parseMarkdownComponentProps(\n          name,\n          parseComponentAttributes(propsText),\n          inner,\n        );\n    if (!component) {\n      emitMarkdownComponentFallbackMessage({\n        file,\n        node: child,\n        reason: fallbackReasonForHtml(source),\n      });\n    }\n    children.splice(\n      index,\n      closeIndex - index + 1,\n      component\n        ? createMarkdownComponentNode(component)\n        : createMarkdownComponentFallbackNode({\n            name,\n            reason: fallbackReasonForHtml(source),\n            source,\n            children: inner,\n          }),\n    );\n  }\n}\n\nfunction emitMarkdownComponentFallbackMessage({\n  file,\n  node,\n  reason,\n}: {\n  file: VFile;\n  node: MarkdownMdastNode;\n  reason: string;\n}) {\n  file.message(\n    new Error(reason),\n    markdownMessagePoint(node),\n    \"markdown:component-fallback\",\n  );\n}\n\nfunction markdownMessagePoint(node: MarkdownMdastNode) {\n  const point = node.position?.start;\n  return typeof point?.line === \"number\" && typeof point.column === \"number\"\n    ? { column: point.column, line: point.line }\n    : null;\n}\n\nfunction createMarkdownComponentNode(\n  component: MarkdownComponent,\n): MarkdownMdastNode {\n  return {\n    type: \"pretextComponent\",\n    data: {\n      hName: \"div\",\n      hProperties: {\n        dataPretextComponentName: component.name,\n        dataPretextComponentProps: JSON.stringify(component.props),\n      },\n    },\n    children: component.children ?? [],\n  };\n}\n\nfunction createMarkdownComponentFallbackNode({\n  children = [],\n  name,\n  reason,\n  source,\n}: {\n  children?: MarkdownMdastNode[];\n  name: string;\n  reason: string;\n  source: string;\n}): MarkdownMdastNode {\n  return {\n    type: \"pretextComponentFallback\",\n    data: {\n      hName: \"div\",\n      hProperties: {\n        dataPretextComponentFallback: \"\",\n        dataPretextComponentFallbackName: name,\n        dataPretextComponentFallbackReason: reason,\n        dataPretextComponentFallbackSource: source,\n      },\n    },\n    children: [{ type: \"text\", value: source }, ...children],\n  };\n}\n\nfunction createMarkdownCalloutNode(node: MarkdownMdastNode): MarkdownMdastNode {\n  const kind = calloutKind(readDirectiveName(node));\n  const attrs = readDirectiveAttributes(node) ?? {};\n  const title =\n    typeof attrs.title === \"string\" && attrs.title\n      ? attrs.title\n      : calloutTitle(kind);\n  return {\n    type: \"pretextCallout\",\n    data: {\n      hName: \"div\",\n      hProperties: {\n        dataPretextCalloutKind: kind,\n        dataPretextCalloutTitle: title,\n      },\n    },\n    children: node.children ?? [],\n  };\n}\n\n// Splits an HTML block holding several self-closing component tags (only\n// whitespace between them) into one node per tag: a component node when the tag\n// parses, otherwise a fallback node — so one invalid tag never blanks the whole\n// run. Returns null only when the block isn't entirely component tags (then\n// normal HTML handling applies).\nfunction splitMarkdownComponentHtml(value: string, file: VFile) {\n  const trimmed = value.trim();\n  // Quote-aware so `>` inside an attribute value doesn't end a tag early.\n  const tagPattern = /<[A-Z][A-Za-z0-9]*\\b(?:[^>\"']|\"[^\"]*\"|'[^']*')*\\/>/g;\n  const matches = [...trimmed.matchAll(tagPattern)];\n  if (matches.length < 2) return null;\n\n  let cursor = 0;\n  for (const match of matches) {\n    if (trimmed.slice(cursor, match.index).trim() !== \"\") return null;\n    cursor = match.index + match[0].length;\n  }\n  if (trimmed.slice(cursor).trim() !== \"\") return null;\n\n  // Only treat the block as components when every tag names a known component.\n  if (\n    !matches.every((match) => {\n      const name = componentNameFromHtml(match[0]);\n      return name !== null && isMarkdownLeafComponentName(name);\n    })\n  ) {\n    return null;\n  }\n\n  return matches.map((match) => {\n    const tag = match[0];\n    const component = parseMarkdownComponentHtml(tag);\n    if (component) return createMarkdownComponentNode(component);\n    const reason = fallbackReasonForHtml(tag);\n    emitMarkdownComponentFallbackMessage({\n      file,\n      node: { type: \"html\", value: tag } as MarkdownMdastNode,\n      reason,\n    });\n    return createMarkdownComponentFallbackNode({\n      name: componentNameFromHtml(tag) ?? \"Component\",\n      reason,\n      source: tag,\n    });\n  });\n}\n\nfunction parseMarkdownComponentHtml(value: string) {\n  // Match exactly one self-closing tag: attribute chars are either non-quote/\n  // non-`>` or fully-quoted strings, so `>` inside an attribute (e.g. a mermaid\n  // `source=\"graph TD; A-->B\"`) is allowed, while a multi-tag HTML block fails\n  // here and is handled by splitMarkdownComponentHtml.\n  const match = /^<([A-Z][A-Za-z0-9]*)\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)\\/>$/.exec(\n    value.trim(),\n  );\n  if (!match) return null;\n  const name = match[1]!;\n  const propsText = match[2]!;\n  if (!isMarkdownLeafComponentName(name)) {\n    return null;\n  }\n  return parseMarkdownComponentProps(\n    name,\n    parseComponentAttributes(propsText),\n    [],\n  );\n}\n\nfunction parseMarkdownDirectiveComponent(node: MarkdownMdastNode) {\n  const name = componentNameForDirective(readDirectiveName(node));\n  if (!name) return null;\n  return parseMarkdownComponentProps(\n    name,\n    readDirectiveAttributes(node),\n    node.children ?? [],\n  );\n}\n\nfunction parseMarkdownComponentProps(\n  name: string,\n  props: Record<string, unknown> | null,\n  children: MarkdownMdastNode[],\n): MarkdownComponent | null {\n  if (!props) return null;\n  if (Object.keys(props).some((key) => /^on/i.test(key))) return null;\n\n  if (name === \"Diagram\") {\n    if (props.type !== \"mermaid\" || typeof props.source !== \"string\")\n      return null;\n    return {\n      name,\n      props: {\n        caption: readPropString(props.caption),\n        source: normalizeMarkdownDiagramSource(props.source),\n        title: readPropString(props.title),\n        type: \"mermaid\",\n      },\n    };\n  }\n  if (name === \"Metric\") {\n    if (!props.label || !props.value || props.tone) return null;\n    return {\n      name,\n      props: {\n        label: readPropString(props.label),\n        value: readPropString(props.value),\n      },\n    };\n  }\n  if (name === \"Badge\") {\n    const label = readPropString(props.label) || mdastText(children);\n    const tone = readPropString(props.tone);\n    if (!label || (tone && ![\"default\", \"success\", \"warning\"].includes(tone))) {\n      return null;\n    }\n    return { name, props: { label, tone } };\n  }\n  if (name === \"Image\") {\n    if (!props.src || !props.alt) return null;\n    return {\n      name,\n      props: {\n        alt: readPropString(props.alt),\n        height: readPropString(props.height),\n        src: readPropString(props.src),\n        title: readPropString(props.title),\n        width: readPropString(props.width),\n      },\n    };\n  }\n  if (name === \"Video\") {\n    if (!props.src || !props.label) return null;\n    return {\n      name,\n      props: {\n        controls: readPropString(props.controls),\n        label: readPropString(props.label),\n        loop: readPropString(props.loop),\n        muted: readPropString(props.muted),\n        src: readPropString(props.src),\n        title: readPropString(props.title),\n      },\n    };\n  }\n  if (name === \"Accordion\") {\n    if (!props.title) return null;\n    return { name, props: { title: readPropString(props.title) }, children };\n  }\n  if (name === \"Callout\") {\n    const kind = calloutKind(readPropString(props.kind) || \"note\");\n    return {\n      name,\n      props: {\n        kind,\n        title: readPropString(props.title) || calloutTitle(kind),\n      },\n      children,\n    };\n  }\n  if (name === \"Tabs\" || name === \"Tab\") {\n    return {\n      name,\n      props: {\n        label: readPropString(props.label),\n        title: readPropString(props.title),\n      },\n      children,\n    };\n  }\n  return null;\n}\n\nfunction normalizeMarkdownDiagramSource(source: string) {\n  return source\n    .split(/;\\s*/)\n    .map((line) => line.trim())\n    .filter(Boolean)\n    .join(\"\\n\");\n}\n\nfunction readDirectiveName(node: MarkdownMdastNode) {\n  const directive = node as unknown as { name?: unknown };\n  return typeof directive.name === \"string\" ? directive.name : \"\";\n}\n\nfunction readDirectiveAttributes(node: MarkdownMdastNode) {\n  const attributes = (node as { attributes?: unknown }).attributes;\n  return attributes && typeof attributes === \"object\"\n    ? (attributes as Record<string, unknown>)\n    : null;\n}\n\nfunction parseQuotedAttributes(value: string): Record<string, string> {\n  return Object.fromEntries(\n    Array.from(value.matchAll(/\\s*([A-Za-z][A-Za-z0-9_]*)=\"([^\"]*)\"/g)).map(\n      (item) => [item[1]!, item[2]!],\n    ),\n  );\n}\n\nfunction parseComponentAttributes(value: string) {\n  if (/\\{\\s*\\.\\.\\./.test(value)) return null;\n  const attributes: Record<string, string> = {};\n  const consumed: Array<[number, number]> = [];\n  for (const match of value.matchAll(\n    /\\s+([A-Za-z][A-Za-z0-9_]*)=(?:\"([^\"]*)\"|\\{(\\d+(?:\\.\\d+)?|true|false)\\})/g,\n  )) {\n    attributes[match[1]!] = match[2] ?? match[3] ?? \"\";\n    consumed.push([match.index ?? 0, (match.index ?? 0) + match[0].length]);\n  }\n  for (const match of value.matchAll(/\\s+([A-Za-z][A-Za-z0-9_]*)(?=\\s|$)/g)) {\n    const index = match.index ?? 0;\n    if (consumed.some(([start, end]) => index >= start && index < end)) {\n      continue;\n    }\n    attributes[match[1]!] = \"true\";\n    consumed.push([index, index + match[0].length]);\n  }\n  if (removeRanges(value, consumed).trim()) return null;\n  return attributes;\n}\n\nfunction removeRanges(value: string, ranges: Array<[number, number]>) {\n  let result = \"\";\n  let offset = 0;\n  for (const [start, end] of ranges.sort((a, b) => a[0] - b[0])) {\n    result += value.slice(offset, start);\n    offset = end;\n  }\n  return result + value.slice(offset);\n}\n\nfunction readPropString(value: unknown) {\n  return typeof value === \"string\" ? value : \"\";\n}\n\nfunction mdastText(nodes: readonly MarkdownMdastNode[]): string {\n  return nodes\n    .map((node) =>\n      typeof node.value === \"string\"\n        ? node.value\n        : mdastText(node.children ?? []),\n    )\n    .join(\"\");\n}\n\nfunction componentNameForDirective(name: string) {\n  const normalized = name.toLowerCase();\n  const names: Record<string, string> = {\n    accordion: \"Accordion\",\n    badge: \"Badge\",\n    callout: \"Callout\",\n    diagram: \"Diagram\",\n    image: \"Image\",\n    metric: \"Metric\",\n    tab: \"Tab\",\n    tabs: \"Tabs\",\n    video: \"Video\",\n  };\n  return names[normalized] ?? null;\n}\n\nfunction isMarkdownLeafComponentName(name: string) {\n  return [\"Badge\", \"Diagram\", \"Image\", \"Metric\", \"Video\"].includes(name);\n}\n\nfunction isMarkdownContainerComponentName(name: string) {\n  return Boolean(componentNameForDirective(name));\n}\n\nfunction isMarkdownComponentHtml(value: string) {\n  return /^<\\/?[A-Z][A-Za-z0-9.]*(?:\\b|\\.)/.test(value.trim());\n}\n\nfunction componentNameFromHtml(value: string) {\n  return /^<\\/?([A-Z][A-Za-z0-9.]*)/.exec(value.trim())?.[1] ?? null;\n}\n\nfunction hasEventHandlerAttribute(value: string) {\n  return /\\son[A-Za-z]+\\s*=/.test(value);\n}\n\nfunction fallbackReasonForHtml(value: string) {\n  const name = componentNameFromHtml(value);\n  if (name?.includes(\".\")) {\n    return \"Remote or namespaced components are not supported\";\n  }\n  if (!name || !componentNameForDirective(name)) return \"Unsupported component\";\n  if (hasEventHandlerAttribute(value))\n    return \"Event handler props are not supported\";\n  if (value.includes(\"{\")) return \"Component props must be literal values\";\n  return \"Unsupported component\";\n}\n\nfunction sourceTextForMdastNode(node: MarkdownMdastNode, file: VFile) {\n  const value = String(file.value ?? \"\");\n  const start = node.position?.start?.offset;\n  const end = node.position?.end?.offset;\n  if (\n    typeof start !== \"number\" ||\n    typeof end !== \"number\" ||\n    start < 0 ||\n    end < start\n  ) {\n    return \"\";\n  }\n  return value.slice(start, end);\n}\n\nfunction isCalloutKind(name: string) {\n  return [\"caution\", \"important\", \"note\", \"success\", \"tip\", \"warning\"].includes(\n    name.toLowerCase(),\n  );\n}\n\nfunction calloutKind(value: string) {\n  const normalized = value.toLowerCase();\n  if (normalized === \"success\") return \"tip\";\n  if (isCalloutKind(normalized)) return normalized;\n  return \"note\";\n}\n\nfunction calloutTitle(kind: string) {\n  const titles: Record<string, string> = {\n    caution: \"Caution\",\n    important: \"Important\",\n    note: \"Note\",\n    tip: \"Tip\",\n    warning: \"Warning\",\n  };\n  return titles[kind] ?? \"Note\";\n}\n\nfunction directiveSourceForUnsafeComponent(node: MarkdownMdastNode) {\n  const name = readDirectiveName(node);\n  const attrs = readDirectiveAttributes(node) ?? {};\n  const attrSource = Object.entries(attrs)\n    .map(([key, value]) => `${key}=\"${String(value)}\"`)\n    .join(\" \");\n  const marker = node.type === \"textDirective\" ? \":\" : \"::\";\n  return `${marker}${name}${attrSource ? `{${attrSource}}` : \"\"}`;\n}\n",
      "type": "registry:ui",
      "target": "@ui/markdown-unified-pipeline.ts"
    },
    {
      "path": "registry/new-york-v4/ui/markdown-url-policy.ts",
      "content": "\"use client\";\n\nconst URL_CONTROL_CHARACTER_PATTERN = /[\\u0000-\\u001F\\u007F]/;\nconst URL_SCHEME_PATTERN = /^([A-Za-z][A-Za-z0-9+.-]*):/;\nconst URL_BACKSLASH_PATTERN = /[\\\\\\uFE68\\uFF3C\\u2216]/;\nconst URL_CONFUSABLE_DELIMITER_PATTERN =\n  /[\\u02D0\\u0589\\u05C3\\u0703\\u0704\\u16EC\\u1803\\u1809\\u205A\\u2236\\uA789\\uFE13\\uFE55\\uFF1A\\u2044\\u2215\\u2571\\u27CB\\u29F8\\uFF0F]/;\nconst ALLOWED_LINK_PROTOCOLS = new Set([\"http\", \"https\", \"mailto\"]);\n\nexport function sanitizeMarkdownUrl(value: string) {\n  const trimmed = value.trim();\n  if (!trimmed) return \"\";\n  if (URL_CONTROL_CHARACTER_PATTERN.test(trimmed)) return \"\";\n  if (URL_BACKSLASH_PATTERN.test(trimmed)) return \"\";\n  if (URL_CONFUSABLE_DELIMITER_PATTERN.test(trimmed)) return \"\";\n\n  const decoded = decodeMarkdownUrl(trimmed).trim();\n  if (!decoded || URL_CONTROL_CHARACTER_PATTERN.test(decoded)) return \"\";\n  if (URL_BACKSLASH_PATTERN.test(decoded)) return \"\";\n  if (URL_CONFUSABLE_DELIMITER_PATTERN.test(decoded)) return \"\";\n\n  const decodedScheme = getMarkdownUrlScheme(decoded);\n  const rawScheme = getMarkdownUrlScheme(trimmed);\n  if (decodedScheme && decodedScheme !== rawScheme) return \"\";\n  if (decodedScheme && !ALLOWED_LINK_PROTOCOLS.has(decodedScheme)) return \"\";\n\n  if (trimmed.startsWith(\"#\")) return trimmed;\n  if (trimmed.startsWith(\"/\")) return trimmed.startsWith(\"//\") ? \"\" : trimmed;\n\n  try {\n    const url = new URL(trimmed, \"https://retab.local\");\n    if (ALLOWED_LINK_PROTOCOLS.has(url.protocol.slice(0, -1))) {\n      return rawScheme ? url.href : trimmed;\n    }\n  } catch {\n    return \"\";\n  }\n\n  return \"\";\n}\n\nexport function sanitizeMarkdownImageUrl(value: string) {\n  const safeUrl = sanitizeMarkdownUrl(value);\n  if (!safeUrl || safeUrl.startsWith(\"mailto:\") || safeUrl.startsWith(\"#\")) {\n    return \"\";\n  }\n  if (isMarkdownSvgResourceUrl(safeUrl)) return \"\";\n  return safeUrl;\n}\n\nexport function sanitizeMarkdownMediaUrl(value: string) {\n  // Image URLs already reject SVG resources, so media inherits that policy.\n  return sanitizeMarkdownImageUrl(value);\n}\n\nfunction decodeMarkdownUrl(value: string) {\n  try {\n    return decodeURIComponent(value);\n  } catch {\n    return value;\n  }\n}\n\nfunction getMarkdownUrlScheme(value: string) {\n  return URL_SCHEME_PATTERN.exec(value)?.[1]?.toLowerCase() ?? null;\n}\n\nexport function isMarkdownSvgResourceUrl(value: string) {\n  const decoded = decodeMarkdownUrl(value).trim();\n\n  try {\n    const url = new URL(decoded, \"https://retab.local\");\n    return /\\.(?:svg|svgz)$/i.test(url.pathname);\n  } catch {\n    const pathname = decoded.split(/[?#]/, 1)[0] ?? decoded;\n    return /\\.(?:svg|svgz)$/i.test(pathname);\n  }\n}\n",
      "type": "registry:ui",
      "target": "@ui/markdown-url-policy.ts"
    },
    {
      "path": "registry/new-york-v4/ui/text-viewer-virtualization.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useKeyedLayoutEffect } from \"@/hooks/use-keyed-layout-effect\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nconst DEFAULT_VIEWPORT_HEIGHT = 600;\nconst DEFAULT_VIEWPORT_WIDTH = 800;\nconst DEFAULT_OVERSCAN = 6;\nconst MAX_VIRTUAL_ITEMS = 500;\n\nexport interface TextVirtualItem {\n  index: number;\n  key: React.Key;\n  start: number;\n  size: number;\n  end: number;\n}\n\nexport interface TextVirtualOffsets {\n  starts: number[];\n  totalSize: number;\n}\n\nexport interface TextVirtualViewport {\n  scrollTop: number;\n  clientHeight: number;\n  clientWidth: number;\n}\n\nexport interface TextScrollAnchor {\n  index: number;\n  offsetWithinLine: number;\n}\n\nexport interface TextFrameVirtualItem {\n  end: number;\n  index: number;\n  key: React.Key;\n  size: number;\n  start: number;\n}\n\nexport interface TextFrameGeometry {\n  bottom: number;\n  height: number;\n  top: number;\n}\n\nexport interface TextFrameScrollAnchor {\n  index: number;\n  offsetWithinFrame: number;\n}\n\nexport interface TextInverseStickyWindow {\n  afterHeight: number;\n  beforeHeight: number;\n  renderedBottom: number;\n  renderedHeight: number;\n  renderedTop: number;\n  stickyOffset: number;\n}\n\nexport function buildTextVirtualOffsets({\n  itemSizes,\n  paddingStart = 0,\n  paddingEnd = 0,\n}: {\n  itemSizes: readonly number[];\n  paddingStart?: number;\n  paddingEnd?: number;\n}): TextVirtualOffsets {\n  const starts: number[] = [];\n  let offset = safeSize(paddingStart);\n\n  for (const itemSize of itemSizes) {\n    starts.push(offset);\n    offset += safeSize(itemSize);\n  }\n\n  return {\n    starts,\n    totalSize: offset + safeSize(paddingEnd),\n  };\n}\n\nexport function getTextVirtualItems({\n  itemSizes,\n  offsets,\n  scrollTop,\n  viewportHeight,\n  overscan = DEFAULT_OVERSCAN,\n}: {\n  itemSizes: readonly number[];\n  offsets: TextVirtualOffsets;\n  scrollTop: number;\n  viewportHeight: number;\n  overscan?: number;\n}): TextVirtualItem[] {\n  const count = itemSizes.length;\n  if (count === 0) return [];\n\n  const safeScrollTop = safeOffset(scrollTop);\n  const safeViewportHeight = Math.max(1, safeSize(viewportHeight));\n  const safeOverscan = safeCount(overscan);\n  const visibleStartIndex = findFirstItemEndingAfter({\n    itemSizes,\n    starts: offsets.starts,\n    offset: safeScrollTop,\n  });\n  const visibleEndExclusive = findFirstItemStartingAtOrAfter({\n    starts: offsets.starts,\n    offset: safeScrollTop + safeViewportHeight,\n  });\n  const start = Math.max(0, visibleStartIndex - safeOverscan);\n  const end = Math.min(\n    count,\n    Math.max(visibleStartIndex + 1, visibleEndExclusive) + safeOverscan,\n  );\n  const cappedEnd = Math.min(count, start + MAX_VIRTUAL_ITEMS);\n\n  return Array.from(\n    { length: Math.min(end, cappedEnd) - start },\n    (_, localIndex) => {\n      const index = start + localIndex;\n      const size = safeSize(itemSizes[index]);\n      const itemStart = offsets.starts[index] ?? 0;\n      return {\n        index,\n        key: index,\n        start: itemStart,\n        size,\n        end: itemStart + size,\n      };\n    },\n  );\n}\n\nexport function textScrollTopForItem({\n  itemIndex,\n  itemSizes,\n  offsets,\n  viewportHeight,\n  align = \"center\",\n}: {\n  itemIndex: number;\n  itemSizes: readonly number[];\n  offsets: TextVirtualOffsets;\n  viewportHeight: number;\n  align?: \"start\" | \"center\" | \"end\";\n}) {\n  if (!Number.isSafeInteger(itemIndex) || itemIndex < 0) return 0;\n  const itemStart = offsets.starts[itemIndex];\n  if (itemStart == null) return 0;\n\n  const itemSize = safeSize(itemSizes[itemIndex]);\n  const safeViewportHeight = safeSize(viewportHeight);\n  if (align === \"end\") {\n    return Math.max(0, itemStart - safeViewportHeight + itemSize);\n  }\n  if (align === \"center\") {\n    return Math.max(0, itemStart - safeViewportHeight / 2 + itemSize / 2);\n  }\n  return Math.max(0, itemStart);\n}\n\nexport function getTextScrollAnchor({\n  itemSizes,\n  offsets,\n  scrollTop,\n}: {\n  itemSizes: readonly number[];\n  offsets: Pick<TextVirtualOffsets, \"starts\">;\n  scrollTop: number;\n}): TextScrollAnchor | null {\n  if (itemSizes.length === 0) return null;\n\n  const safeScrollTop = safeOffset(scrollTop);\n  let low = 0;\n  let high = itemSizes.length - 1;\n  let index = 0;\n\n  while (low <= high) {\n    const mid = Math.floor((low + high) / 2);\n    const start = offsets.starts[mid] ?? 0;\n    const end = start + safeSize(itemSizes[mid]);\n    if (end > safeScrollTop) {\n      index = mid;\n      high = mid - 1;\n    } else {\n      low = mid + 1;\n    }\n  }\n\n  return {\n    index,\n    offsetWithinLine: Math.max(0, safeScrollTop - (offsets.starts[index] ?? 0)),\n  };\n}\n\nexport function useTextVariableVirtualizer({\n  itemSizes,\n  overscan = DEFAULT_OVERSCAN,\n  paddingStart = 0,\n  paddingEnd = 0,\n  scrollRef,\n}: {\n  itemSizes: readonly number[];\n  overscan?: number;\n  paddingStart?: number;\n  paddingEnd?: number;\n  scrollRef: React.RefObject<HTMLElement | null>;\n}) {\n  const viewport = useTextVirtualViewport(scrollRef);\n  const offsets = React.useMemo(\n    () => buildTextVirtualOffsets({ itemSizes, paddingStart, paddingEnd }),\n    [itemSizes, paddingEnd, paddingStart],\n  );\n  const viewportHeight = viewport.clientHeight || DEFAULT_VIEWPORT_HEIGHT;\n  const viewportWidth = viewport.clientWidth || DEFAULT_VIEWPORT_WIDTH;\n  const virtualItems = React.useMemo(\n    () =>\n      getTextVirtualItems({\n        itemSizes,\n        offsets,\n        overscan,\n        scrollTop: viewport.scrollTop,\n        viewportHeight,\n      }),\n    [itemSizes, offsets, overscan, viewport.scrollTop, viewportHeight],\n  );\n  return {\n    offsets,\n    totalSize: offsets.totalSize,\n    viewportHeight,\n    viewportWidth,\n    virtualItems,\n  };\n}\n\nexport function getTextFrameVirtualItems({\n  frames,\n  maxItems = MAX_VIRTUAL_ITEMS,\n  overscanPx = 0,\n  scrollTop,\n  viewportHeight,\n}: {\n  frames: readonly TextFrameGeometry[];\n  maxItems?: number;\n  overscanPx?: number;\n  scrollTop: number;\n  viewportHeight: number;\n}): TextFrameVirtualItem[] {\n  if (frames.length === 0) return [];\n\n  const safeScrollTop = safeOffset(scrollTop);\n  const safeViewportHeight = Math.max(1, safeSize(viewportHeight));\n  const safeOverscanPx = safeSize(overscanPx);\n  const visibleStart = findFirstFrameEndingAfter(frames, safeScrollTop);\n  const visibleEnd = Math.max(\n    visibleStart + 1,\n    findFirstFrameStartingAtOrAfter(\n      frames,\n      safeScrollTop + safeViewportHeight,\n      visibleStart,\n    ),\n  );\n  const start = findFirstFrameEndingAfter(\n    frames,\n    Math.max(0, safeScrollTop - safeOverscanPx),\n  );\n  const end = Math.max(\n    visibleEnd,\n    findFirstFrameStartingAtOrAfter(\n      frames,\n      safeScrollTop + safeViewportHeight + safeOverscanPx,\n      visibleStart,\n    ),\n  );\n  const safeMaxItems = Math.max(visibleEnd - start, safeCount(maxItems));\n  const cappedEnd = Math.min(frames.length, end, start + safeMaxItems);\n\n  return Array.from({ length: cappedEnd - start }, (_, localIndex) => {\n    const index = start + localIndex;\n    const frame = frames[index]!;\n    return {\n      end: frame.bottom,\n      index,\n      key: index,\n      size: safeSize(frame.height),\n      start: frame.top,\n    };\n  });\n}\n\nexport function getTextInverseStickyWindow({\n  renderedBottom,\n  renderedTop,\n  totalHeight,\n  viewportHeight,\n}: {\n  renderedBottom: number;\n  renderedTop: number;\n  totalHeight: number;\n  viewportHeight: number;\n}): TextInverseStickyWindow {\n  const safeTotalHeight = safeOffset(totalHeight);\n  const safeViewportHeight = Math.max(1, safeSize(viewportHeight));\n  const top = clamp(safeOffset(renderedTop), 0, safeTotalHeight);\n  const bottom = clamp(\n    Math.max(top, safeOffset(renderedBottom)),\n    top,\n    safeTotalHeight,\n  );\n  const renderedHeight = bottom - top;\n\n  return {\n    afterHeight: Math.max(0, safeTotalHeight - bottom),\n    beforeHeight: top,\n    renderedBottom: bottom,\n    renderedHeight,\n    renderedTop: top,\n    stickyOffset: -Math.max(0, renderedHeight - safeViewportHeight),\n  };\n}\n\nexport function getTextFrameScrollAnchor({\n  frames,\n  scrollTop,\n}: {\n  frames: readonly TextFrameGeometry[];\n  scrollTop: number;\n}): TextFrameScrollAnchor | null {\n  if (frames.length === 0) return null;\n\n  const safeScrollTop = safeOffset(scrollTop);\n  const index = findFirstFrameEndingAfter(frames, safeScrollTop);\n  const frame = frames[index] ?? frames[frames.length - 1];\n  if (!frame) return null;\n\n  return {\n    index,\n    offsetWithinFrame: Math.max(0, safeScrollTop - frame.top),\n  };\n}\n\nexport function useTextVirtualViewport(\n  scrollRef: React.RefObject<HTMLElement | null>,\n): TextVirtualViewport {\n  const [viewport, setViewport] = React.useState<TextVirtualViewport>({\n    scrollTop: 0,\n    clientHeight: 0,\n    clientWidth: 0,\n  });\n\n  useKeyedLayoutEffect(joinEffectKey([scrollRef]), () => {\n    const scrollElement = scrollRef.current;\n    if (!scrollElement) return;\n\n    let frame = 0;\n    const readViewport = () => {\n      frame = 0;\n      const next = {\n        scrollTop: safeOffset(scrollElement.scrollTop),\n        clientHeight: safeSize(scrollElement.clientHeight),\n        clientWidth: safeSize(scrollElement.clientWidth),\n      };\n      setViewport((current) =>\n        current.scrollTop === next.scrollTop &&\n        current.clientHeight === next.clientHeight &&\n        current.clientWidth === next.clientWidth\n          ? current\n          : next,\n      );\n    };\n    const scheduleRead = () => {\n      if (frame) return;\n      frame = requestAnimationFrame(readViewport);\n    };\n\n    readViewport();\n    scrollElement.addEventListener(\"scroll\", scheduleRead, { passive: true });\n    const observer =\n      typeof ResizeObserver !== \"undefined\"\n        ? new ResizeObserver(scheduleRead)\n        : null;\n    observer?.observe(scrollElement);\n\n    return () => {\n      if (frame) cancelAnimationFrame(frame);\n      scrollElement.removeEventListener(\"scroll\", scheduleRead);\n      observer?.disconnect();\n    };\n  });\n\n  return viewport;\n}\n\nfunction findFirstFrameEndingAfter(\n  frames: readonly TextFrameGeometry[],\n  offset: number,\n) {\n  let low = 0;\n  let high = frames.length - 1;\n  let result = frames.length - 1;\n\n  while (low <= high) {\n    const mid = Math.floor((low + high) / 2);\n    if ((frames[mid]?.bottom ?? 0) > offset) {\n      result = mid;\n      high = mid - 1;\n    } else {\n      low = mid + 1;\n    }\n  }\n\n  return result;\n}\n\nfunction findFirstFrameStartingAtOrAfter(\n  frames: readonly TextFrameGeometry[],\n  offset: number,\n  start: number,\n) {\n  let low = Math.max(0, start);\n  let high = frames.length - 1;\n  let result = frames.length;\n\n  while (low <= high) {\n    const mid = Math.floor((low + high) / 2);\n    if ((frames[mid]?.top ?? 0) >= offset) {\n      result = mid;\n      high = mid - 1;\n    } else {\n      low = mid + 1;\n    }\n  }\n\n  return result;\n}\n\nfunction findFirstItemEndingAfter({\n  itemSizes,\n  starts,\n  offset,\n}: {\n  itemSizes: readonly number[];\n  starts: readonly number[];\n  offset: number;\n}) {\n  let low = 0;\n  let high = itemSizes.length - 1;\n  let result = itemSizes.length - 1;\n\n  while (low <= high) {\n    const mid = Math.floor((low + high) / 2);\n    const end = (starts[mid] ?? 0) + safeSize(itemSizes[mid]);\n    if (end > offset) {\n      result = mid;\n      high = mid - 1;\n    } else {\n      low = mid + 1;\n    }\n  }\n\n  return result;\n}\n\nfunction findFirstItemStartingAtOrAfter({\n  starts,\n  offset,\n}: {\n  starts: readonly number[];\n  offset: number;\n}) {\n  let low = 0;\n  let high = starts.length - 1;\n  let result = starts.length;\n\n  while (low <= high) {\n    const mid = Math.floor((low + high) / 2);\n    if ((starts[mid] ?? 0) >= offset) {\n      result = mid;\n      high = mid - 1;\n    } else {\n      low = mid + 1;\n    }\n  }\n\n  return result;\n}\n\nfunction safeCount(value: number) {\n  return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;\n}\n\nfunction safeOffset(value: number) {\n  return Number.isFinite(value) && value > 0 ? value : 0;\n}\n\nfunction safeSize(value: number | undefined) {\n  return Number.isFinite(value) && value != null && value > 0 ? value : 0;\n}\n\nfunction clamp(value: number, min: number, max: number) {\n  return Math.min(max, Math.max(min, value));\n}\n",
      "type": "registry:ui",
      "target": "@ui/text-viewer-virtualization.ts"
    },
    {
      "path": "registry/new-york-v4/ui/text-viewer-scroll-interactions.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nexport const TEXT_SCROLL_INTERACTION_RESTORE_DELAY_MS = 120;\n\nexport type ScrollInteractionSnapshot = {\n  overflowTarget: HTMLElement | null;\n  overflowX: InlineStyleSnapshot | null;\n  pointerEvents: InlineStyleSnapshot;\n  target: HTMLElement;\n};\n\ntype InlineStyleSnapshot = {\n  priority: string;\n  value: string;\n};\n\nexport function useTextViewerScrollInteractions<Viewport extends HTMLElement>({\n  getInteractionTarget,\n  getOverflowTarget,\n  onScroll,\n  viewportRef,\n}: {\n  getInteractionTarget: () => HTMLElement | null | undefined;\n  getOverflowTarget?: () => HTMLElement | null | undefined;\n  onScroll: (viewport: Viewport) => void;\n  viewportRef: React.RefObject<Viewport | null>;\n}) {\n  const restoreTimerRef = React.useRef(0);\n  const snapshotRef = React.useRef<ScrollInteractionSnapshot | null>(null);\n\n  useKeyedMountEffect(\n    joinEffectKey([\n      getInteractionTarget,\n      getOverflowTarget,\n      onScroll,\n      viewportRef,\n    ]),\n    () => {\n      const viewport = viewportRef.current;\n      if (!viewport) return;\n\n      const handleScroll = () => {\n        suspendTextViewerScrollInteractions({\n          getInteractionTarget,\n          getOverflowTarget,\n          snapshotRef,\n        });\n        if (restoreTimerRef.current) {\n          window.clearTimeout(restoreTimerRef.current);\n        }\n        restoreTimerRef.current = window.setTimeout(() => {\n          restoreTimerRef.current = 0;\n          restoreTextViewerScrollInteractions(snapshotRef);\n        }, TEXT_SCROLL_INTERACTION_RESTORE_DELAY_MS);\n        onScroll(viewport);\n      };\n\n      viewport.addEventListener(\"scroll\", handleScroll, { passive: true });\n      return () => {\n        viewport.removeEventListener(\"scroll\", handleScroll);\n        if (restoreTimerRef.current) {\n          window.clearTimeout(restoreTimerRef.current);\n          restoreTimerRef.current = 0;\n        }\n        restoreTextViewerScrollInteractions(snapshotRef);\n      };\n    },\n  );\n}\n\nexport function suspendTextViewerScrollInteractions({\n  getInteractionTarget,\n  getOverflowTarget,\n  snapshotRef,\n}: {\n  getInteractionTarget: () => HTMLElement | null | undefined;\n  getOverflowTarget?: () => HTMLElement | null | undefined;\n  snapshotRef: React.MutableRefObject<ScrollInteractionSnapshot | null>;\n}) {\n  const target = getInteractionTarget();\n  if (!target) {\n    restoreTextViewerScrollInteractions(snapshotRef);\n    return;\n  }\n\n  const overflowTarget = isMobileSafari()\n    ? (getOverflowTarget?.() ?? target.parentElement ?? target)\n    : null;\n  const current = snapshotRef.current;\n  if (\n    current &&\n    current.target === target &&\n    current.overflowTarget === overflowTarget\n  ) {\n    target.style.pointerEvents = \"none\";\n    overflowTarget?.style.setProperty(\"overflow-x\", \"hidden\");\n    return;\n  }\n\n  restoreTextViewerScrollInteractions(snapshotRef);\n  snapshotRef.current = {\n    overflowTarget,\n    overflowX: overflowTarget\n      ? readInlineStyle(overflowTarget, \"overflow-x\")\n      : null,\n    pointerEvents: readInlineStyle(target, \"pointer-events\"),\n    target,\n  };\n  target.style.pointerEvents = \"none\";\n  overflowTarget?.style.setProperty(\"overflow-x\", \"hidden\");\n}\n\nexport function restoreTextViewerScrollInteractions(\n  snapshotRef: React.MutableRefObject<ScrollInteractionSnapshot | null>,\n) {\n  const snapshot = snapshotRef.current;\n  if (!snapshot) return;\n\n  restoreInlineStyle(snapshot.target, \"pointer-events\", snapshot.pointerEvents);\n  if (snapshot.overflowTarget && snapshot.overflowX) {\n    restoreInlineStyle(\n      snapshot.overflowTarget,\n      \"overflow-x\",\n      snapshot.overflowX,\n    );\n  }\n  snapshotRef.current = null;\n}\n\nfunction isMobileSafari() {\n  if (typeof navigator === \"undefined\") return false;\n  const userAgent = navigator.userAgent;\n  return (\n    /Safari/i.test(userAgent) &&\n    /Mobile/i.test(userAgent) &&\n    !/CriOS|FxiOS|EdgiOS/i.test(userAgent)\n  );\n}\n\nfunction readInlineStyle(\n  element: HTMLElement,\n  propertyName: string,\n): InlineStyleSnapshot {\n  return {\n    priority: element.style.getPropertyPriority(propertyName),\n    value: element.style.getPropertyValue(propertyName),\n  };\n}\n\nfunction restoreInlineStyle(\n  element: HTMLElement,\n  propertyName: string,\n  snapshot: InlineStyleSnapshot,\n) {\n  if (!snapshot.value) {\n    element.style.removeProperty(propertyName);\n    return;\n  }\n  element.style.setProperty(propertyName, snapshot.value, snapshot.priority);\n}\n",
      "type": "registry:ui",
      "target": "@ui/text-viewer-scroll-interactions.ts"
    },
    {
      "path": "registry/new-york-v4/ui/code-viewer-syntax-prism.ts",
      "content": "import Prism from \"prismjs\";\n\nimport type { CodeTokenLeaf } from \"./code-viewer-syntax-protocol\";\n\nPrism.manual = true;\n\nconst coreLanguages = new Set([\"css\", \"javascript\", \"markup\"]);\nconst languageLoaders: Record<string, () => Promise<unknown>> = {\n  bash: () => import(\"prismjs/components/prism-bash\"),\n  diff: () => import(\"prismjs/components/prism-diff\"),\n  dockerfile: () => import(\"prismjs/components/prism-docker\"),\n  go: () => import(\"prismjs/components/prism-go\"),\n  java: () => import(\"prismjs/components/prism-java\"),\n  json: () => import(\"prismjs/components/prism-json\"),\n  jsx: () => import(\"prismjs/components/prism-jsx\"),\n  markdown: () => import(\"prismjs/components/prism-markdown\"),\n  python: () => import(\"prismjs/components/prism-python\"),\n  rust: () => import(\"prismjs/components/prism-rust\"),\n  ruby: () => import(\"prismjs/components/prism-ruby\"),\n  sql: () => import(\"prismjs/components/prism-sql\"),\n  tsx: async () => {\n    await Promise.all([\n      import(\"prismjs/components/prism-typescript\"),\n      import(\"prismjs/components/prism-jsx\"),\n    ]);\n    return import(\"prismjs/components/prism-tsx\");\n  },\n  typescript: () => import(\"prismjs/components/prism-typescript\"),\n  yaml: () => import(\"prismjs/components/prism-yaml\"),\n};\n\nconst loadingLanguages = new Map<string, Promise<void>>();\n\nexport function isCodePrismLanguageLoaded(languageId: string) {\n  return Boolean(Prism.languages[languageId]);\n}\n\nexport function isCodePrismLanguageSupported(languageId: string) {\n  return coreLanguages.has(languageId) || languageId in languageLoaders;\n}\n\nexport async function ensureCodePrismLanguage(languageId: string) {\n  if (isCodePrismLanguageLoaded(languageId)) return;\n  if (coreLanguages.has(languageId)) return;\n\n  let loading = loadingLanguages.get(languageId);\n  if (!loading) {\n    const loadLanguage = languageLoaders[languageId];\n    if (!loadLanguage) {\n      loading = Promise.reject(\n        new Error(`Unsupported code syntax language: ${languageId}`),\n      );\n    } else {\n      loading = loadLanguage().then(() => undefined);\n    }\n    loadingLanguages.set(languageId, loading);\n  }\n\n  await loading;\n}\n\nexport function tokenizeCodeLine(languageId: string, line: string) {\n  const grammar = Prism.languages[languageId] ?? null;\n  if (!grammar) return null;\n  return flattenCodeTokens(Prism.tokenize(line, grammar));\n}\n\nfunction flattenCodeTokens(\n  tokens: Array<string | Prism.Token>,\n  parentKind = \"\",\n  leaves: CodeTokenLeaf[] = [],\n): CodeTokenLeaf[] {\n  for (const token of tokens) {\n    if (typeof token === \"string\") {\n      leaves.push({ kind: parentKind, text: token });\n    } else if (Array.isArray(token.content)) {\n      flattenCodeTokens(\n        token.content as Array<string | Prism.Token>,\n        token.type,\n        leaves,\n      );\n    } else if (typeof token.content === \"string\") {\n      leaves.push({ kind: token.type, text: token.content });\n    } else {\n      flattenCodeTokens([token.content as Prism.Token], token.type, leaves);\n    }\n  }\n  return leaves;\n}\n",
      "type": "registry:ui",
      "target": "@ui/code-viewer-syntax-prism.ts"
    },
    {
      "path": "registry/new-york-v4/ui/code-viewer-syntax-protocol.ts",
      "content": "export const CODE_LINE_TOKENIZE_MAX = 2000;\n\nexport type CodeTokenLeaf = {\n  kind: string;\n  text: string;\n};\n\nexport type CodeSyntaxWorkerRequest = {\n  type: \"tokenize\";\n  requestId: number;\n  generation: number;\n  languageId: string;\n  lines: string[];\n};\n\nexport type CodeSyntaxWorkerResponse =\n  | {\n      type: \"tokens\";\n      requestId: number;\n      generation: number;\n      languageId: string;\n      results: CodeSyntaxWorkerTokenResult[];\n    }\n  | {\n      type: \"error\";\n      requestId: number;\n      generation: number;\n      languageId: string;\n      message: string;\n    };\n\nexport type CodeSyntaxWorkerTokenResult = {\n  line: string;\n  tokens: CodeTokenLeaf[] | null;\n};\n\nexport function shouldTokenizeCodeLine(line: string) {\n  return line.length > 0 && line.length <= CODE_LINE_TOKENIZE_MAX;\n}\n",
      "type": "registry:ui",
      "target": "@ui/code-viewer-syntax-protocol.ts"
    },
    {
      "path": "registry/new-york-v4/ui/markdown-greenfield-code-highlight-html.ts",
      "content": "import type { CodeTokenLeaf } from \"./code-viewer-syntax-protocol\";\n\nexport function markdownCodeTokensToHtml({\n  highlightPattern,\n  line,\n  tokens,\n}: {\n  highlightPattern: string;\n  line: string;\n  tokens: readonly CodeTokenLeaf[] | null;\n}) {\n  if (!tokens) return null;\n  if (!tokens.length) return \" \";\n  const highlightStart = highlightPattern ? line.indexOf(highlightPattern) : -1;\n  const highlightEnd =\n    highlightStart >= 0 ? highlightStart + highlightPattern.length : -1;\n  let cursor = 0;\n  let html = \"\";\n\n  for (const token of tokens) {\n    const tokenStart = cursor;\n    const tokenEnd = cursor + token.text.length;\n    cursor = tokenEnd;\n    html += tokenToHtml({\n      highlightEnd,\n      highlightStart,\n      token,\n      tokenEnd,\n      tokenStart,\n    });\n  }\n\n  return html || \" \";\n}\n\nfunction tokenToHtml({\n  highlightEnd,\n  highlightStart,\n  token,\n  tokenEnd,\n  tokenStart,\n}: {\n  highlightEnd: number;\n  highlightStart: number;\n  token: CodeTokenLeaf;\n  tokenEnd: number;\n  tokenStart: number;\n}) {\n  const inner = tokenTextToHtml({\n    highlightEnd,\n    highlightStart,\n    text: token.text,\n    textEnd: tokenEnd,\n    textStart: tokenStart,\n  });\n  if (!token.kind || !/^[a-z-]+$/.test(token.kind)) return inner;\n  return `<span class=\"cv-token-${token.kind}\" data-pretext-code-token=\"${token.kind}\">${inner}</span>`;\n}\n\nfunction tokenTextToHtml({\n  highlightEnd,\n  highlightStart,\n  text,\n  textEnd,\n  textStart,\n}: {\n  highlightEnd: number;\n  highlightStart: number;\n  text: string;\n  textEnd: number;\n  textStart: number;\n}) {\n  if (\n    highlightStart < 0 ||\n    textEnd <= highlightStart ||\n    textStart >= highlightEnd\n  ) {\n    return escapeHtml(text);\n  }\n\n  const before = text.slice(0, Math.max(0, highlightStart - textStart));\n  const highlighted = text.slice(\n    Math.max(0, highlightStart - textStart),\n    Math.min(text.length, highlightEnd - textStart),\n  );\n  const after = text.slice(Math.min(text.length, highlightEnd - textStart));\n  return [\n    escapeHtml(before),\n    highlighted\n      ? `<span data-highlighted-chars=\"\">${escapeHtml(highlighted)}</span>`\n      : \"\",\n    escapeHtml(after),\n  ].join(\"\");\n}\n\nfunction escapeHtml(value: string) {\n  return value\n    .replace(/&/g, \"&amp;\")\n    .replace(/</g, \"&lt;\")\n    .replace(/>/g, \"&gt;\");\n}\n",
      "type": "registry:ui",
      "target": "@ui/markdown-greenfield-code-highlight-html.ts"
    },
    {
      "path": "registry/new-york-v4/ui/markdown-greenfield-code-highlight-protocol.ts",
      "content": "export const MARKDOWN_CODE_HIGHLIGHT_BATCH_SIZE = 64;\nexport const MARKDOWN_CODE_HIGHLIGHT_CACHE_LIMIT = 4096;\nexport const MARKDOWN_CODE_HIGHLIGHT_RENDERER_VERSION = 1;\n\nexport type MarkdownCodeHighlightLineRequest = {\n  index: number;\n  line: string;\n};\n\nexport type MarkdownCodeHighlightLineResult = {\n  html: string | null;\n  index: number;\n};\n\nexport type MarkdownCodeHighlightWorkerRequest = {\n  generation: number;\n  highlightPattern: string;\n  languageId: string;\n  lines: MarkdownCodeHighlightLineRequest[];\n  requestId: number;\n  type: \"highlight\";\n};\n\nexport type MarkdownCodeHighlightWorkerResponse =\n  | {\n      generation: number;\n      languageId: string;\n      requestId: number;\n      results: MarkdownCodeHighlightLineResult[];\n      type: \"highlighted\";\n    }\n  | {\n      generation: number;\n      languageId: string;\n      message: string;\n      requestId: number;\n      type: \"error\";\n    };\n",
      "type": "registry:ui",
      "target": "@ui/markdown-greenfield-code-highlight-protocol.ts"
    },
    {
      "path": "registry/new-york-v4/ui/markdown-greenfield-code-highlight.worker.ts",
      "content": "import { shouldTokenizeCodeLine } from \"./code-viewer-syntax-protocol\";\nimport {\n  ensureCodePrismLanguage,\n  tokenizeCodeLine,\n} from \"./code-viewer-syntax-prism\";\nimport { markdownCodeTokensToHtml } from \"./markdown-greenfield-code-highlight-html\";\nimport type {\n  MarkdownCodeHighlightWorkerRequest,\n  MarkdownCodeHighlightWorkerResponse,\n} from \"./markdown-greenfield-code-highlight-protocol\";\n\nconst workerSelf = self as unknown as {\n  onmessage:\n    | ((event: MessageEvent<MarkdownCodeHighlightWorkerRequest>) => void)\n    | null;\n  postMessage(message: MarkdownCodeHighlightWorkerResponse): void;\n};\n\nfunction post(message: MarkdownCodeHighlightWorkerResponse) {\n  workerSelf.postMessage(message);\n}\n\nasync function highlightInWorker(request: MarkdownCodeHighlightWorkerRequest) {\n  await ensureCodePrismLanguage(request.languageId);\n  post({\n    generation: request.generation,\n    languageId: request.languageId,\n    requestId: request.requestId,\n    results: request.lines.map(({ index, line }) => ({\n      html: shouldTokenizeCodeLine(line)\n        ? markdownCodeTokensToHtml({\n            highlightPattern: request.highlightPattern,\n            line,\n            tokens: tokenizeCodeLine(request.languageId, line),\n          })\n        : null,\n      index,\n    })),\n    type: \"highlighted\",\n  });\n}\n\nworkerSelf.onmessage = (\n  event: MessageEvent<MarkdownCodeHighlightWorkerRequest>,\n) => {\n  const request = event.data;\n  if (request.type !== \"highlight\") return;\n\n  void highlightInWorker(request).catch((error) => {\n    post({\n      generation: request.generation,\n      languageId: request.languageId,\n      message: error instanceof Error ? error.message : String(error),\n      requestId: request.requestId,\n      type: \"error\",\n    });\n  });\n};\n",
      "type": "registry:ui",
      "target": "@ui/markdown-greenfield-code-highlight.worker.ts"
    },
    {
      "path": "registry/new-york-v4/ui/code-viewer-prism-components.d.ts",
      "content": "declare module \"prismjs/components/prism-bash\";\ndeclare module \"prismjs/components/prism-diff\";\ndeclare module \"prismjs/components/prism-docker\";\ndeclare module \"prismjs/components/prism-go\";\ndeclare module \"prismjs/components/prism-java\";\ndeclare module \"prismjs/components/prism-json\";\ndeclare module \"prismjs/components/prism-jsx\";\ndeclare module \"prismjs/components/prism-markdown\";\ndeclare module \"prismjs/components/prism-python\";\ndeclare module \"prismjs/components/prism-rust\";\ndeclare module \"prismjs/components/prism-ruby\";\ndeclare module \"prismjs/components/prism-sql\";\ndeclare module \"prismjs/components/prism-tsx\";\ndeclare module \"prismjs/components/prism-typescript\";\ndeclare module \"prismjs/components/prism-yaml\";\n",
      "type": "registry:ui",
      "target": "@ui/code-viewer-prism-components.d.ts"
    },
    {
      "path": "registry/new-york-v4/ui/mermaid-renderer.ts",
      "content": "const MERMAID_MAX_LINES = 160;\nconst MERMAID_MAX_SOURCE_LENGTH = 12_000;\nconst MERMAID_CACHE_LIMIT = 64;\nconst MERMAID_SCOPED_CACHE_LIMIT = 128;\nconst MMDR_VERSION = \"0.2.2\";\nconst MMDR_WASM_URL = \"/vendor/mmdr/typst_mmdr.wasm\";\nconst MMDR_BASE_THEME = \"modern\";\nconst MMDR_LAYOUT = \"\";\n\nconst MMDR_THEME = {\n  background: \"transparent\",\n  cluster_bkg: \"transparent\",\n  cluster_border: \"var(--mmdr-border)\",\n  edge_label_background: \"transparent\",\n  font_family: \"Inter, ui-sans-serif, system-ui, sans-serif\",\n  font_size: 16,\n  line_color: \"var(--mmdr-line)\",\n  primary_border_color: \"var(--mmdr-border)\",\n  primary_color: \"var(--mmdr-node-fill)\",\n  primary_text_color: \"var(--mmdr-text)\",\n  secondary_color: \"var(--mmdr-node-fill)\",\n  tertiary_color: \"var(--mmdr-node-fill)\",\n} as const;\nconst MMDR_THEME_JSON = JSON.stringify(MMDR_THEME);\n\nconst MERMAID_CONFIG = {\n  flowchart: {\n    htmlLabels: false,\n    useMaxWidth: true,\n  },\n  securityLevel: \"strict\",\n  sequence: {\n    useMaxWidth: true,\n  },\n  startOnLoad: false,\n  suppressErrorRendering: true,\n  theme: \"base\",\n  themeVariables: {\n    background: \"transparent\",\n    fontFamily: \"Inter, ui-sans-serif, system-ui, sans-serif\",\n    lineColor: \"#64748b\",\n    primaryBorderColor: \"#94a3b8\",\n    primaryColor: \"#f8fafc\",\n    primaryTextColor: \"#0f172a\",\n    secondaryColor: \"#ecfeff\",\n    tertiaryColor: \"#f0fdf4\",\n  },\n} as const;\n\nconst DIAGRAM_CACHE_CONFIG_KEY = JSON.stringify({\n  mermaid: MERMAID_CONFIG,\n  mmdr: {\n    baseTheme: MMDR_BASE_THEME,\n    theme: MMDR_THEME,\n    version: MMDR_VERSION,\n  },\n});\nconst MERMAID_CACHE_CONFIG_KEY = JSON.stringify(MERMAID_CONFIG);\n\nexport const MERMAID_VIEWER_STYLES = `\n[data-pretext-mermaid-svg] {\n  --mmdr-border: var(--border);\n  --mmdr-line: var(--muted-foreground);\n  --mmdr-node-fill: color-mix(in srgb, var(--background) 88%, var(--foreground) 12%);\n  --mmdr-text: var(--foreground);\n}\n[data-pretext-mermaid-svg] svg {\n  color: var(--foreground);\n  display: block;\n  height: auto;\n  margin-inline: auto;\n  max-width: 100%;\n}\n[data-pretext-mermaid-svg] rect.background {\n  fill: transparent;\n  stroke: none;\n}\n[data-pretext-mermaid-svg] .node rect,\n[data-pretext-mermaid-svg] .node polygon,\n[data-pretext-mermaid-svg] .node circle,\n[data-pretext-mermaid-svg] .node ellipse,\n[data-pretext-mermaid-svg] .node path,\n[data-pretext-mermaid-svg] .basic.label-container,\n[data-pretext-mermaid-svg] .label-container,\n[data-pretext-mermaid-svg] .actor,\n[data-pretext-mermaid-svg] .entityBox,\n[data-pretext-mermaid-svg] .state,\n[data-pretext-mermaid-svg] .classGroup rect,\n[data-pretext-mermaid-svg] .requirement,\n[data-pretext-mermaid-svg] .requirementBox,\n[data-pretext-mermaid-svg] .element,\n[data-pretext-mermaid-svg] .cluster rect,\n[data-pretext-mermaid-svg] .note,\n[data-pretext-mermaid-svg] .task {\n  fill: color-mix(in srgb, var(--background) 88%, var(--foreground) 12%);\n  stroke: var(--border);\n  stroke-width: 1px;\n}\n[data-pretext-mermaid-svg] .cluster rect,\n[data-pretext-mermaid-svg] .section,\n[data-pretext-mermaid-svg] .grid .tick line {\n  fill: color-mix(in srgb, var(--background) 94%, var(--foreground) 6%);\n  stroke: var(--border);\n}\n[data-pretext-mermaid-svg] text,\n[data-pretext-mermaid-svg] tspan,\n[data-pretext-mermaid-svg] .label,\n[data-pretext-mermaid-svg] .label text,\n[data-pretext-mermaid-svg] .nodeLabel,\n[data-pretext-mermaid-svg] .edgeLabel,\n[data-pretext-mermaid-svg] .actor,\n[data-pretext-mermaid-svg] .messageText,\n[data-pretext-mermaid-svg] .noteText,\n[data-pretext-mermaid-svg] .taskText,\n[data-pretext-mermaid-svg] .sectionTitle,\n[data-pretext-mermaid-svg] .titleText {\n  color: var(--foreground);\n  fill: var(--foreground);\n}\n[data-pretext-mermaid-svg] .edgePath path,\n[data-pretext-mermaid-svg] .flowchart-link,\n[data-pretext-mermaid-svg] .messageLine0,\n[data-pretext-mermaid-svg] .messageLine1,\n[data-pretext-mermaid-svg] .actor-line,\n[data-pretext-mermaid-svg] .relation,\n[data-pretext-mermaid-svg] .transition,\n[data-pretext-mermaid-svg] line {\n  fill: none;\n  stroke: var(--muted-foreground);\n}\n[data-pretext-mermaid-svg] marker path,\n[data-pretext-mermaid-svg] marker polygon,\n[data-pretext-mermaid-svg] marker circle,\n[data-pretext-mermaid-svg] .marker,\n[data-pretext-mermaid-svg] .arrowMarkerPath {\n  fill: var(--muted-foreground);\n  stroke: var(--muted-foreground);\n}\n`;\n\nconst mermaidDiagramCache = new Map<string, Promise<DiagramState>>();\nconst mermaidScopedSvgCache = new Map<string, string>();\n\ntype MermaidApi = {\n  initialize?: (config: typeof MERMAID_CONFIG) => void;\n  render: (id: string, source: string) => Promise<{ svg: string }>;\n};\n\ntype MmdrApi = {\n  render: (source: string) => string;\n};\n\ntype MmdrExports = {\n  memory: WebAssembly.Memory;\n  render: (\n    sourceLength: number,\n    baseThemeLength: number,\n    themeLength: number,\n    layoutLength: number,\n  ) => number;\n};\n\ntype MermaidRenderJob = {\n  reject: (reason: unknown) => void;\n  resolve: (state: DiagramState) => void;\n  run: () => Promise<DiagramState>;\n};\n\ntype MermaidIdleWindow = Window &\n  typeof globalThis & {\n    requestIdleCallback?: (\n      callback: () => void,\n      options?: { timeout?: number },\n    ) => number;\n  };\n\nlet mermaidApiPromise: Promise<MermaidApi> | null = null;\nlet mmdrApiPromise: Promise<MmdrApi> | null = null;\nlet isMmdrUnavailable = false;\nlet mermaidInitializedConfigKey = \"\";\nlet isMermaidRenderQueueRunning = false;\nconst mermaidRenderQueue: MermaidRenderJob[] = [];\n\nexport type DiagramRenderer = \"basic\" | \"mermaid\" | \"mmdr\";\nexport type DiagramState =\n  | { status: \"failed\"; message: string }\n  | { status: \"loading\" }\n  | { renderer: DiagramRenderer; status: \"ready\"; svg: string };\ntype ReadyDiagramState = Extract<DiagramState, { status: \"ready\" }>;\n\nexport function resetMermaidRendererForTests() {\n  mermaidDiagramCache.clear();\n  mermaidScopedSvgCache.clear();\n  mermaidRenderQueue.length = 0;\n  mermaidApiPromise = null;\n  mmdrApiPromise = null;\n  isMmdrUnavailable = false;\n  mermaidInitializedConfigKey = \"\";\n  isMermaidRenderQueueRunning = false;\n}\n\nexport async function renderDiagram(\n  source: string,\n  id: string,\n): Promise<DiagramState> {\n  const state = await getCachedDiagram(source);\n  if (state.status !== \"ready\") return state;\n  const scopedKey = `${DIAGRAM_CACHE_CONFIG_KEY}\\0${state.renderer}\\0${id}\\0${source}`;\n  let svg = mermaidScopedSvgCache.get(scopedKey);\n  if (!svg) {\n    svg = scopeCachedMermaidSvg(state.svg, id);\n    mermaidScopedSvgCache.set(scopedKey, svg);\n    trimMapToLimit(mermaidScopedSvgCache, MERMAID_SCOPED_CACHE_LIMIT);\n  }\n  return { renderer: state.renderer, status: \"ready\", svg };\n}\n\nfunction getCachedDiagram(source: string) {\n  const key = `${DIAGRAM_CACHE_CONFIG_KEY}\\0${source}`;\n  let cached = mermaidDiagramCache.get(key);\n  if (!cached) {\n    cached = enqueueMermaidRender(() => loadDiagram(source));\n    mermaidDiagramCache.set(key, cached);\n    trimMapToLimit(mermaidDiagramCache, MERMAID_CACHE_LIMIT);\n  }\n  return cached;\n}\n\nfunction enqueueMermaidRender(run: () => Promise<DiagramState>) {\n  return new Promise<DiagramState>((resolve, reject) => {\n    mermaidRenderQueue.push({ reject, resolve, run });\n    void drainMermaidRenderQueue();\n  });\n}\n\nasync function drainMermaidRenderQueue() {\n  if (isMermaidRenderQueueRunning) return;\n  isMermaidRenderQueueRunning = true;\n  try {\n    for (;;) {\n      const job = mermaidRenderQueue.shift();\n      if (!job) return;\n      try {\n        await waitForMermaidRenderSlot();\n        job.resolve(await job.run());\n      } catch (error) {\n        job.reject(error);\n      }\n    }\n  } finally {\n    isMermaidRenderQueueRunning = false;\n  }\n}\n\nfunction waitForMermaidRenderSlot() {\n  if (typeof window === \"undefined\") return Promise.resolve();\n  const idleWindow = window as MermaidIdleWindow;\n  if (typeof idleWindow.requestIdleCallback === \"function\") {\n    return new Promise<void>((resolve) => {\n      idleWindow.requestIdleCallback?.(() => resolve(), { timeout: 250 });\n    });\n  }\n  if (typeof window.requestAnimationFrame === \"function\") {\n    return new Promise<void>((resolve) => {\n      window.requestAnimationFrame(() => resolve());\n    });\n  }\n  return new Promise<void>((resolve) => {\n    window.setTimeout(resolve, 0);\n  });\n}\n\nasync function loadDiagram(source: string): Promise<DiagramState> {\n  const mmdrState = await loadMmdrDiagram(source);\n  if (mmdrState.status === \"ready\") return mmdrState;\n  return loadMermaidDiagram(source);\n}\n\nasync function loadMmdrDiagram(source: string): Promise<DiagramState> {\n  try {\n    const mmdr = await loadMmdrApi();\n    return {\n      renderer: \"mmdr\",\n      status: \"ready\",\n      svg: sanitizeSvg(mmdr.render(source)),\n    };\n  } catch {\n    return { status: \"failed\", message: \"mmdr unavailable\" };\n  }\n}\n\nasync function loadMermaidDiagram(source: string): Promise<DiagramState> {\n  try {\n    const mermaid = await loadMermaidApi();\n    const result = await mermaid.render(\n      `markdown-diagram-cache-${hashMermaidSource(source)}`,\n      source,\n    );\n    return {\n      renderer: \"mermaid\",\n      status: \"ready\",\n      svg: sanitizeSvg(result.svg),\n    };\n  } catch (error) {\n    const message = error instanceof Error ? error.message : \"Invalid diagram\";\n    if (!/getBBox|layout|force-basic-fallback/i.test(message)) {\n      return { status: \"failed\", message };\n    }\n    return renderBasicMermaidDiagram(source);\n  }\n}\n\nasync function loadMmdrApi() {\n  if (!canUseMmdrRenderer()) {\n    throw new Error(\"mmdr is unavailable in this runtime\");\n  }\n  if (isMmdrUnavailable) {\n    throw new Error(\"mmdr failed to load\");\n  }\n  mmdrApiPromise ??= instantiateMmdrApi().catch((error) => {\n    isMmdrUnavailable = true;\n    throw error;\n  });\n  return mmdrApiPromise;\n}\n\nasync function loadMermaidApi() {\n  mermaidApiPromise ??= import(\"mermaid\").then(\n    (mermaidModule) => mermaidModule.default as MermaidApi,\n  );\n  const mermaid = await mermaidApiPromise;\n  if (mermaidInitializedConfigKey !== MERMAID_CACHE_CONFIG_KEY) {\n    mermaid.initialize?.(MERMAID_CONFIG);\n    mermaidInitializedConfigKey = MERMAID_CACHE_CONFIG_KEY;\n  }\n  return mermaid;\n}\n\nfunction canUseMmdrRenderer() {\n  return (\n    typeof window !== \"undefined\" &&\n    typeof window.fetch === \"function\" &&\n    typeof WebAssembly !== \"undefined\" &&\n    typeof TextDecoder !== \"undefined\" &&\n    typeof TextEncoder !== \"undefined\"\n  );\n}\n\nasync function instantiateMmdrApi(): Promise<MmdrApi> {\n  let instance: WebAssembly.Instance | null = null;\n  let currentArgs: Uint8Array[] = [];\n  let currentResult: Uint8Array | null = null;\n  const imports = {\n    typst_env: {\n      wasm_minimal_protocol_send_result_to_host(\n        pointer: number,\n        length: number,\n      ) {\n        if (!instance) return;\n        const memory = new Uint8Array(\n          (instance.exports as unknown as MmdrExports).memory.buffer,\n        );\n        currentResult = memory.slice(pointer, pointer + length);\n      },\n      wasm_minimal_protocol_write_args_to_buffer(pointer: number) {\n        if (!instance) return;\n        const memory = new Uint8Array(\n          (instance.exports as unknown as MmdrExports).memory.buffer,\n        );\n        let offset = pointer;\n        for (const arg of currentArgs) {\n          memory.set(arg, offset);\n          offset += arg.length;\n        }\n      },\n    },\n  };\n  const response = await window.fetch(MMDR_WASM_URL);\n  if (!response.ok) {\n    throw new Error(`Failed to load mmdr renderer: ${response.status}`);\n  }\n  const wasm = await WebAssembly.instantiate(\n    await response.arrayBuffer(),\n    imports,\n  );\n  instance = wasm.instance;\n  const exports = instance.exports as unknown as MmdrExports;\n  const encoder = new TextEncoder();\n  const decoder = new TextDecoder();\n\n  return {\n    render(source: string) {\n      currentArgs = [\n        encoder.encode(source),\n        encoder.encode(MMDR_BASE_THEME),\n        encoder.encode(MMDR_THEME_JSON),\n        encoder.encode(MMDR_LAYOUT),\n      ];\n      currentResult = null;\n      const status = exports.render(\n        currentArgs[0]?.length ?? 0,\n        currentArgs[1]?.length ?? 0,\n        currentArgs[2]?.length ?? 0,\n        currentArgs[3]?.length ?? 0,\n      );\n      const result = decoder.decode(currentResult ?? new Uint8Array());\n      currentArgs = [];\n      currentResult = null;\n      if (status !== 0) {\n        throw new Error(result || \"mmdr failed to render the diagram\");\n      }\n      return result;\n    },\n  };\n}\n\nfunction scopeCachedMermaidSvg(svg: string, idPrefix: string) {\n  if (typeof DOMParser === \"undefined\") return svg;\n  const document = new DOMParser().parseFromString(svg, \"image/svg+xml\");\n  const root = document.documentElement;\n  if (!root || root.tagName.toLowerCase() !== \"svg\") return svg;\n\n  const ids = new Map<string, string>();\n  for (const element of Array.from(root.querySelectorAll(\"[id]\"))) {\n    const id = element.getAttribute(\"id\");\n    if (!id) continue;\n    const nextId = `${idPrefix}-${id}`;\n    ids.set(id, nextId);\n    element.setAttribute(\"id\", nextId);\n  }\n  if (!ids.size) return new XMLSerializer().serializeToString(root);\n\n  for (const element of Array.from(root.querySelectorAll(\"*\"))) {\n    for (const attribute of Array.from(element.attributes)) {\n      const value = attribute.value;\n      let nextValue = value;\n      for (const [oldId, nextId] of ids) {\n        nextValue = nextValue\n          .replaceAll(`url(#${oldId})`, `url(#${nextId})`)\n          .replaceAll(`#${oldId}`, `#${nextId}`);\n      }\n      if (nextValue !== value) element.setAttribute(attribute.name, nextValue);\n    }\n  }\n\n  return new XMLSerializer().serializeToString(root);\n}\n\nfunction hashMermaidSource(source: string) {\n  let hash = 2166136261;\n  for (let index = 0; index < source.length; index += 1) {\n    hash ^= source.charCodeAt(index);\n    hash = Math.imul(hash, 16777619);\n  }\n  return (hash >>> 0).toString(36);\n}\n\nfunction renderBasicMermaidDiagram(source: string): ReadyDiagramState {\n  const kind = diagramKind(source);\n  const lines = readableDiagramLines(source);\n  const escaped = escapeHtml(lines.join(\" | \") || describeDiagram(source));\n  const piePaths =\n    kind === \"pie\"\n      ? '<path d=\"M20 20h20v20H20z\"/><path d=\"M44 20h20v20H44z\"/><path d=\"M68 20h20v20H68z\"/>'\n      : \"\";\n  return {\n    renderer: \"basic\",\n    status: \"ready\",\n    svg: `<svg role=\"img\" aria-label=\"Mermaid diagram\" data-pretext-basic-mermaid=\"${kind}\" viewBox=\"0 0 720 160\" width=\"100%\" height=\"160\" xmlns=\"http://www.w3.org/2000/svg\"><rect width=\"720\" height=\"160\" rx=\"8\" fill=\"currentColor\" opacity=\"0.05\"/>${piePaths}<text x=\"24\" y=\"82\" fill=\"currentColor\" font-family=\"monospace\" font-size=\"16\">${escaped}</text></svg>`,\n  };\n}\n\nfunction sanitizeSvg(svg: string) {\n  if (typeof DOMParser === \"undefined\") return svg;\n\n  const document = new DOMParser().parseFromString(svg, \"image/svg+xml\");\n  const root = document.documentElement;\n  if (!root || root.tagName.toLowerCase() !== \"svg\") {\n    const fallback = renderBasicMermaidDiagram(\"\");\n    return fallback.status === \"ready\" ? fallback.svg : \"\";\n  }\n\n  root.setAttribute(\"role\", \"img\");\n  if (!root.getAttribute(\"aria-label\")) {\n    root.setAttribute(\"aria-label\", \"Mermaid diagram\");\n  }\n  root.setAttribute(\"data-pretext-sanitized-mermaid\", \"\");\n\n  for (const element of Array.from(root.querySelectorAll(\"*\"))) {\n    const tagName = element.tagName.toLowerCase();\n    if (\n      tagName === \"script\" ||\n      tagName === \"a\" ||\n      tagName === \"animate\" ||\n      tagName === \"foreignobject\" ||\n      tagName === \"image\" ||\n      tagName === \"iframe\" ||\n      tagName === \"object\" ||\n      tagName === \"embed\" ||\n      tagName === \"style\" ||\n      tagName === \"use\"\n    ) {\n      element.remove();\n      continue;\n    }\n\n    for (const attribute of Array.from(element.attributes)) {\n      const name = attribute.name.toLowerCase();\n      const value = attribute.value.trim();\n      if (name.startsWith(\"on\") || name === \"style\") {\n        element.removeAttribute(attribute.name);\n        continue;\n      }\n      if ((name === \"id\" || name === \"name\") && isDomClobberingId(value)) {\n        element.setAttribute(attribute.name, `user-content-${value}`);\n        continue;\n      }\n      if (\n        (name === \"href\" || name.endsWith(\":href\") || name === \"src\") &&\n        !isSafeSvgReference(value)\n      ) {\n        element.removeAttribute(attribute.name);\n      }\n    }\n  }\n\n  for (const attribute of Array.from(root.attributes)) {\n    const name = attribute.name.toLowerCase();\n    if (name.startsWith(\"on\") || name === \"style\") {\n      root.removeAttribute(attribute.name);\n      continue;\n    }\n    if (\n      (name === \"id\" || name === \"name\") &&\n      isDomClobberingId(attribute.value)\n    ) {\n      root.setAttribute(attribute.name, `user-content-${attribute.value}`);\n    }\n  }\n\n  return new XMLSerializer()\n    .serializeToString(root)\n    .replace(/^<svg\\b([^>]*)\\brole=\"([^\"]*)\"([^>]*)>/, '<svg role=\"$2\"$1$3>');\n}\n\nfunction trimMapToLimit<K, V>(map: Map<K, V>, limit: number) {\n  while (map.size > limit) {\n    const oldestKey = map.keys().next().value;\n    if (oldestKey === undefined) break;\n    map.delete(oldestKey);\n  }\n}\n\nfunction isSafeSvgReference(value: string) {\n  return (\n    value === \"\" ||\n    value.startsWith(\"#\") ||\n    value.startsWith(\"data:image/\") ||\n    /^https?:\\/\\//i.test(value)\n  );\n}\n\nexport function readDiagramLimitMessage(source: string) {\n  if (source.length > MERMAID_MAX_SOURCE_LENGTH) {\n    return \"Mermaid diagram too large to render safely. Copy the source and render it in a dedicated diagram tool.\";\n  }\n  if (source.split(/\\r\\n|[\\n\\r\\u2028\\u2029]/).length > MERMAID_MAX_LINES) {\n    return \"Mermaid diagram has too many lines to render safely. Copy the source and render it in a dedicated diagram tool.\";\n  }\n  return null;\n}\n\nexport function describeDiagram(source: string) {\n  const lines = semanticDiagramLines(source);\n  const kind = diagramKind(source);\n  if (kind === \"graph\") {\n    const direction = lines[0]\n      ?.match(/\\b(?:graph|flowchart)\\s+([A-Z]{2})\\b/i)?.[1]\n      ?.toUpperCase();\n    const directionText = direction === \"LR\" ? \"left to right\" : \"top down\";\n    const edges = lines.filter((line) => /-->|---|-.->|==>/.test(line)).length;\n    const nodes = new Set<string>();\n    for (const line of lines) {\n      for (const match of line.matchAll(/\\b([A-Za-z][\\w-]*)\\b/g)) {\n        const value = match[1]!;\n        if (\n          ![\"graph\", \"flowchart\", \"TD\", \"LR\", \"TB\", \"BT\", \"RL\"].includes(value)\n        ) {\n          nodes.add(value);\n        }\n      }\n    }\n    return `Mermaid graph diagram flowing ${directionText}, with ${nodes.size} nodes and ${edges} edge${edges === 1 ? \"\" : \"s\"}.`;\n  }\n  if (kind === \"sequence\") {\n    const messages = lines.filter((line) =>\n      /->>|-->>|->|-->/.test(line),\n    ).length;\n    const participants = new Set<string>();\n    for (const line of lines) {\n      const participant = /^participant\\s+\\S+(?:\\s+as\\s+(.+))?/i.exec(line);\n      if (participant)\n        participants.add(participant[1] ?? line.split(/\\s+/)[1]!);\n      for (const side of line.split(/->>|-->>|->|-->/)) {\n        const name = side.split(\":\")[0]?.trim();\n        if (name && !/^sequenceDiagram/i.test(name)) participants.add(name);\n      }\n    }\n    return `Mermaid sequence diagram with ${participants.size} participants and ${messages} message${messages === 1 ? \"\" : \"s\"}.`;\n  }\n  if (kind === \"state\") {\n    const transitions = lines.filter((line) => /-->/.test(line)).length;\n    const states = new Set<string>();\n    for (const line of lines) {\n      const stateAlias = /^state\\s+\"([^\"]+)\"\\s+as\\s+(\\w+)/.exec(line);\n      if (stateAlias) {\n        states.add(stateAlias[1]!);\n        continue;\n      }\n      if (!/-->/.test(line)) continue;\n      for (const side of line.split(/-->/)) {\n        const name = side\n          .split(\":\")[0]!\n          .replace(/\\[\\*\\]/g, \"\")\n          .trim();\n        if (name && !/^stateDiagram/.test(name)) {\n          states.add(name);\n        }\n      }\n    }\n    return `Mermaid state diagram with ${states.size} states and ${transitions} transitions.`;\n  }\n  if (kind === \"class\") {\n    const relationships = lines.filter((line) =>\n      /<\\|--|--|<--|\\.\\./.test(line),\n    ).length;\n    const classes = new Set<string>();\n    for (const line of lines) {\n      for (const match of line.matchAll(/\\b([A-Z][A-Za-z0-9_]*)\\b/g))\n        classes.add(match[1]!);\n    }\n    return `Mermaid class diagram with ${classes.size} classes and ${relationships} relationship${relationships === 1 ? \"\" : \"s\"}.`;\n  }\n  if (kind === \"er\") {\n    const relationships = lines.filter((line) =>\n      /\\|\\||o\\{|\\|\\{/.test(line),\n    ).length;\n    const entities = new Set<string>();\n    for (const line of lines) {\n      for (const match of line.matchAll(/\\b([A-Z][A-Z0-9_]*)\\b/g)) {\n        if (![\"ERDIAGRAM\"].includes(match[1]!)) entities.add(match[1]!);\n      }\n    }\n    return `Mermaid entity relationship diagram with ${entities.size} entities and ${relationships} relationships.`;\n  }\n  if (kind === \"journey\")\n    return `Mermaid journey diagram with ${countLinesStarting(lines, \"section \")} sections and ${lines.filter((line) => /:\\s*\\d+\\s*:/.test(line)).length} tasks.`;\n  if (kind === \"gantt\")\n    return `Mermaid Gantt chart with ${countLinesStarting(lines, \"section \")} sections and ${lines.filter((line) => /:/.test(line) && !/^dateFormat/i.test(line) && !/^title\\b/i.test(line)).length} tasks.`;\n  if (kind === \"gitGraph\")\n    return `Mermaid Git graph with ${countLinesStarting(lines, \"branch \")} branch, ${countExact(lines, \"commit\")} commits, and ${countLinesStarting(lines, \"merge \")} merge.`;\n  if (kind === \"timeline\")\n    return `Mermaid timeline with ${countLinesStarting(lines, \"section \")} sections and ${lines.filter((line) => /^\\d/.test(line)).length} events.`;\n  if (kind === \"mindmap\")\n    return `Mermaid mind map with ${Math.max(0, lines.length - 1)} nodes.`;\n  if (kind === \"quadrantChart\")\n    return `Mermaid quadrant chart with ${lines.filter((line) => /:\\s*\\[[^\\]]+\\]/.test(line)).length} points.`;\n  if (kind === \"requirementDiagram\")\n    return `Mermaid requirement diagram with ${countLinesStarting(lines, \"requirement \")} requirement, ${countLinesStarting(lines, \"element \")} element, and ${lines.filter((line) => /-\\s*\\w+\\s*->/.test(line)).length} relationship.`;\n  if (kind === \"xychart\") {\n    const series = lines.filter((line) => /^(bar|line)\\s+\\[/.test(line));\n    const values = series.reduce(\n      (sum, line) => sum + (line.match(/-?\\d+(?:\\.\\d+)?/g)?.length ?? 0),\n      0,\n    );\n    return `Mermaid XY chart with ${series.length} series and ${values} values.`;\n  }\n  if (kind === \"sankey\") {\n    const flows = lines.filter((line) => line.includes(\",\"));\n    const nodes = new Set(\n      flows.flatMap((line) =>\n        line\n          .split(\",\")\n          .slice(0, 2)\n          .map((item) => item.trim()),\n      ),\n    );\n    return `Mermaid Sankey diagram with ${nodes.size} nodes and ${flows.length} flows.`;\n  }\n  if (kind === \"c4\")\n    return `Mermaid C4 diagram with ${lines.filter((line) => /^(Person|System)\\(/.test(line)).length} nodes and ${countLinesStarting(lines, \"Rel(\")} relationship.`;\n  if (kind === \"pie\") {\n    const values = lines\n      .flatMap((line) => line.match(/:\\s*(\\d+(?:\\.\\d+)?)/)?.[1] ?? [])\n      .map(Number);\n    const total = values.reduce((sum, value) => sum + value, 0);\n    return `Mermaid pie chart with ${values.length} slices and total value ${total}.`;\n  }\n  return \"Mermaid diagram\";\n}\n\nexport function estimateDiagramBodyHeight(source: string) {\n  switch (diagramKind(source)) {\n    case \"graph\":\n      return 286;\n    case \"sequence\":\n      return 160;\n    case \"state\":\n      return 238;\n    case \"class\":\n      return 220;\n    case \"er\":\n      return 292;\n    case \"journey\":\n      return 270;\n    case \"gantt\":\n      return 300;\n    case \"gitGraph\":\n      return 212;\n    case \"timeline\":\n      return 270;\n    case \"mindmap\":\n      return 232;\n    case \"quadrantChart\":\n      return 296;\n    case \"requirementDiagram\":\n      return 230;\n    case \"xychart\":\n      return 334;\n    case \"sankey\":\n      return 310;\n    case \"c4\":\n      return 236;\n    case \"pie\":\n      return 224;\n    default:\n      return 180;\n  }\n}\n\nfunction diagramKind(source: string) {\n  const first = semanticDiagramLines(source)[0] ?? \"\";\n  if (/^(?:graph|flowchart)\\b/i.test(first)) return \"graph\";\n  if (/^sequenceDiagram\\b/i.test(first)) return \"sequence\";\n  if (/^stateDiagram/i.test(first)) return \"state\";\n  if (/^classDiagram\\b/i.test(first)) return \"class\";\n  if (/^erDiagram\\b/i.test(first)) return \"er\";\n  if (/^journey\\b/i.test(first)) return \"journey\";\n  if (/^gantt\\b/i.test(first)) return \"gantt\";\n  if (/^gitGraph\\b/i.test(first)) return \"gitGraph\";\n  if (/^timeline\\b/i.test(first)) return \"timeline\";\n  if (/^mindmap\\b/i.test(first)) return \"mindmap\";\n  if (/^quadrantChart\\b/i.test(first)) return \"quadrantChart\";\n  if (/^requirementDiagram\\b/i.test(first)) return \"requirementDiagram\";\n  if (/^xychart/i.test(first)) return \"xychart\";\n  if (/^sankey/i.test(first)) return \"sankey\";\n  if (/^C4/i.test(first)) return \"c4\";\n  if (/^pie\\b/i.test(first)) return \"pie\";\n  return \"source\";\n}\n\nfunction semanticDiagramLines(source: string) {\n  return stripMermaidFrontmatter(source)\n    .split(/\\r\\n|[\\n\\r\\u2028\\u2029]/)\n    .map((line) => line.trim())\n    .filter((line) => line && !line.startsWith(\"%%\"));\n}\n\nfunction readableDiagramLines(source: string) {\n  const lines = semanticDiagramLines(source);\n  if (diagramKind(source) === \"pie\") {\n    return lines.map((line) => {\n      const match = /\"([^\"]+)\"\\s*:\\s*(\\d+(?:\\.\\d+)?)/.exec(line);\n      return match ? `${match[1]} ${match[2]} (${match[2]}%)` : line;\n    });\n  }\n  return lines;\n}\n\nfunction stripMermaidFrontmatter(source: string) {\n  const lines = source.split(/\\r\\n|[\\n\\r\\u2028\\u2029]/);\n  if (lines[0]?.trim() !== \"---\") return source;\n  const end = lines.findIndex(\n    (line, index) => index > 0 && line.trim() === \"---\",\n  );\n  return end === -1 ? source : lines.slice(end + 1).join(\"\\n\");\n}\n\nfunction countLinesStarting(lines: readonly string[], prefix: string) {\n  return lines.filter((line) => line.startsWith(prefix)).length;\n}\n\nfunction countExact(lines: readonly string[], value: string) {\n  return lines.filter((line) => line === value).length;\n}\n\nfunction isDomClobberingId(id: string) {\n  return [\"constructor\", \"forms\", \"images\", \"location\", \"__proto__\"].includes(\n    id,\n  );\n}\n\nfunction escapeHtml(value: string) {\n  return value\n    .replace(/&/g, \"&amp;\")\n    .replace(/</g, \"&lt;\")\n    .replace(/>/g, \"&gt;\")\n    .replace(/\"/g, \"&quot;\");\n}\n",
      "type": "registry:ui",
      "target": "@ui/mermaid-renderer.ts"
    },
    {
      "path": "registry/new-york-v4/ui/file-viewer-context.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport type { FileCategory } from \"@/lib/viewer-source\";\nimport type { FileViewerElementRegistry } from \"./file-viewer-elements\";\nimport type { FileViewerMotionKernel } from \"./file-viewer-motion-kernel\";\n\nexport type FileViewerSidebarMode = \"inline\" | \"overlay\";\nexport type FileViewerSidebarRequestedMode = \"auto\" | FileViewerSidebarMode;\nexport type FileViewerSidebarState = \"expanded\" | \"collapsed\";\nexport type FileViewerSidebarSide = \"left\" | \"right\";\nexport type FileViewerSidebarCollapsible = \"offcanvas\" | \"none\";\n\nexport type FileViewerHeaderMode = \"inline\" | \"outlets\";\n\nexport const DEFAULT_FILE_VIEWER_SIDEBAR_WIDTH = \"10rem\";\n\nexport type FileViewerSetSidebarOpen = (\n  value: boolean | ((isSidebarOpen: boolean) => boolean),\n) => void;\n\nexport type FileViewerSidebarOpenProps = {\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  open?: boolean;\n};\n\nexport type FileViewerContextValue = {\n  headerMode: FileViewerHeaderMode;\n  hasHeaderOutlets: boolean;\n  isInsideFileViewer: boolean;\n  resourceCategory: FileCategory;\n  sidebarOpenProps: FileViewerSidebarOpenProps;\n};\n\nexport type FileViewerSidebarRegistration = {\n  collapsible: FileViewerSidebarCollapsible;\n  id: string;\n  side: FileViewerSidebarSide;\n  width: string;\n  widthPixels: number;\n};\n\nexport type FileViewerSidebarValue = {\n  canToggleSidebar: boolean;\n  isSidebarInteractive: boolean;\n  isSidebarOpen: boolean;\n  mode: FileViewerSidebarMode;\n  side: FileViewerSidebarSide;\n  sidebarId: string;\n  sidebarState: FileViewerSidebarState;\n  setSidebarOpen: FileViewerSetSidebarOpen;\n  toggleSidebar: () => void;\n};\n\nexport type FileViewerShellStaticContextValue = {\n  canToggleSidebar: boolean;\n  collapsible: FileViewerSidebarCollapsible;\n  elementRegistry: FileViewerElementRegistry;\n  mode: FileViewerSidebarMode;\n  motionDurationMs: number;\n  motionKernel: FileViewerMotionKernel;\n  registerSidebar: (registration: FileViewerSidebarRegistration) => () => void;\n  rootId: string;\n  setSidebarOpen: FileViewerSetSidebarOpen;\n  side: FileViewerSidebarSide;\n  sidebarId: string;\n  sidebarWidth: string;\n  toggleSidebar: () => void;\n};\n\nexport type FileViewerSidebarDynamicContextValue = {\n  isSidebarInteractive: boolean;\n  isSidebarOpen: boolean;\n  isSidebarTransitioning: boolean;\n  sidebarState: FileViewerSidebarState;\n};\n\nexport const FileViewerContext = React.createContext<FileViewerContextValue>({\n  headerMode: \"inline\",\n  hasHeaderOutlets: false,\n  isInsideFileViewer: false,\n  resourceCategory: \"unsupported\",\n  sidebarOpenProps: {},\n});\n\nexport const FileViewerShellStaticContext =\n  React.createContext<FileViewerShellStaticContextValue | null>(null);\n\nexport const FileViewerSidebarDynamicContext =\n  React.createContext<FileViewerSidebarDynamicContextValue | null>(null);\n\nexport function useFileViewerContext() {\n  return React.useContext(FileViewerContext);\n}\n\nexport function useOptionalFileViewerShellStatic() {\n  return React.useContext(FileViewerShellStaticContext);\n}\n\nexport function useFileViewerShellStatic(consumer: string) {\n  const context = React.useContext(FileViewerShellStaticContext);\n  if (!context) {\n    throw new Error(`${consumer} must be rendered inside FileViewer.`);\n  }\n  return context;\n}\n\nexport function useOptionalFileViewerShell() {\n  const staticContext = React.useContext(FileViewerShellStaticContext);\n  const sidebarContext = React.useContext(FileViewerSidebarDynamicContext);\n\n  return React.useMemo(\n    () =>\n      staticContext && sidebarContext\n        ? { ...staticContext, ...sidebarContext }\n        : null,\n    [sidebarContext, staticContext],\n  );\n}\n\nexport function useFileViewerShell(consumer: string) {\n  const context = useOptionalFileViewerShell();\n  if (!context) {\n    throw new Error(`${consumer} must be rendered inside FileViewer.`);\n  }\n  return context;\n}\n\nexport function useFileViewerSidebar(): FileViewerSidebarValue {\n  const fileViewerContext = React.useContext(FileViewerContext);\n  const shellContext = useOptionalFileViewerShell();\n\n  if (!fileViewerContext.isInsideFileViewer || !shellContext) {\n    throw new Error(\"useFileViewerSidebar must be used within FileViewer.\");\n  }\n\n  return shellContext;\n}\n",
      "type": "registry:ui",
      "target": "@ui/file-viewer-context.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/file-viewer-elements.ts",
      "content": "\"use client\";\n\nimport type {\n  FileViewerDocumentSurface,\n  FileViewerMotionKernel,\n} from \"./file-viewer-motion-kernel\";\nimport type { FileViewerMotionFrame } from \"./file-viewer-motion-plan\";\n\nexport const FILE_VIEWER_BEFORE_LAYOUT_MOTION_EVENT =\n  \"file-viewer:before-layout-motion\";\n\n// The kernel dispatches the before-layout-motion event as a CustomEvent whose\n// detail is its LIVE interactive frame, so renderers can capture pre-commit\n// anchors against what is actually on screen (a mid-flight retarget sees the\n// settled layout plus the in-flight transform).\nexport function readFileViewerBeforeLayoutMotionFrame(\n  event: Event,\n): FileViewerMotionFrame | null {\n  if (!(event instanceof CustomEvent)) return null;\n  const detail: unknown = event.detail;\n  return detail != null && typeof detail === \"object\"\n    ? (detail as FileViewerMotionFrame)\n    : null;\n}\n\nexport type FileViewerElements = {\n  documentSurfaceElement: HTMLElement | null;\n  getDocumentSurfaceMotionProbeElement: (() => HTMLElement | null) | null;\n  sidebarElement: HTMLElement | null;\n  sidebarGapElement: HTMLDivElement | null;\n  sidebarTriggerElement: HTMLElement | null;\n  viewerShellElement: HTMLDivElement | null;\n};\n\nexport type FileViewerElementRegistry = {\n  getElements: () => FileViewerElements;\n  registerDocumentSurface: (surface: FileViewerDocumentSurface) => () => void;\n  registerSidebarElement: (element: HTMLElement | null) => void;\n  registerSidebarGapElement: (element: HTMLDivElement | null) => void;\n  registerSidebarTriggerElement: (element: HTMLElement | null) => void;\n  registerViewerShellElement: (element: HTMLDivElement | null) => void;\n};\n\nexport function createFileViewerElementRegistry({\n  motionKernel,\n  onViewerShellElementChange,\n}: {\n  motionKernel: FileViewerMotionKernel;\n  onViewerShellElementChange: (element: HTMLDivElement | null) => void;\n}): FileViewerElementRegistry {\n  const elements: FileViewerElements = {\n    documentSurfaceElement: null,\n    getDocumentSurfaceMotionProbeElement: null,\n    sidebarElement: null,\n    sidebarGapElement: null,\n    sidebarTriggerElement: null,\n    viewerShellElement: null,\n  };\n  let documentSurfaceRegistration = 0;\n\n  return {\n    getElements: () => elements,\n    registerDocumentSurface: (surface) => {\n      documentSurfaceRegistration += 1;\n      const registration = documentSurfaceRegistration;\n      elements.documentSurfaceElement = surface.element;\n      elements.getDocumentSurfaceMotionProbeElement =\n        surface.getMotionProbeElement ?? null;\n      motionKernel.setDocumentSurface(surface);\n      return () => {\n        if (documentSurfaceRegistration !== registration) return;\n        elements.documentSurfaceElement = null;\n        elements.getDocumentSurfaceMotionProbeElement = null;\n        motionKernel.setDocumentSurface(null);\n      };\n    },\n    registerSidebarElement: (element) => {\n      if (elements.sidebarElement === element) return;\n      elements.sidebarElement = element;\n    },\n    registerSidebarGapElement: (element) => {\n      if (elements.sidebarGapElement === element) return;\n      elements.sidebarGapElement = element;\n      motionKernel.setSidebarGapElement(element);\n    },\n    registerSidebarTriggerElement: (element) => {\n      elements.sidebarTriggerElement = element;\n    },\n    registerViewerShellElement: (element) => {\n      if (elements.viewerShellElement === element) return;\n      elements.viewerShellElement = element;\n      onViewerShellElementChange(element);\n    },\n  };\n}\n",
      "type": "registry:ui",
      "target": "@ui/file-viewer-elements.ts"
    },
    {
      "path": "registry/new-york-v4/ui/file-viewer-motion-kernel.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { flushSync } from \"react-dom\";\n\nimport { FILE_VIEWER_BEFORE_LAYOUT_MOTION_EVENT } from \"./file-viewer-elements\";\nimport {\n  easeFileViewerMotion,\n  FILE_VIEWER_MOTION_DURATION_MS,\n  areFileViewerMotionFramesEqual,\n  areFileViewerMotionRestFramesEqual,\n  createFileViewerIdleMotionFrame,\n  createFileViewerMotionPlan,\n  createFileViewerMotionRestFrame,\n  type FileViewerMotionFrame,\n  type FileViewerMotionPlan,\n  type FileViewerMotionRestFrame,\n  type FileViewerMotionTarget,\n} from \"./file-viewer-motion-plan\";\n\n// The kernel is the single owner of sidebar motion. It holds the clock (one\n// rAF loop from slide start through settle), writes the continuous inline\n// styles, and publishes to React subscribers only at phase edges\n// (idle → sliding → settling → idle). Everything discrete — data attributes,\n// inert/aria, the overlay translate classes — is owned by React renders.\n//\n// Commit-then-relax ordering: the first sliding frame is flushed\n// synchronously inside the toggle's own task, so renderers commit their\n// TARGET layout and rebase scroll before anything paints; the per-tick style\n// writes then only relax the counter-transform to identity. Settle removes a\n// no-op transform — it never commits layout, so no flushSync runs inside rAF.\nexport type FileViewerMotionKernel = {\n  getFlightRecords: () => readonly FileViewerMotionFlightRecord[];\n  getInteractiveSnapshot: () => FileViewerMotionFrame;\n  getSnapshot: () => FileViewerMotionFrame;\n  setDocumentSurface: (surface: FileViewerDocumentSurface | null) => void;\n  setSidebarGapElement: (element: HTMLElement | null) => void;\n  startMotion: (target: FileViewerMotionTarget) => void;\n  subscribe: (listener: () => void) => () => void;\n  syncTarget: (target: FileViewerMotionTarget) => void;\n};\n\n// Always-on flight recorder: every motion leaves a bounded trace (per-tick\n// widths, phase edges, settle holds, inter-frame gaps) so a blink report is\n// diagnosable after the fact without re-instrumenting.\nexport type FileViewerMotionFlightRecord = {\n  fromInlineSize: number;\n  id: number;\n  interrupted: boolean;\n  maxTickGapMs: number;\n  open: boolean;\n  settleHoldFrameCount: number;\n  startedAt: number;\n  ticks: FileViewerMotionFlightTick[];\n  toInlineSize: number;\n};\n\nexport type FileViewerMotionFlightTick = {\n  elapsedMs: number;\n  phase: FileViewerMotionFrame[\"phase\"];\n  sidebarInlineSize: number;\n};\n\nconst FILE_VIEWER_FLIGHT_RECORD_LIMIT = 8;\nconst FILE_VIEWER_FLIGHT_TICK_LIMIT = 240;\n\nexport type FileViewerDocumentSurface = {\n  element: HTMLElement;\n  getMotionProbeElement?: (() => HTMLElement | null) | null;\n  readSettleSnapshot?: FileViewerDocumentSurfaceSettleSnapshotReader | null;\n  resolveMotionStyle?: FileViewerDocumentSurfaceMotionResolver | null;\n};\n\nexport type FileViewerDocumentSurfaceSettleSnapshotReader = () =>\n  | readonly number[]\n  | null\n  | undefined;\n\n// Layout reads are quarantined outside the kernel (viewer-measurement / the\n// frame controller layer). The kernel owns time and style writes only, so the\n// settle-hold rect reader is injected by its creator rather than imported.\nexport type FileViewerElementRectSnapshotReader = (\n  element: HTMLElement | null,\n) => readonly number[];\n\nexport type FileViewerMotionKernelOptions = {\n  readElementRectSnapshot?: FileViewerElementRectSnapshotReader | null;\n};\n\nexport type FileViewerDocumentSurfaceMotionStyle = {\n  customProperties?: Readonly<Record<string, string | null>>;\n  transform: string;\n  transformOrigin: string;\n  willChange: string;\n};\n\nexport type FileViewerDocumentSurfaceMotionResolver = (\n  frame: FileViewerMotionFrame,\n) => FileViewerDocumentSurfaceMotionStyle | null;\n\ntype FileViewerActiveMotion = {\n  durationMs: number;\n  from: FileViewerMotionRestFrame;\n  // The clock re-anchors to the first tick's vsync frame time: startedAt is\n  // stamped inside the toggle's task, but the synchronous slide-start commit\n  // can burn 10ms+ before anything paints, and an ease anchored at the click\n  // lands its first painted frame that deep into the curve.\n  hasFrameClockAnchor: boolean;\n  id: number;\n  startedAt: number;\n  to: FileViewerMotionRestFrame;\n};\n\ntype FileViewerSettleRelease = {\n  idleFrame: FileViewerMotionFrame;\n  lastSnapshot: readonly number[];\n  remainingFrameCount: number;\n  settlingFrame: FileViewerMotionFrame;\n  stableFrameCount: number;\n};\n\nconst FILE_VIEWER_SETTLE_SCROLL_EPSILON_PX = 0.25;\nconst FILE_VIEWER_SETTLE_STABLE_FRAME_COUNT = 2;\nconst FILE_VIEWER_SETTLE_MAX_HOLD_FRAMES = 6;\nconst FILE_VIEWER_SUBPIXEL_ENDPOINT_EPSILON_PX = 1;\n\nexport const DEFAULT_FILE_VIEWER_MOTION_FRAME: FileViewerMotionFrame = {\n  shellInlineSize: 0,\n  durationMs: FILE_VIEWER_MOTION_DURATION_MS,\n  fromInlineSize: 0,\n  layoutInlineSize: 0,\n  mode: \"overlay\",\n  motionId: null,\n  motionProgress: 1,\n  open: false,\n  phase: \"idle\",\n  side: \"left\",\n  sidebarInlineSize: 0,\n  sidebarWidth: 0,\n  toInlineSize: 0,\n};\n\nexport function createFileViewerMotionKernel({\n  readElementRectSnapshot = null,\n}: FileViewerMotionKernelOptions = {}): FileViewerMotionKernel {\n  const listeners = new Set<() => void>();\n  let contractFrame = DEFAULT_FILE_VIEWER_MOTION_FRAME;\n  let interactiveFrame = DEFAULT_FILE_VIEWER_MOTION_FRAME;\n  let target: FileViewerMotionTarget = {\n    shellInlineSize: 0,\n    durationMs: DEFAULT_FILE_VIEWER_MOTION_FRAME.durationMs,\n    mode: DEFAULT_FILE_VIEWER_MOTION_FRAME.mode,\n    open: DEFAULT_FILE_VIEWER_MOTION_FRAME.open,\n    side: DEFAULT_FILE_VIEWER_MOTION_FRAME.side,\n    sidebarWidth: 0,\n  };\n  let documentSurface: FileViewerDocumentSurface | null = null;\n  let documentSurfaceCustomProperties = new Set<string>();\n  let sidebarGapElement: HTMLElement | null = null;\n  let activeMotion: FileViewerActiveMotion | null = null;\n  let settleRelease: FileViewerSettleRelease | null = null;\n  let rafHandle = 0;\n  let settleReleaseHandle = 0;\n  let motionSequence = 0;\n  const flightRecords: FileViewerMotionFlightRecord[] = [];\n  let activeFlightRecord: FileViewerMotionFlightRecord | null = null;\n  let lastFlightTickAt = 0;\n\n  const beginFlightRecord = (motion: FileViewerActiveMotion) => {\n    if (activeFlightRecord && activeFlightRecord.id !== motion.id) {\n      activeFlightRecord.interrupted = true;\n    }\n    activeFlightRecord = {\n      fromInlineSize: motion.from.layoutInlineSize,\n      id: motion.id,\n      interrupted: false,\n      maxTickGapMs: 0,\n      open: motion.to.open,\n      settleHoldFrameCount: 0,\n      startedAt: motion.startedAt,\n      ticks: [],\n      toInlineSize: motion.to.layoutInlineSize,\n    };\n    lastFlightTickAt = motion.startedAt;\n    flightRecords.push(activeFlightRecord);\n    if (flightRecords.length > FILE_VIEWER_FLIGHT_RECORD_LIMIT) {\n      flightRecords.splice(\n        0,\n        flightRecords.length - FILE_VIEWER_FLIGHT_RECORD_LIMIT,\n      );\n    }\n  };\n\n  const recordFlightTick = (frame: FileViewerMotionFrame, now = readNow()) => {\n    const record = activeFlightRecord;\n    if (!record || frame.motionId !== record.id) return;\n    record.maxTickGapMs = Math.max(record.maxTickGapMs, now - lastFlightTickAt);\n    lastFlightTickAt = now;\n    if (frame.phase === \"settling\") record.settleHoldFrameCount += 1;\n    if (record.ticks.length >= FILE_VIEWER_FLIGHT_TICK_LIMIT) return;\n    record.ticks.push({\n      elapsedMs: Math.max(0, now - record.startedAt),\n      phase: frame.phase,\n      sidebarInlineSize: frame.sidebarInlineSize,\n    });\n  };\n\n  const notify = () => {\n    for (const listener of listeners) listener();\n  };\n\n  const publishContractFrame = (\n    nextFrame: FileViewerMotionFrame,\n    { flushSubscribers = false }: { flushSubscribers?: boolean } = {},\n  ) => {\n    if (areFileViewerMotionFramesEqual(contractFrame, nextFrame)) return;\n    contractFrame = nextFrame;\n\n    if (flushSubscribers) {\n      flushSync(notify);\n      return;\n    }\n\n    notify();\n  };\n\n  // The gap's inline size and the document surface's counter-scale must land\n  // in the same frame: two independent CSS transitions (width on the gap,\n  // transform on the surface) can desync under main-thread jank, letting the\n  // document edge drift off the sidebar edge mid-slide. The kernel therefore\n  // writes both here, once per tick.\n  const writeElementStyles = (nextFrame: FileViewerMotionFrame) => {\n    writeSidebarGapStyle(nextFrame);\n    writeDocumentSurfaceStyle(nextFrame);\n  };\n\n  const commit = (\n    nextFrame: FileViewerMotionFrame,\n    { publish = true }: { publish?: boolean } = {},\n  ) => {\n    writeElementStyles(nextFrame);\n    interactiveFrame = nextFrame;\n    if (publish) publishContractFrame(nextFrame);\n  };\n\n  const cancelTick = () => {\n    if (rafHandle === 0) return;\n    getCancelAnimationFrame()(rafHandle);\n    rafHandle = 0;\n  };\n\n  const cancelSettleRelease = () => {\n    settleRelease = null;\n    if (settleReleaseHandle === 0) return;\n    getCancelAnimationFrame()(settleReleaseHandle);\n    settleReleaseHandle = 0;\n  };\n\n  const readMotionSample = (\n    motion: FileViewerActiveMotion,\n    now = readNow(),\n  ): FileViewerMotionFrame => {\n    const rawTimeProgress =\n      motion.durationMs <= 0\n        ? 1\n        : clamp((now - motion.startedAt) / motion.durationMs, 0, 1);\n    const rawMotionProgress = easeFileViewerMotion(rawTimeProgress);\n    const rawSidebarInlineSize = lerp(\n      motion.from.sidebarInlineSize,\n      motion.to.sidebarInlineSize,\n      rawMotionProgress,\n    );\n    const isSubpixelEndpoint =\n      rawMotionProgress > 0.98 &&\n      Math.abs(rawSidebarInlineSize - motion.to.sidebarInlineSize) <=\n        FILE_VIEWER_SUBPIXEL_ENDPOINT_EPSILON_PX;\n    const motionProgress = isSubpixelEndpoint ? 1 : rawMotionProgress;\n    const sidebarInlineSize = isSubpixelEndpoint\n      ? motion.to.sidebarInlineSize\n      : rawSidebarInlineSize;\n    const layoutInlineSize = Math.max(\n      0,\n      motion.to.shellInlineSize - sidebarInlineSize,\n    );\n    const fromInlineSize = motion.from.layoutInlineSize;\n\n    return {\n      shellInlineSize: motion.to.shellInlineSize,\n      durationMs: motion.durationMs,\n      fromInlineSize,\n      layoutInlineSize,\n      mode: motion.to.mode,\n      motionId: motion.id,\n      motionProgress,\n      open: motion.to.open,\n      phase: motionProgress < 1 ? \"sliding\" : \"settling\",\n      side: motion.to.side,\n      sidebarInlineSize,\n      sidebarWidth: motion.to.sidebarWidth,\n      toInlineSize: motion.to.layoutInlineSize,\n    };\n  };\n\n  const settle = () => {\n    if (!activeMotion) return;\n    const finishedMotion = activeMotion;\n    activeMotion = null;\n    cancelTick();\n\n    const idleFrame = createFileViewerIdleMotionFrame(finishedMotion.to);\n    const settlingFrame: FileViewerMotionFrame = {\n      ...idleFrame,\n      fromInlineSize: finishedMotion.from.layoutInlineSize,\n      motionId: finishedMotion.id,\n      phase: \"settling\",\n    };\n\n    // Layout and scroll were committed at slide start; settling only clears\n    // the (now identity) counter-transform and holds until shell geometry\n    // stops moving. Nothing here re-renders geometry, so no flushSync in rAF.\n    commit(settlingFrame, { publish: false });\n    recordFlightTick(settlingFrame);\n    publishContractFrame(settlingFrame);\n    scheduleSettleRelease(settlingFrame, idleFrame);\n  };\n\n  // Ticks sample the clock at the rAF FRAME timestamp, never the callback's\n  // execution time: the frame time is the vsync the paint belongs to, and a\n  // callback running late in a janky frame would otherwise write a position\n  // ahead of the frame's own time axis — a real paint-side velocity excess\n  // (the probes' rule 11, applied to the writer). The first tick also\n  // re-anchors startedAt to its frame time, so the ease starts at the first\n  // paintable frame rather than at the click that precedes the slide-start\n  // commit.\n  const tick = (frameTime: number) => {\n    rafHandle = 0;\n    if (!activeMotion) return;\n    const now = Number.isFinite(frameTime) ? frameTime : readNow();\n    if (!activeMotion.hasFrameClockAnchor) {\n      activeMotion.hasFrameClockAnchor = true;\n      activeMotion.startedAt = now;\n    }\n    const sample = readMotionSample(activeMotion, now);\n    if (sample.motionProgress >= 1) {\n      settle();\n      return;\n    }\n    commit(sample, { publish: false });\n    recordFlightTick(sample, now);\n    scheduleTick();\n  };\n\n  const scheduleTick = () => {\n    if (rafHandle !== 0) return;\n    rafHandle = getRequestAnimationFrame()(tick);\n  };\n\n  const scheduleSettleRelease = (\n    settlingFrame: FileViewerMotionFrame,\n    idleFrame: FileViewerMotionFrame,\n  ) => {\n    cancelSettleRelease();\n    settleRelease = {\n      idleFrame,\n      lastSnapshot: readSettleSnapshot(),\n      remainingFrameCount: FILE_VIEWER_SETTLE_MAX_HOLD_FRAMES,\n      settlingFrame,\n      stableFrameCount: 0,\n    };\n    scheduleSettleReleaseFrame();\n  };\n\n  const scheduleSettleReleaseFrame = () => {\n    if (settleReleaseHandle !== 0) return;\n    settleReleaseHandle = getRequestAnimationFrame()(holdSettleRelease);\n  };\n\n  const holdSettleRelease = () => {\n    settleReleaseHandle = 0;\n    if (!settleRelease) return;\n\n    commit(settleRelease.settlingFrame, { publish: false });\n    recordFlightTick(settleRelease.settlingFrame);\n\n    const snapshot = readSettleSnapshot();\n    const stableFrameCount = areSettleSnapshotsEqual(\n      settleRelease.lastSnapshot,\n      snapshot,\n    )\n      ? settleRelease.stableFrameCount + 1\n      : 0;\n    const remainingFrameCount = settleRelease.remainingFrameCount - 1;\n\n    if (\n      stableFrameCount >= FILE_VIEWER_SETTLE_STABLE_FRAME_COUNT ||\n      remainingFrameCount <= 0\n    ) {\n      const idleFrame = settleRelease.idleFrame;\n      settleRelease = null;\n      // Natural completion: close the flight record so the next motion does\n      // not mark this one interrupted.\n      activeFlightRecord = null;\n      commit(idleFrame);\n      return;\n    }\n\n    settleRelease = {\n      ...settleRelease,\n      lastSnapshot: snapshot,\n      remainingFrameCount,\n      stableFrameCount,\n    };\n    scheduleSettleReleaseFrame();\n  };\n\n  // The event carries the kernel's LIVE frame so renderers can capture their\n  // pre-commit anchor against what is actually on screen — during a mid-flight\n  // retarget that is the settled layout PLUS the in-flight transform, not the\n  // settled layout alone.\n  const dispatchBeforeLayoutMotion = (currentFrame: FileViewerMotionFrame) => {\n    documentSurface?.element.dispatchEvent(\n      new CustomEvent<FileViewerMotionFrame>(\n        FILE_VIEWER_BEFORE_LAYOUT_MOTION_EVENT,\n        { detail: currentFrame },\n      ),\n    );\n  };\n\n  const interruptActiveFlightRecord = () => {\n    if (!activeFlightRecord) return;\n    activeFlightRecord.interrupted = true;\n    activeFlightRecord = null;\n  };\n\n  const retarget = (nextTarget: FileViewerMotionTarget, animate: boolean) => {\n    cancelSettleRelease();\n    // Continuity is with what is PAINTED, not with the clock: mid-flight the\n    // screen shows the last tick's commit (`interactiveFrame`), which can be\n    // a frame behind a fresh clock sample. Planning (and the before-motion\n    // capture renderers do off the event detail) from the painted frame keeps\n    // the retarget hand-off pixel-continuous; the new motion simply re-lerps\n    // from the painted geometry.\n    const currentFrame =\n      interactiveFrame.shellInlineSize > 0\n        ? interactiveFrame\n        : createFileViewerIdleMotionFrame(\n            createFileViewerMotionRestFrame(target),\n          );\n    const plan = createFileViewerMotionPlan({\n      animate: animate && !prefersReducedMotion(),\n      currentFrame,\n      nextTarget,\n    });\n    if (shouldDispatchBeforeLayoutMotion(plan)) {\n      dispatchBeforeLayoutMotion(currentFrame);\n    }\n    target = plan.resolvedTarget;\n\n    if (!plan.shouldAnimate) {\n      if (activeMotion) interruptActiveFlightRecord();\n      activeMotion = null;\n      cancelTick();\n      commit(createFileViewerIdleMotionFrame(plan.nextRestFrame));\n      return;\n    }\n\n    motionSequence += 1;\n    activeMotion = {\n      durationMs: plan.resolvedTarget.durationMs,\n      from: { ...plan.currentRestFrame, layoutInlineSize: plan.fromInlineSize },\n      hasFrameClockAnchor: false,\n      id: motionSequence,\n      startedAt: readNow(),\n      to: plan.nextRestFrame,\n    };\n    beginFlightRecord(activeMotion);\n    const startFrame = readMotionSample(activeMotion, activeMotion.startedAt);\n    // Commit the discontinuity while it cannot be seen: flush the first\n    // sliding frame synchronously (inside the toggle's own task) so renderers\n    // lay out at the target width and rebase scroll before first paint, hidden\n    // behind the counter-transform written above in the same task.\n    writeElementStyles(startFrame);\n    interactiveFrame = startFrame;\n    recordFlightTick(startFrame, activeMotion.startedAt);\n    publishContractFrame(startFrame, { flushSubscribers: true });\n    scheduleTick();\n  };\n\n  const syncTarget = (nextTarget: FileViewerMotionTarget) => {\n    const nextRestFrame = createFileViewerMotionRestFrame(nextTarget);\n\n    if (activeMotion) {\n      target = nextTarget;\n      if (areFileViewerMotionRestFramesEqual(activeMotion.to, nextRestFrame)) {\n        return;\n      }\n      // A mode flip mid-motion (breakpoint crossing during the slide) cannot\n      // be animated: React re-renders the new mode immediately, so an inline\n      // slide continuing against overlay DOM (or vice versa) double-moves the\n      // surface. Snap to the new rest geometry instead.\n      if (nextRestFrame.mode !== activeMotion.to.mode) {\n        interruptActiveFlightRecord();\n        activeMotion = null;\n        cancelTick();\n        cancelSettleRelease();\n        commit(createFileViewerIdleMotionFrame(nextRestFrame));\n        return;\n      }\n      retarget(nextTarget, true);\n      return;\n    }\n\n    cancelSettleRelease();\n    target = nextTarget;\n    commit(createFileViewerIdleMotionFrame(nextRestFrame));\n  };\n\n  return {\n    getFlightRecords: () => flightRecords.slice(),\n    getInteractiveSnapshot: () =>\n      activeMotion ? readMotionSample(activeMotion) : interactiveFrame,\n    getSnapshot: () => contractFrame,\n    setDocumentSurface: (surface) => {\n      const previousSurface = documentSurface;\n      if (\n        previousSurface &&\n        (!surface || previousSurface.element !== surface.element)\n      ) {\n        clearDocumentSurfaceStyle(previousSurface.element);\n      }\n      documentSurface = surface;\n      writeDocumentSurfaceStyle(interactiveFrame);\n    },\n    setSidebarGapElement: (element) => {\n      sidebarGapElement = element;\n      writeSidebarGapStyle(interactiveFrame);\n    },\n    startMotion: (nextTarget) => retarget(nextTarget, true),\n    subscribe: (listener) => {\n      listeners.add(listener);\n      return () => {\n        listeners.delete(listener);\n      };\n    },\n    syncTarget,\n  };\n\n  function writeSidebarGapStyle(nextFrame: FileViewerMotionFrame) {\n    if (!sidebarGapElement) return;\n\n    // Overlay motion is CSS-owned; relinquish the gap so its `w-0` class is\n    // the only writer outside inline mode.\n    if (nextFrame.mode !== \"inline\") {\n      sidebarGapElement.style.width = \"\";\n      sidebarGapElement.style.flexBasis = \"\";\n      return;\n    }\n\n    sidebarGapElement.style.width = `${nextFrame.sidebarInlineSize}px`;\n    sidebarGapElement.style.flexBasis = `${nextFrame.sidebarInlineSize}px`;\n  }\n\n  function writeDocumentSurfaceStyle(nextFrame: FileViewerMotionFrame) {\n    if (!documentSurface) return;\n\n    const { element, resolveMotionStyle } = documentSurface;\n    const resolvedStyle = resolveMotionStyle?.(nextFrame);\n    if (resolvedStyle) {\n      writeDocumentSurfaceCustomProperties(\n        element,\n        resolvedStyle.customProperties,\n      );\n      element.style.transform = resolvedStyle.transform;\n      element.style.transformOrigin = resolvedStyle.transformOrigin;\n      element.style.willChange = resolvedStyle.willChange;\n      return;\n    }\n\n    // Default (no motion resolver): identity. Fit-width renderers register\n    // the shared commit-then-relax resolver (file-viewer-fit-width-motion);\n    // a surface without one either tracks the live DOM width on its own or\n    // opts out of shell motion entirely, and must not be transformed here.\n    writeDocumentSurfaceCustomProperties(element, null);\n    element.style.transform = \"\";\n    element.style.transformOrigin = \"\";\n    element.style.willChange = \"\";\n  }\n\n  function writeDocumentSurfaceCustomProperties(\n    element: HTMLElement,\n    customProperties:\n      | Readonly<Record<string, string | null>>\n      | null\n      | undefined,\n  ) {\n    const nextNames = new Set(Object.keys(customProperties ?? {}));\n    for (const name of documentSurfaceCustomProperties) {\n      if (!nextNames.has(name)) {\n        element.style.removeProperty(name);\n      }\n    }\n\n    for (const [name, value] of Object.entries(customProperties ?? {})) {\n      if (value == null) {\n        element.style.removeProperty(name);\n      } else {\n        element.style.setProperty(name, value);\n      }\n    }\n\n    documentSurfaceCustomProperties = nextNames;\n  }\n\n  function clearDocumentSurfaceStyle(element: HTMLElement) {\n    element.style.transform = \"\";\n    element.style.transformOrigin = \"\";\n    element.style.willChange = \"\";\n    for (const name of documentSurfaceCustomProperties) {\n      element.style.removeProperty(name);\n    }\n    documentSurfaceCustomProperties = new Set();\n  }\n\n  function readSettleSnapshot(): readonly number[] {\n    const values: number[] = [];\n\n    appendElementRectSnapshot(values, sidebarGapElement);\n    appendElementRectSnapshot(values, documentSurface?.element ?? null);\n\n    try {\n      const surfaceSnapshot = documentSurface?.readSettleSnapshot?.();\n      if (surfaceSnapshot) {\n        values.push(...surfaceSnapshot.map(toSettleSnapshotNumber));\n      }\n    } catch {\n      // A renderer snapshot is diagnostic, not correctness-critical. If a\n      // renderer unmounts while settling, fall back to shell geometry.\n    }\n\n    return values.length > 0 ? values : [0];\n  }\n\n  function appendElementRectSnapshot(\n    values: number[],\n    element: HTMLElement | null,\n  ) {\n    if (!readElementRectSnapshot || !element) return;\n    for (const value of readElementRectSnapshot(element)) {\n      values.push(toSettleSnapshotNumber(value));\n    }\n  }\n}\n\nfunction areSettleSnapshotsEqual(\n  previous: readonly number[],\n  next: readonly number[],\n) {\n  if (previous.length !== next.length) return false;\n  return previous.every(\n    (value, index) =>\n      Math.abs(value - next[index]) <= FILE_VIEWER_SETTLE_SCROLL_EPSILON_PX,\n  );\n}\n\nfunction toSettleSnapshotNumber(value: number) {\n  return Number.isFinite(value) ? value : 0;\n}\n\nfunction shouldDispatchBeforeLayoutMotion({\n  currentRestFrame,\n  nextRestFrame,\n}: FileViewerMotionPlan) {\n  return (\n    currentRestFrame.mode === \"inline\" &&\n    nextRestFrame.mode === \"inline\" &&\n    Math.abs(\n      currentRestFrame.layoutInlineSize - nextRestFrame.layoutInlineSize,\n    ) > 0.001\n  );\n}\n\nexport function useFileViewerMotionFrame(\n  kernel: FileViewerMotionKernel | null | undefined,\n): FileViewerMotionFrame {\n  const subscribe = React.useCallback(\n    (listener: () => void) => kernel?.subscribe(listener) ?? (() => {}),\n    [kernel],\n  );\n  const getSnapshot = React.useCallback(\n    () => kernel?.getSnapshot() ?? DEFAULT_FILE_VIEWER_MOTION_FRAME,\n    [kernel],\n  );\n\n  return React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\nfunction prefersReducedMotion() {\n  return (\n    typeof matchMedia === \"function\" &&\n    matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n  );\n}\n\nfunction readNow() {\n  return typeof performance !== \"undefined\" &&\n    typeof performance.now === \"function\"\n    ? performance.now()\n    : Date.now();\n}\n\nfunction getRequestAnimationFrame() {\n  return (\n    globalThis.requestAnimationFrame ??\n    ((callback: FrameRequestCallback) =>\n      window.setTimeout(() => callback(readNow()), 16))\n  );\n}\n\nfunction getCancelAnimationFrame() {\n  return (\n    globalThis.cancelAnimationFrame ??\n    ((id: number) => {\n      window.clearTimeout(id);\n    })\n  );\n}\n\nfunction lerp(from: number, to: number, progress: number) {\n  return from + (to - from) * progress;\n}\n\nfunction clamp(value: number, min: number, max: number) {\n  return Math.min(max, Math.max(min, value));\n}\n",
      "type": "registry:ui",
      "target": "@ui/file-viewer-motion-kernel.ts"
    },
    {
      "path": "registry/new-york-v4/ui/file-viewer-renderer-contract.ts",
      "content": "\"use client\";\n\nimport {\n  getFileViewerMotionRasterInlineSize,\n  type FileViewerMotionFrame,\n  type FileViewerMotionPhase,\n} from \"./file-viewer-motion-plan\";\nimport type { ViewerDocumentTransition } from \"./viewer-types\";\n\nexport type FileViewerDocumentAlign = \"start\" | \"center\" | \"end\";\n\n// Physical inline direction of the document frame (computed CSS `direction`).\n// The fit-width motion transform works on the physical X axis, so it needs\n// the direction to model where auto-margin alignment actually puts the stage.\nexport type FileViewerInlineDirection = \"ltr\" | \"rtl\";\n\n// `phase` is the motion clock's state; `documentTransition` is the single\n// spelling of the policies derived from it. Renderers read layout/scroll/\n// visual decisions from the transition, never from duplicated top-level\n// fields. `isTransitioning` is shorthand for `phase === \"sliding\"`.\nexport type FileViewerRendererFrame = {\n  align: FileViewerDocumentAlign;\n  canToggleSidebar: boolean;\n  direction: FileViewerInlineDirection;\n  documentTransition: ViewerDocumentTransition;\n  element: HTMLDivElement | null;\n  fromInlineSize: number | null;\n  isTransitioning: boolean;\n  layoutInlineSize: number | null;\n  motionDurationMs: number;\n  phase: FileViewerMotionPhase;\n  rasterInlineSize: number | null;\n  settledInlineSize: number | null;\n  shellInlineSize: number | null;\n  toInlineSize: number | null;\n  usesShellGeometry: boolean;\n};\n\nexport function resolveFileViewerRendererLayoutInlineSize({\n  fallbackInlineSize,\n  rendererFrame,\n}: {\n  fallbackInlineSize: number | null;\n  rendererFrame: FileViewerRendererFrame;\n}) {\n  const fallbackSize = resolveMeasuredInlineSize(fallbackInlineSize);\n\n  // Commit-then-relax: the renderer lays out at the motion's TARGET width for\n  // the entire motion (layoutPolicy \"target\" from the first sliding frame).\n  // The in-flight visual is the surface motion transform reprojecting that\n  // settled layout, so settle never commits layout.\n  if (\n    rendererFrame.documentTransition.layoutPolicy === \"target\" &&\n    rendererFrame.toInlineSize != null\n  ) {\n    return rendererFrame.toInlineSize;\n  }\n\n  return rendererFrame.layoutInlineSize ?? fallbackSize;\n}\n\nexport function createFileViewerRendererFrame({\n  align,\n  canToggleSidebar,\n  direction = \"ltr\",\n  element,\n  fallbackInlineSize,\n  motionFrame,\n  motionDurationMs,\n  usesShellGeometry,\n}: {\n  align: FileViewerDocumentAlign;\n  canToggleSidebar: boolean;\n  direction?: FileViewerInlineDirection;\n  element: HTMLDivElement | null;\n  fallbackInlineSize: number | null;\n  motionFrame: FileViewerMotionFrame;\n  motionDurationMs: number;\n  usesShellGeometry: boolean;\n}): FileViewerRendererFrame {\n  const measuredInlineSize = resolveMeasuredInlineSize(fallbackInlineSize);\n  const shellInlineSize = usesShellGeometry\n    ? motionFrame.shellInlineSize\n    : null;\n  const layoutInlineSize = usesShellGeometry\n    ? motionFrame.layoutInlineSize\n    : measuredInlineSize;\n  const settledInlineSize = usesShellGeometry\n    ? motionFrame.toInlineSize\n    : measuredInlineSize;\n  const rasterInlineSize = usesShellGeometry\n    ? getFileViewerMotionRasterInlineSize(motionFrame)\n    : layoutInlineSize;\n  const fromInlineSize = usesShellGeometry\n    ? motionFrame.fromInlineSize\n    : settledInlineSize;\n  const toInlineSize = usesShellGeometry\n    ? motionFrame.toInlineSize\n    : settledInlineSize;\n  const documentTransition = createFileViewerRendererTransition({\n    motionFrame,\n    usesShellGeometry,\n  });\n\n  const phase = usesShellGeometry ? motionFrame.phase : \"idle\";\n\n  return {\n    align,\n    canToggleSidebar,\n    direction,\n    documentTransition,\n    element,\n    fromInlineSize,\n    isTransitioning: phase === \"sliding\",\n    layoutInlineSize,\n    motionDurationMs,\n    phase,\n    rasterInlineSize,\n    settledInlineSize: settledInlineSize ?? layoutInlineSize,\n    shellInlineSize,\n    toInlineSize: toInlineSize ?? layoutInlineSize,\n    usesShellGeometry,\n  };\n}\n\nfunction resolveMeasuredInlineSize(inlineSize: number | null | undefined) {\n  return inlineSize != null && Number.isFinite(inlineSize) && inlineSize > 0\n    ? inlineSize\n    : null;\n}\n\nfunction createFileViewerRendererTransition({\n  motionFrame,\n  usesShellGeometry,\n}: {\n  motionFrame: FileViewerMotionFrame;\n  usesShellGeometry: boolean;\n}): ViewerDocumentTransition {\n  if (!usesShellGeometry) {\n    return {\n      layoutPolicy: \"live\",\n      scrollPolicy: \"preserve\",\n      source: \"none\",\n      transitionId: null,\n      visualPolicy: \"none\",\n    };\n  }\n\n  switch (motionFrame.phase) {\n    // Sliding commits the TARGET layout immediately (inside the toggle's own\n    // task, before first paint) and rebases scroll to the reading anchor in\n    // the same commit; the shell transform hides the jump. Settling then has\n    // no layout or scroll work left — it only clears the identity transform.\n    case \"sliding\":\n      return {\n        layoutPolicy: \"target\",\n        scrollPolicy: \"rebase\",\n        source: \"viewer-shell\",\n        transitionId: motionFrame.motionId,\n        visualPolicy: \"shell-transform\",\n      };\n    case \"settling\":\n      return {\n        layoutPolicy: \"target\",\n        scrollPolicy: \"rebase\",\n        source: \"viewer-shell\",\n        transitionId: motionFrame.motionId,\n        visualPolicy: \"shell-transform\",\n      };\n    case \"idle\":\n      return {\n        layoutPolicy: \"live\",\n        scrollPolicy: \"preserve\",\n        source: \"none\",\n        transitionId: null,\n        visualPolicy: \"none\",\n      };\n  }\n}\n",
      "type": "registry:ui",
      "target": "@ui/file-viewer-renderer-contract.ts"
    },
    {
      "path": "registry/new-york-v4/ui/file-viewer-renderer-frame.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n  useOptionalFileViewerShell,\n  useOptionalFileViewerShellStatic,\n  useFileViewerShellStatic,\n} from \"./file-viewer-context\";\nimport type { FileViewerDocumentSurface } from \"./file-viewer-motion-kernel\";\nimport {\n  createFileViewerRendererFrame,\n  type FileViewerDocumentAlign,\n  type FileViewerRendererFrame,\n} from \"./file-viewer-renderer-contract\";\nimport { useFileViewerMotionFrame } from \"./file-viewer-motion-kernel\";\nimport { useViewerInlineDirection } from \"./viewer-measurement\";\n\nexport type FileViewerRendererEnvironment = {\n  registerDocumentSurface: (surface: FileViewerDocumentSurface) => () => void;\n  usesShellGeometry: boolean;\n};\n\nexport type FileViewerDocumentFrameState = {\n  align: FileViewerDocumentAlign;\n  element: HTMLDivElement | null;\n  inlineSize: number | null;\n};\n\nconst FileViewerDocumentFrameContext =\n  React.createContext<FileViewerDocumentFrameState | null>(null);\n\nexport function FileViewerDocumentFrameProvider({\n  children,\n  value,\n}: {\n  children: React.ReactNode;\n  value: FileViewerDocumentFrameState;\n}) {\n  return (\n    <FileViewerDocumentFrameContext.Provider value={value}>\n      {children}\n    </FileViewerDocumentFrameContext.Provider>\n  );\n}\n\nexport function useOptionalFileViewerDocumentFrame(): FileViewerDocumentFrameState | null {\n  return React.useContext(FileViewerDocumentFrameContext);\n}\n\nexport function useOptionalFileViewerRendererEnvironment(): FileViewerRendererEnvironment {\n  const { elementRegistry, usesShellGeometry } =\n    useFileViewerRendererEnvironmentState();\n  const registerDocumentSurface = React.useCallback(\n    (surface: FileViewerDocumentSurface) =>\n      elementRegistry?.registerDocumentSurface(surface) ?? (() => {}),\n    [elementRegistry],\n  );\n\n  return React.useMemo(\n    () => ({\n      registerDocumentSurface,\n      usesShellGeometry,\n    }),\n    [registerDocumentSurface, usesShellGeometry],\n  );\n}\n\nexport type FileViewerSidebarMotion = {\n  /** True when the shell animates the sidebar (inline mode with a toggle). */\n  isMotionManaged: boolean;\n  isSidebarInteractive: boolean;\n  isSidebarOpen: boolean;\n  isSidebarTransitioning: boolean;\n};\n\nexport function useOptionalFileViewerSidebarMotion(): FileViewerSidebarMotion | null {\n  const shell = useOptionalFileViewerShell();\n\n  return React.useMemo(\n    () =>\n      shell\n        ? {\n            isMotionManaged: shell.mode === \"inline\" && shell.canToggleSidebar,\n            isSidebarInteractive: shell.isSidebarInteractive,\n            isSidebarOpen: shell.isSidebarOpen,\n            isSidebarTransitioning: shell.isSidebarTransitioning,\n          }\n        : null,\n    [shell],\n  );\n}\n\nexport function useFileViewerRendererFrame({\n  fallbackInlineSize,\n}: {\n  fallbackInlineSize?: number | null;\n} = {}): FileViewerRendererFrame {\n  useFileViewerShellStatic(\"useFileViewerRendererFrame\");\n  return useResolvedFileViewerRendererFrame({\n    fallbackInlineSize,\n    required: true,\n  });\n}\n\nexport function useOptionalFileViewerRendererFrame({\n  fallbackInlineSize,\n}: {\n  fallbackInlineSize?: number | null;\n} = {}): FileViewerRendererFrame {\n  return useResolvedFileViewerRendererFrame({\n    fallbackInlineSize,\n    required: false,\n  });\n}\n\nfunction useResolvedFileViewerRendererFrame({\n  fallbackInlineSize,\n  required,\n}: {\n  fallbackInlineSize?: number | null;\n  required: boolean;\n}): FileViewerRendererFrame {\n  const { motionFrame, shell, usesShellGeometry } =\n    useFileViewerRendererEnvironmentState();\n  const documentFrame = useOptionalFileViewerDocumentFrame();\n\n  if (required && !documentFrame) {\n    throw new Error(\n      \"useFileViewerRendererFrame must be used within FileViewerInset.\",\n    );\n  }\n\n  const fallbackSize =\n    fallbackInlineSize != null && Number.isFinite(fallbackInlineSize)\n      ? fallbackInlineSize\n      : null;\n\n  // The fit-width motion transform is a physical-X computation, so renderers\n  // need the frame's computed CSS `direction` alongside its logical align.\n  const direction = useViewerInlineDirection(documentFrame?.element ?? null);\n\n  return React.useMemo(\n    () =>\n      createFileViewerRendererFrame({\n        align: documentFrame?.align ?? \"center\",\n        canToggleSidebar: shell?.canToggleSidebar ?? false,\n        direction,\n        element: documentFrame?.element ?? null,\n        fallbackInlineSize: documentFrame?.inlineSize ?? fallbackSize,\n        motionFrame,\n        motionDurationMs: shell?.motionDurationMs ?? 0,\n        usesShellGeometry,\n      }),\n    [\n      direction,\n      documentFrame?.align,\n      documentFrame?.element,\n      documentFrame?.inlineSize,\n      fallbackSize,\n      shell?.canToggleSidebar,\n      motionFrame,\n      shell?.motionDurationMs,\n      usesShellGeometry,\n    ],\n  );\n}\n\nfunction useFileViewerRendererEnvironmentState() {\n  const shell = useOptionalFileViewerShellStatic();\n  const motionFrame = useFileViewerMotionFrame(shell?.motionKernel);\n  const usesShellGeometry = Boolean(\n    shell &&\n      motionFrame.shellInlineSize > 0 &&\n      shell.mode === \"inline\" &&\n      (shell.canToggleSidebar || shell.collapsible === \"none\"),\n  );\n\n  return React.useMemo(\n    () => ({\n      elementRegistry: shell?.elementRegistry,\n      motionFrame,\n      shell,\n      usesShellGeometry,\n    }),\n    [motionFrame, shell, usesShellGeometry],\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/file-viewer-renderer-frame.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/file-viewer-motion-plan.ts",
      "content": "\"use client\";\n\nimport type {\n  FileViewerSidebarMode,\n  FileViewerSidebarSide,\n} from \"./file-viewer-context\";\n\nexport const FILE_VIEWER_MOTION_EPSILON = 0.5;\n\n// The one duration for every sidebar motion timeline — the kernel clock, the\n// motion targets, and the overlay panel's CSS transition all read this value.\nexport const FILE_VIEWER_MOTION_DURATION_MS = 150;\n\n// The one easing for every sidebar motion timeline. Cubic ease-out: the slide\n// decelerates into rest. Linear progress ends at full velocity, and content\n// far from the reading anchor (the bottom of a tall fit-width document)\n// travels several pixels per millisecond straight into a hard stop — a\n// visible jolt the anchor line never shows.\nexport function easeFileViewerMotion(timeProgress: number) {\n  return 1 - (1 - timeProgress) ** 3;\n}\n\nexport type FileViewerMotionPhase = \"idle\" | \"sliding\" | \"settling\";\n\nexport type FileViewerMotionTarget = {\n  shellInlineSize: number;\n  durationMs: number;\n  mode: FileViewerSidebarMode;\n  open: boolean;\n  side: FileViewerSidebarSide;\n  sidebarWidth: number;\n};\n\nexport type FileViewerMotionFrame = {\n  shellInlineSize: number;\n  durationMs: number;\n  fromInlineSize: number;\n  layoutInlineSize: number;\n  mode: FileViewerSidebarMode;\n  motionId: number | null;\n  motionProgress: number;\n  open: boolean;\n  phase: FileViewerMotionPhase;\n  side: FileViewerSidebarSide;\n  sidebarInlineSize: number;\n  sidebarWidth: number;\n  toInlineSize: number;\n};\n\nexport type FileViewerMotionRestFrame = Pick<\n  FileViewerMotionFrame,\n  | \"shellInlineSize\"\n  | \"durationMs\"\n  | \"layoutInlineSize\"\n  | \"mode\"\n  | \"open\"\n  | \"side\"\n  | \"sidebarInlineSize\"\n  | \"sidebarWidth\"\n>;\n\nexport type FileViewerMotionPlan = {\n  currentRestFrame: FileViewerMotionRestFrame;\n  fromInlineSize: number;\n  nextRestFrame: FileViewerMotionRestFrame;\n  resolvedTarget: FileViewerMotionTarget;\n  shouldAnimate: boolean;\n};\n\nexport function createFileViewerMotionRestFrame(\n  target: FileViewerMotionTarget,\n): FileViewerMotionRestFrame {\n  const shellInlineSize = target.shellInlineSize;\n  const sidebarInlineSize =\n    target.mode === \"inline\" && target.open\n      ? Math.min(target.sidebarWidth, shellInlineSize)\n      : 0;\n\n  return {\n    shellInlineSize,\n    durationMs: target.durationMs,\n    layoutInlineSize: Math.max(0, shellInlineSize - sidebarInlineSize),\n    mode: target.mode,\n    open: target.open,\n    side: target.side,\n    sidebarInlineSize,\n    sidebarWidth: target.sidebarWidth,\n  };\n}\n\nexport function createFileViewerIdleMotionFrame(\n  restFrame: FileViewerMotionRestFrame,\n): FileViewerMotionFrame {\n  return {\n    ...restFrame,\n    fromInlineSize: restFrame.layoutInlineSize,\n    motionId: null,\n    motionProgress: 1,\n    phase: \"idle\",\n    toInlineSize: restFrame.layoutInlineSize,\n  };\n}\n\nexport function getFileViewerMotionRasterInlineSize(\n  frame: Pick<\n    FileViewerMotionFrame,\n    \"fromInlineSize\" | \"layoutInlineSize\" | \"toInlineSize\"\n  >,\n): number {\n  return Math.max(\n    frame.fromInlineSize,\n    frame.toInlineSize,\n    frame.layoutInlineSize,\n  );\n}\n\nexport function createFileViewerMotionPlan({\n  animate,\n  currentFrame,\n  nextTarget,\n}: {\n  animate: boolean;\n  currentFrame: FileViewerMotionFrame;\n  nextTarget: FileViewerMotionTarget;\n}): FileViewerMotionPlan {\n  const resolvedTarget = resolveFileViewerMotionTarget({\n    currentFrame,\n    nextTarget,\n  });\n  const currentRestFrame = pickFileViewerMotionRestFrame(currentFrame);\n  const nextRestFrame = createFileViewerMotionRestFrame(resolvedTarget);\n  // The motion's visual origin is what is on screen RIGHT NOW: for a fresh\n  // motion that is the rest layout; for a mid-flight retarget it is the live\n  // interpolated width, so the new motion's first frame (and every renderer's\n  // anchor solve) continues from the picture the reader is looking at rather\n  // than the interrupted motion's origin.\n  const fromInlineSize =\n    currentFrame.phase === \"sliding\"\n      ? currentFrame.layoutInlineSize\n      : currentRestFrame.layoutInlineSize;\n  const shouldAnimate =\n    animate &&\n    resolvedTarget.mode === \"inline\" &&\n    currentFrame.shellInlineSize > 0 &&\n    Math.abs(\n      currentRestFrame.sidebarInlineSize - nextRestFrame.sidebarInlineSize,\n    ) > FILE_VIEWER_MOTION_EPSILON &&\n    resolvedTarget.durationMs > 0;\n\n  return {\n    currentRestFrame,\n    fromInlineSize,\n    nextRestFrame,\n    resolvedTarget,\n    shouldAnimate,\n  };\n}\n\nexport function areFileViewerMotionRestFramesEqual(\n  previous: FileViewerMotionRestFrame,\n  next: FileViewerMotionRestFrame,\n) {\n  return (\n    areFileViewerMotionNumbersEqual(\n      previous.shellInlineSize,\n      next.shellInlineSize,\n    ) &&\n    previous.durationMs === next.durationMs &&\n    areFileViewerMotionNumbersEqual(\n      previous.layoutInlineSize,\n      next.layoutInlineSize,\n    ) &&\n    previous.mode === next.mode &&\n    previous.open === next.open &&\n    previous.side === next.side &&\n    areFileViewerMotionNumbersEqual(\n      previous.sidebarInlineSize,\n      next.sidebarInlineSize,\n    ) &&\n    areFileViewerMotionNumbersEqual(previous.sidebarWidth, next.sidebarWidth)\n  );\n}\n\nexport function areFileViewerMotionFramesEqual(\n  previous: FileViewerMotionFrame,\n  next: FileViewerMotionFrame,\n) {\n  return (\n    areFileViewerMotionRestFramesEqual(previous, next) &&\n    previous.motionId === next.motionId &&\n    areFileViewerMotionNumbersEqual(\n      previous.motionProgress,\n      next.motionProgress,\n    ) &&\n    previous.phase === next.phase &&\n    areFileViewerMotionNumbersEqual(\n      previous.fromInlineSize,\n      next.fromInlineSize,\n    ) &&\n    areFileViewerMotionNumbersEqual(previous.toInlineSize, next.toInlineSize)\n  );\n}\n\nfunction pickFileViewerMotionRestFrame(\n  frame: FileViewerMotionFrame,\n): FileViewerMotionRestFrame {\n  return {\n    shellInlineSize: frame.shellInlineSize,\n    durationMs: frame.durationMs,\n    layoutInlineSize: frame.layoutInlineSize,\n    mode: frame.mode,\n    open: frame.open,\n    side: frame.side,\n    sidebarInlineSize: frame.sidebarInlineSize,\n    sidebarWidth: frame.sidebarWidth,\n  };\n}\n\nfunction resolveFileViewerMotionTarget({\n  currentFrame,\n  nextTarget,\n}: {\n  currentFrame: FileViewerMotionFrame;\n  nextTarget: FileViewerMotionTarget;\n}): FileViewerMotionTarget {\n  if (\n    nextTarget.mode !== \"overlay\" ||\n    currentFrame.mode !== \"inline\" ||\n    currentFrame.shellInlineSize <= 0\n  ) {\n    return nextTarget;\n  }\n\n  return {\n    ...nextTarget,\n    shellInlineSize:\n      nextTarget.shellInlineSize > 0\n        ? nextTarget.shellInlineSize\n        : currentFrame.shellInlineSize,\n    mode: \"inline\",\n  };\n}\n\nfunction areFileViewerMotionNumbersEqual(previous: number, next: number) {\n  return Math.abs(previous - next) <= 0.001;\n}\n",
      "type": "registry:ui",
      "target": "@ui/file-viewer-motion-plan.ts"
    },
    {
      "path": "registry/new-york-v4/ui/markdown-greenfield-renderer-frame.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { useKeyedLayoutEffect } from \"@/hooks/use-keyed-layout-effect\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nimport { FILE_VIEWER_BEFORE_LAYOUT_MOTION_EVENT } from \"./file-viewer-elements\";\nimport { createFileViewerAlignTranslateSurfaceMotionResolver } from \"./file-viewer-fit-width-motion\";\nimport {\n  resolveFileViewerRendererLayoutInlineSize,\n  type FileViewerDocumentAlign,\n} from \"./file-viewer-renderer-contract\";\nimport {\n  useOptionalFileViewerRendererEnvironment,\n  useOptionalFileViewerRendererFrame,\n} from \"./file-viewer-renderer-frame\";\nimport { MARKDOWN_GREENFIELD_CHUNK_MAX_INLINE_SIZE } from \"./markdown-greenfield-layout\";\n\nexport type MarkdownGreenfieldRendererFrame = {\n  setDocumentSurfaceElement: React.RefCallback<HTMLDivElement>;\n  transformOrigin: string;\n  usesShellGeometry: boolean;\n  viewportInlineSize: number;\n};\n\nexport function useMarkdownGreenfieldRendererFrame({\n  fallbackViewportInlineSize,\n  onBeforeLayoutMotion,\n}: {\n  fallbackViewportInlineSize: number;\n  onBeforeLayoutMotion: () => void;\n}): MarkdownGreenfieldRendererFrame {\n  const { registerDocumentSurface, usesShellGeometry } =\n    useOptionalFileViewerRendererEnvironment();\n  const rendererFrame = useOptionalFileViewerRendererFrame({\n    fallbackInlineSize: fallbackViewportInlineSize,\n  });\n  const viewportInlineSize =\n    resolveFileViewerRendererLayoutInlineSize({\n      fallbackInlineSize: fallbackViewportInlineSize,\n      rendererFrame,\n    }) ?? fallbackViewportInlineSize;\n  const surfaceRef = React.useRef<HTMLDivElement | null>(null);\n  const [surfaceElement, setSurfaceElement] =\n    React.useState<HTMLDivElement | null>(null);\n  const handleBeforeLayoutMotion = React.useCallback(() => {\n    onBeforeLayoutMotion();\n  }, [onBeforeLayoutMotion]);\n  const setDocumentSurfaceElement = React.useCallback(\n    (element: HTMLDivElement | null) => {\n      const previousElement = surfaceRef.current;\n      if (previousElement === element) return;\n      previousElement?.removeEventListener(\n        FILE_VIEWER_BEFORE_LAYOUT_MOTION_EVENT,\n        handleBeforeLayoutMotion,\n      );\n      surfaceRef.current = element;\n      setSurfaceElement((previous) =>\n        previous === element ? previous : element,\n      );\n      if (!element) return;\n      element.addEventListener(\n        FILE_VIEWER_BEFORE_LAYOUT_MOTION_EVENT,\n        handleBeforeLayoutMotion,\n      );\n    },\n    [handleBeforeLayoutMotion],\n  );\n  // Commit-then-relax's relax half. The canvas commits the motion's TARGET\n  // width via minWidth from the first sliding frame, so on a widening pane\n  // the centered chunks would recenter synchronously with the click; the\n  // resolver translates them back to the live width's align margin and eases\n  // to identity. The chunk column is CLAMPED (max inline size), not\n  // fit-width — its content width does not scale with the pane — so the\n  // reprojection is translate-only, never a scale.\n  //\n  // The align is the CHUNK COLUMN's, not rendererFrame.align: the shell\n  // declares the document frame \"start\" (inert — the frame is w-full), while\n  // the column centers itself inside the canvas by its own markup (left-1/2\n  // -translate-x-1/2, physical and direction-independent). Deriving from the\n  // shell align here made the resolver a silent no-op and the close-leg snap\n  // survived it.\n  const resolveSurfaceMotionStyle = React.useMemo(\n    () =>\n      createFileViewerAlignTranslateSurfaceMotionResolver({\n        align: \"center\",\n        direction: rendererFrame.direction,\n        maxStageInlineSize: MARKDOWN_GREENFIELD_CHUNK_MAX_INLINE_SIZE,\n      }),\n    [rendererFrame.direction],\n  );\n  const documentSurfaceKey = surfaceElement\n    ? joinEffectKey([\n        \"markdown-document-surface\",\n        surfaceElement,\n        registerDocumentSurface,\n        resolveSurfaceMotionStyle,\n      ])\n    : null;\n  useKeyedLayoutEffect(documentSurfaceKey, () => {\n    if (!surfaceElement) return;\n    return registerDocumentSurface({\n      element: surfaceElement,\n      resolveMotionStyle: resolveSurfaceMotionStyle,\n    });\n  });\n\n  return React.useMemo(\n    () => ({\n      setDocumentSurfaceElement,\n      transformOrigin: getMarkdownGreenfieldDocumentTransformOrigin(\n        rendererFrame.align,\n      ),\n      usesShellGeometry,\n      viewportInlineSize,\n    }),\n    [\n      rendererFrame.align,\n      setDocumentSurfaceElement,\n      usesShellGeometry,\n      viewportInlineSize,\n    ],\n  );\n}\n\nfunction getMarkdownGreenfieldDocumentTransformOrigin(\n  align: FileViewerDocumentAlign,\n) {\n  switch (align) {\n    case \"start\":\n      return \"left top\";\n    case \"end\":\n      return \"right top\";\n    case \"center\":\n      return \"center top\";\n  }\n}\n",
      "type": "registry:ui",
      "target": "@ui/markdown-greenfield-renderer-frame.ts"
    },
    {
      "path": "registry/new-york-v4/ui/viewer-types.ts",
      "content": "import type * as React from \"react\";\n\nexport type ViewerSidebarMode = \"inline\" | \"overlay\";\nexport type ViewerSidebarRequestedMode = \"auto\" | ViewerSidebarMode;\nexport type ViewerSidebarGapTransition = \"width\" | \"none\";\nexport type ViewerSidebarState = \"expanded\" | \"collapsed\";\nexport type ViewerSidebarSide = \"left\" | \"right\";\nexport type ViewerSidebarCollapsible = \"offcanvas\" | \"none\";\nexport type ViewerDocumentFrameAlign = \"start\" | \"center\" | \"end\";\nexport type ViewerGeometryTransitionPhase = \"idle\" | \"sliding\";\n\nexport type ViewerDocumentReadingAnchorInput = {\n  scrollTop: number;\n  viewportBlockSize: number;\n};\n\nexport type ViewerDocumentReadingAnchorTarget<Anchor> = {\n  anchor: Anchor;\n  viewportBlockSize: number;\n};\n\nexport type ViewerDocumentTransitionSource =\n  | \"none\"\n  | \"viewer-shell\"\n  | \"document-layout\";\n\n// Commit-then-relax: layout commits its target inside the motion's first\n// frame and scroll rebases in the same commit, so there is no frozen layout\n// and no deferred scroll left in the vocabulary.\nexport type ViewerDocumentLayoutPolicy = \"live\" | \"target\";\nexport type ViewerDocumentScrollPolicy = \"preserve\" | \"rebase\";\nexport type ViewerDocumentVisualPolicy =\n  | \"none\"\n  | \"document-flip\"\n  | \"shell-transform\";\n\nexport type ViewerDocumentTransition = {\n  layoutPolicy: ViewerDocumentLayoutPolicy;\n  scrollPolicy: ViewerDocumentScrollPolicy;\n  source: ViewerDocumentTransitionSource;\n  transitionId: number | string | null;\n  visualPolicy: ViewerDocumentVisualPolicy;\n};\n\nexport type ViewerDocumentLayoutModel<Anchor> = {\n  blockSize: number;\n  captureReadingAnchor: (\n    input: ViewerDocumentReadingAnchorInput,\n  ) => Anchor | null;\n  getReadingAnchorScrollTop: (\n    target: ViewerDocumentReadingAnchorTarget<Anchor>,\n  ) => number | null;\n  inlineSize: number;\n  isTransitioning?: boolean;\n  transition?: ViewerDocumentTransition;\n};\n\nexport type ViewerDocumentPhysicalScrollPosition = {\n  physicalScrollTop: number;\n  scrollPageOffset: number;\n};\n\nexport type ViewerDocumentResolvedScrollTarget = {\n  left?: number;\n  top: number;\n};\n\nexport type ViewerDocumentScrollMapper = {\n  getLogicalScrollTop: (input: {\n    blockSize: number;\n    physicalScrollTop: number;\n    scrollPageOffset: number;\n    viewportBlockSize: number;\n  }) => number;\n  getPhysicalScrollSize: (input: {\n    blockSize: number;\n    viewportBlockSize: number;\n  }) => number;\n  resolvePhysicalScrollPosition: (input: {\n    blockSize: number;\n    logicalScrollTop: number;\n    scrollPageOffset: number;\n    viewportBlockSize: number;\n  }) => ViewerDocumentPhysicalScrollPosition;\n};\n\nexport type ViewerDocumentScrollMetrics = {\n  physicalScrollSize: number;\n  physicalScrollTop: number;\n  scrollPageOffset: number;\n  scrollTop: number;\n  viewportBlockSize: number;\n};\n\nexport type ViewerDocumentScrollTargetResolver<Anchor, Target> = (input: {\n  layout: ViewerDocumentLayoutModel<Anchor>;\n  scrollTop: number;\n  target: Target;\n  viewportElement: HTMLDivElement;\n}) => ViewerDocumentResolvedScrollTarget | null;\n\n// A zoom step is the one geometry change whose intent is \"zoom the camera\",\n// not \"keep my reading position\": it re-anchors the viewport CENTER on both\n// axes and relaxes a FLIP about that fixed point. `capture` runs in the zoom\n// gesture's own task against the pre-zoom layout and painted DOM;\n// `resolveScrollTarget` and `play` run inside the geometry commit against the\n// post-zoom layout (commit-then-relax).\nexport type ViewerDocumentZoomMotionBypassReason =\n  | \"resolve-failed\"\n  | \"shell-transition\"\n  | \"stale-intent\";\n\nexport type ViewerDocumentZoomMotionController<Transaction = unknown> = {\n  capture: (input: {\n    scrollTop: number;\n    viewportElement: HTMLDivElement;\n  }) => Transaction | null;\n  /**\n   * Telemetry tap: a captured zoom intent reached a geometry commit but the\n   * zoom lane declined it. Without this the bypass is invisible — the commit\n   * falls back to the reading-anchor restore and no flight is recorded.\n   */\n  noteBypass?: (reason: ViewerDocumentZoomMotionBypassReason) => void;\n  resolveScrollTarget: (input: {\n    transaction: Transaction;\n    viewportElement: HTMLDivElement;\n  }) => ViewerDocumentResolvedScrollTarget | null;\n  play: (input: {\n    transaction: Transaction;\n    viewportElement: HTMLDivElement;\n  }) => (() => void) | null;\n};\n\nexport type ViewerGeometrySnapshot = {\n  bodyInlineSize: number;\n  documentInlineSize: number;\n  hasMeasuredBody: boolean;\n  isTransitioning: boolean;\n  mode: ViewerSidebarMode;\n  open: boolean;\n  progress: number;\n  sidebarGapTransition: ViewerSidebarGapTransition;\n  sidebarInlineSize: number;\n  sidebarWidth: number;\n  side: ViewerSidebarSide;\n  state: ViewerSidebarState;\n  transitionPhase: ViewerGeometryTransitionPhase;\n};\n\nexport type ViewerGeometryStore = {\n  getSnapshot: () => ViewerGeometrySnapshot;\n  setTarget: (target: ViewerGeometryTarget) => void;\n  subscribe: (listener: () => void) => () => void;\n};\n\nexport type ViewerGeometryTarget = {\n  bodyElement: HTMLElement | null;\n  mode: ViewerSidebarMode;\n  open: boolean;\n  rootElement: HTMLElement | null;\n  sidebarElement: HTMLElement | null;\n  sidebarGapTransition: ViewerSidebarGapTransition;\n  sidebarWidth: number;\n  side: ViewerSidebarSide;\n  state: ViewerSidebarState;\n};\n\nexport type ViewerSidebarStateValue = {\n  state: ViewerSidebarState;\n  open: boolean;\n  setOpen: (value: boolean | ((open: boolean) => boolean)) => void;\n  toggleSidebar: () => void;\n  canToggleSidebar: boolean;\n  mode: ViewerSidebarMode;\n  side: ViewerSidebarSide;\n};\n\nexport type ViewerRootProps = React.ComponentProps<\"div\"> & {\n  defaultOpen?: boolean;\n  inlineBreakpoint?: number;\n  mode?: ViewerSidebarRequestedMode;\n  onOpenChange?: (open: boolean) => void;\n  open?: boolean;\n  sidebarCollapsible?: ViewerSidebarCollapsible;\n  sidebarGapTransition?: ViewerSidebarGapTransition;\n  sidebarSide?: ViewerSidebarSide;\n  stateNamespace?: ViewerStateAttributeNamespace;\n};\n\nexport type ViewerFrameProps = React.ComponentProps<\"div\">;\nexport type ViewerHeaderProps = React.ComponentProps<\"div\">;\nexport type ViewerBodyProps = React.ComponentProps<\"div\">;\nexport type ViewerSurfaceProps = React.ComponentProps<\"div\">;\nexport type ViewerViewportProps = React.ComponentProps<\"div\">;\nexport type ViewerDocumentFrameProps = React.ComponentProps<\"div\"> & {\n  align?: ViewerDocumentFrameAlign;\n  maxInlineSize?: React.CSSProperties[\"maxInlineSize\"];\n};\n\nexport type ViewerStateAttributeNamespace = {\n  prefix: string;\n  slots?: {\n    body?: boolean;\n    root?: boolean;\n    sidebar?: boolean;\n  };\n};\n\nexport type ViewerSidebarRegistration = {\n  collapsible: ViewerSidebarCollapsible;\n  element: HTMLElement;\n  id: string;\n  instanceId: string;\n  side: ViewerSidebarSide;\n  width: string;\n  widthPixels: number;\n};\n\nexport type ViewerPortalContainmentAttributes = {\n  \"data-viewer-portal-root-id\": string;\n};\n\nexport type ViewerSidebarRegistrationState = {\n  defaultSidebarCollapsible: ViewerSidebarCollapsible;\n  defaultSidebarSide: ViewerSidebarSide;\n  geometryStore: ViewerGeometryStore;\n  getRootElement: () => HTMLElement | null;\n  hasSidebar: boolean;\n  registerBody: (element: HTMLElement) => () => void;\n  registerSidebar: (registration: ViewerSidebarRegistration) => () => void;\n  rootId: string;\n  sidebarId: string;\n  sidebarGapTransition: ViewerSidebarGapTransition;\n  sidebarSide: ViewerSidebarSide;\n  setLastTriggerElement: (element: HTMLElement | null) => void;\n  stateNamespace?: ViewerStateAttributeNamespace;\n};\n\nexport type ViewerRootDiagnostics = {\n  getRootElement: () => HTMLElement | null;\n  layoutSignature: string;\n  rootId: string;\n};\n\nexport type ViewerSurfaceMeasurement = {\n  hasMeasured: boolean;\n  setViewportElement: React.RefCallback<HTMLDivElement>;\n  viewportElement: HTMLDivElement | null;\n  viewportHeight: number | null;\n  viewportWidth: number | null;\n};\n\nexport type ViewerSidebarSlotNames = {\n  container?: string;\n  gap?: string;\n  inner?: string;\n};\n\nexport type ViewerStateAttributeSlot = \"body\" | \"root\" | \"sidebar\";\nexport type ViewerStateAttributeValues = {\n  hasSidebar?: boolean;\n  sidebarCollapsible?: ViewerSidebarCollapsible;\n  sidebarMode?: ViewerSidebarMode;\n  sidebarOpen?: boolean;\n  sidebarSide?: ViewerSidebarSide;\n  sidebarState?: ViewerSidebarState;\n};\nexport type ViewerDataAttributes = Record<`data-${string}`, string | undefined>;\n\nexport type ViewerSidebarProps = React.ComponentProps<\"aside\"> &\n  ViewerDataAttributes & {\n    side?: ViewerSidebarSide;\n    collapsible?: ViewerSidebarCollapsible;\n    innerClassName?: string;\n    namespacedSlot?: string;\n    namespacedSlotNames?: ViewerSidebarSlotNames;\n    slotNames?: ViewerSidebarSlotNames;\n    width?: string;\n  };\n",
      "type": "registry:ui",
      "target": "@ui/viewer-types.ts"
    },
    {
      "path": "registry/new-york-v4/ui/viewer-measurement.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { useKeyedLayoutEffect } from \"@/hooks/use-keyed-layout-effect\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nexport type StableElementSize<Element extends HTMLElement = HTMLElement> = {\n  element: Element | null;\n  hasMeasured: boolean;\n  height: number | null;\n  setElement: React.RefCallback<Element>;\n  width: number | null;\n};\n\nexport type StableElementSizeOptions = {\n  enabled?: boolean;\n  observe?: boolean;\n  retainLastNonZero?: boolean;\n};\n\nexport type StableCssLengthOptions = {\n  element: HTMLElement | null;\n  retainLastNonZero?: boolean;\n  value: string;\n};\n\ntype MeasuredSize = {\n  height: number | null;\n  width: number | null;\n};\n\ntype RawMeasuredSize = {\n  height: number;\n  width: number;\n};\n\n// DOM layout reads for viewer chrome are quarantined in this module. The\n// file-viewer motion kernel (time + style writes only) receives this reader by\n// injection from the frame controller instead of touching layout APIs itself.\nexport function readElementRectSnapshot(\n  element: HTMLElement | null,\n): readonly number[] {\n  if (!element) return [];\n  const rect = element.getBoundingClientRect();\n  return [rect.left, rect.top, rect.width, rect.height];\n}\n\n// Computed CSS inline direction of an element, sampled when it attaches (a\n// runtime `dir` flip is picked up on the next mount). The fit-width motion\n// transform works on the physical X axis, so the renderer frame needs to\n// know which edge auto-margin alignment pins the stage to.\nexport function useViewerInlineDirection(\n  element: HTMLElement | null,\n): \"ltr\" | \"rtl\" {\n  const [direction, setDirection] = React.useState<\"ltr\" | \"rtl\">(\"ltr\");\n\n  useKeyedLayoutEffect(element ? joinEffectKey([element]) : null, () => {\n    if (!element) return;\n    setDirection(getComputedStyle(element).direction === \"rtl\" ? \"rtl\" : \"ltr\");\n  });\n\n  return direction;\n}\n\nfunction readElementSize(element: HTMLElement): RawMeasuredSize {\n  const rect =\n    typeof element.getBoundingClientRect === \"function\"\n      ? element.getBoundingClientRect()\n      : null;\n\n  return {\n    height: rect?.height || element.clientHeight,\n    width: rect?.width || element.clientWidth,\n  };\n}\n\nfunction resolveMeasuredElementSize({\n  currentSize,\n  nextSize,\n  retainLastNonZero,\n}: {\n  currentSize: MeasuredSize;\n  nextSize: RawMeasuredSize;\n  retainLastNonZero: boolean;\n}): MeasuredSize {\n  const width =\n    Number.isFinite(nextSize.width) &&\n    (!retainLastNonZero || nextSize.width > 0)\n      ? nextSize.width\n      : currentSize.width;\n  const height =\n    Number.isFinite(nextSize.height) &&\n    (!retainLastNonZero || nextSize.height > 0)\n      ? nextSize.height\n      : currentSize.height;\n\n  if (currentSize.width === width && currentSize.height === height) {\n    return currentSize;\n  }\n\n  return { height, width };\n}\n\nexport function useStableElementSize<Element extends HTMLElement = HTMLElement>(\n  options: StableElementSizeOptions = {},\n): StableElementSize<Element> {\n  const enabled = options.enabled ?? true;\n  const observe = options.observe ?? true;\n  const retainLastNonZero = options.retainLastNonZero ?? false;\n  const [element, setElementState] = React.useState<Element | null>(null);\n  const [size, setSize] = React.useState<MeasuredSize>({\n    height: null,\n    width: null,\n  });\n  const hasMeasured = size.height !== null || size.width !== null;\n\n  const setElement = React.useCallback((nextElement: Element | null) => {\n    setElementState(nextElement);\n  }, []);\n\n  useKeyedLayoutEffect(enabled ? null : \"reset\", () => {\n    setSize({ height: null, width: null });\n  });\n\n  useKeyedLayoutEffect(\n    enabled && element\n      ? joinEffectKey([element, observe, retainLastNonZero])\n      : null,\n    () => {\n      if (!element) return;\n\n      setSize((currentSize) =>\n        resolveMeasuredElementSize({\n          currentSize,\n          nextSize: readElementSize(element),\n          retainLastNonZero,\n        }),\n      );\n\n      const ResizeObserverConstructor = observe\n        ? globalThis.ResizeObserver\n        : undefined;\n      if (typeof ResizeObserverConstructor === \"undefined\") return;\n\n      let frame = 0;\n      let latestSize = readElementSize(element);\n      const observer = new ResizeObserverConstructor((entries) => {\n        for (const entry of entries) {\n          latestSize = readElementSize(entry.target as HTMLElement);\n        }\n\n        if (frame) return;\n        frame = requestAnimationFrame(() => {\n          frame = 0;\n          setSize((currentSize) =>\n            resolveMeasuredElementSize({\n              currentSize,\n              nextSize: latestSize,\n              retainLastNonZero,\n            }),\n          );\n        });\n      });\n\n      observer.observe(element);\n\n      return () => {\n        if (frame) cancelAnimationFrame(frame);\n        observer.disconnect();\n      };\n    },\n  );\n\n  return React.useMemo(\n    () => ({\n      element,\n      hasMeasured,\n      height: size.height,\n      setElement,\n      width: size.width,\n    }),\n    [element, hasMeasured, setElement, size.height, size.width],\n  );\n}\n\nexport function useStableCssLength({\n  element,\n  retainLastNonZero = true,\n  value,\n}: StableCssLengthOptions) {\n  const [resolvedLength, setResolvedLength] = React.useState(0);\n\n  useKeyedLayoutEffect(\n    value ? joinEffectKey([element, retainLastNonZero, value]) : null,\n    () => {\n      const nextLength = resolveCssLength(value, element);\n\n      setResolvedLength((currentLength) => {\n        if (retainLastNonZero && nextLength <= 0) return currentLength;\n        return areCssLengthsEqual(currentLength, nextLength)\n          ? currentLength\n          : nextLength;\n      });\n    },\n  );\n\n  return resolvedLength;\n}\n\nfunction resolveCssLength(value: string, element: HTMLElement | null) {\n  const trimmedValue = value.trim();\n  const pixelMatch = trimmedValue.match(/^(-?\\d+(?:\\.\\d+)?)px$/);\n  if (pixelMatch) return Math.max(0, Number(pixelMatch[1]));\n\n  if (typeof window === \"undefined\") return 0;\n\n  const remMatch = trimmedValue.match(/^(-?\\d+(?:\\.\\d+)?)rem$/);\n  if (remMatch) {\n    return (\n      Math.max(0, Number(remMatch[1])) *\n      readComputedFontSize(window.document.documentElement)\n    );\n  }\n\n  const emMatch = trimmedValue.match(/^(-?\\d+(?:\\.\\d+)?)em$/);\n  if (emMatch) {\n    return Math.max(0, Number(emMatch[1])) * readComputedFontSize(element);\n  }\n\n  const measuringElement = window.document.createElement(\"div\");\n  measuringElement.style.contain = \"strict\";\n  measuringElement.style.inlineSize = trimmedValue;\n  measuringElement.style.position = \"absolute\";\n  measuringElement.style.visibility = \"hidden\";\n  (element ?? window.document.body).appendChild(measuringElement);\n  const width = readElementInlineSize(measuringElement);\n  measuringElement.remove();\n  return width;\n}\n\nfunction readElementInlineSize(element: HTMLElement) {\n  const rect =\n    typeof element.getBoundingClientRect === \"function\"\n      ? element.getBoundingClientRect()\n      : null;\n  const width = rect?.width || element.clientWidth || 0;\n  return Number.isFinite(width) && width > 0 ? width : 0;\n}\n\nfunction readComputedFontSize(element: Element | null) {\n  if (typeof window === \"undefined\") return 16;\n  const fontSize = element ? window.getComputedStyle(element).fontSize : \"16px\";\n  const value = Number.parseFloat(fontSize);\n  return Number.isFinite(value) && value > 0 ? value : 16;\n}\n\nfunction areCssLengthsEqual(previous: number, next: number) {\n  return Math.abs(previous - next) <= 0.001;\n}\n",
      "type": "registry:ui",
      "target": "@ui/viewer-measurement.ts"
    },
    {
      "path": "registry/new-york-v4/ui/file-viewer-fit-width-motion.ts",
      "content": "\"use client\";\n\nimport type { FileViewerDocumentSurfaceMotionResolver } from \"./file-viewer-motion-kernel\";\nimport type { FileViewerMotionFrame } from \"./file-viewer-motion-plan\";\nimport type {\n  FileViewerDocumentAlign,\n  FileViewerInlineDirection,\n} from \"./file-viewer-renderer-contract\";\n\nexport const FILE_VIEWER_FIT_WIDTH_ANCHOR_BLOCK_PROPERTY =\n  \"--file-viewer-fit-width-anchor-block\";\n\n// Commit-then-relax: the renderer lays out at the motion's TARGET width from\n// the first commit, and this resolver reprojects that settled layout to the\n// in-flight visual width with one uniform transform. The transform terminates\n// on identity, so settle removes a no-op style instead of committing layout.\n//\n// The anchor custom property is the reading line's block offset in the settled\n// stage's own coordinates (post-rebase scrollTop + marker offset). It is read\n// live via var(), so the renderer writes it once per motion (in a layout\n// effect after the slide-start scroll rebase) without re-entering the kernel.\nexport function createFileViewerFitWidthSurfaceMotionResolver({\n  align,\n  anchorBlockProperty = FILE_VIEWER_FIT_WIDTH_ANCHOR_BLOCK_PROPERTY,\n  direction = \"ltr\",\n  isFitWidth,\n  stageInlineSize,\n  stageOuterInlinePadding = 0,\n  stageInlinePadding = 0,\n  stageInlineSlope = 1,\n  stageBlockSlope = stageInlineSlope,\n}: {\n  align: FileViewerDocumentAlign;\n  anchorBlockProperty?: string;\n  direction?: FileViewerInlineDirection;\n  isFitWidth: boolean;\n  stageInlineSize: number;\n  /** Constant symmetric padding around the transformed stage. */\n  stageOuterInlinePadding?: number;\n  /** Constant symmetric inline padding inside the transformed stage. */\n  stageInlinePadding?: number;\n  stageInlineSlope?: number;\n  /**\n   * Slope for the BLOCK axis when it differs from the inline one. A stage\n   * whose inline box carries constant padding while its block stack scales\n   * with the content (the image viewer: fit subtracts the horizontal\n   * padding, vertical gaps/padding scale) has two different affine models —\n   * X tracks the pane 1:1 while Y scales by the content ratio — and a\n   * uniform scale cannot land both axes exactly. Defaults to the inline\n   * slope (uniform scale) for fully proportional stages like the PDF.\n   */\n  stageBlockSlope?: number;\n}): FileViewerDocumentSurfaceMotionResolver {\n  return (frame) => {\n    if (!isFitWidth || frame.phase !== \"sliding\") {\n      return {\n        transform: \"\",\n        transformOrigin: \"\",\n        willChange: \"\",\n      };\n    }\n\n    return {\n      transform: getFileViewerFitWidthSurfaceMotionTransform({\n        align,\n        anchorBlockProperty,\n        direction,\n        frame,\n        stageInlineSize,\n        stageOuterInlinePadding,\n        stageInlinePadding,\n        stageInlineSlope,\n        stageBlockSlope,\n      }),\n      transformOrigin: \"0px 0px\",\n      willChange: \"transform\",\n    };\n  };\n}\n\n// Commit-then-relax for a CLAMPED reading column rather than a fit-width\n// stage: the stage's inline size is min(canvas, maxStageInlineSize), so it\n// does not scale with the pane — the only thing a width change moves is the\n// align margin. The canvas commits the motion's TARGET width from the first\n// sliding frame (minWidth under layoutPolicy \"target\"), which means a\n// widening pane's chunks land at the settled margin synchronously with the\n// click; this resolver reprojects them back to the live width's margin with\n// a translate that terminates on identity. A narrowing pane never engages it\n// (the canvas tracks the live width above its minWidth, so live and settled\n// margins agree) — exactly the leg that already glides on layout.\nexport function createFileViewerAlignTranslateSurfaceMotionResolver({\n  align,\n  direction = \"ltr\",\n  maxStageInlineSize,\n}: {\n  align: FileViewerDocumentAlign;\n  direction?: FileViewerInlineDirection;\n  /** The column's max inline size (the chunk's max-width, in px). */\n  maxStageInlineSize: number;\n}): FileViewerDocumentSurfaceMotionResolver {\n  return (frame) => {\n    if (frame.phase !== \"sliding\") {\n      return {\n        transform: \"\",\n        transformOrigin: \"\",\n        willChange: \"\",\n      };\n    }\n\n    return {\n      transform: getFileViewerAlignTranslateSurfaceMotionTransform({\n        align,\n        direction,\n        frame,\n        maxStageInlineSize,\n      }),\n      transformOrigin: \"0px 0px\",\n      willChange: \"transform\",\n    };\n  };\n}\n\nfunction getFileViewerAlignTranslateSurfaceMotionTransform({\n  align,\n  direction,\n  frame,\n  maxStageInlineSize,\n}: {\n  align: FileViewerDocumentAlign;\n  direction: FileViewerInlineDirection;\n  frame: FileViewerMotionFrame;\n  maxStageInlineSize: number;\n}) {\n  if (\n    !Number.isFinite(maxStageInlineSize) ||\n    maxStageInlineSize <= 0 ||\n    frame.layoutInlineSize <= 0 ||\n    frame.toInlineSize <= 0\n  ) {\n    return \"\";\n  }\n\n  // The canvas lays out at max(live, target): minWidth holds the committed\n  // target under a still-narrow pane, and a pane wider than the target just\n  // fills. The stage (reading column) centers/aligns INSIDE the canvas, and\n  // an overflowing canvas itself pins to the pane's start edge — left in\n  // LTR, right in RTL — so the stage's pane-space position carries the\n  // canvas offset too.\n  const canvasInlineSize = Math.max(frame.layoutInlineSize, frame.toInlineSize);\n  const stageInlineSize = Math.min(canvasInlineSize, maxStageInlineSize);\n  const canvasInlineOffset =\n    direction === \"rtl\"\n      ? Math.min(0, frame.layoutInlineSize - canvasInlineSize)\n      : 0;\n  const settledStageLeft =\n    canvasInlineOffset +\n    getFileViewerStageInlineMargin({\n      align,\n      availableInlineSize: canvasInlineSize,\n      direction,\n      stageInlineSize,\n    });\n  const liveStageLeft = getFileViewerStageInlineMargin({\n    align,\n    availableInlineSize: frame.layoutInlineSize,\n    direction,\n    stageInlineSize,\n  });\n  const translateX = liveStageLeft - settledStageLeft;\n\n  if (Math.abs(translateX) <= 0.001) return \"\";\n\n  return `translate3d(${formatFileViewerMotionPixel(translateX)}px, 0px, 0)`;\n}\n\nexport function getFileViewerFitWidthScale({\n  availableInlineSize,\n  contentInlineSize,\n  stageInlinePadding = 0,\n}: {\n  availableInlineSize: number;\n  contentInlineSize: number;\n  stageInlinePadding?: number;\n}) {\n  if (availableInlineSize <= 0 || contentInlineSize <= 0) return 1;\n\n  const contentAvailableInlineSize = Math.max(\n    1,\n    availableInlineSize - stageInlinePadding,\n  );\n  return contentAvailableInlineSize / contentInlineSize;\n}\n\n// The visual scale the resolver renders for a given live width — the same\n// affine reprojection as the transform itself. Renderers use it to reason\n// about the on-screen state (anchor capture/solve) without duplicating the\n// formula.\n//\n// stageInlineSlope is how many stage pixels the settled stage grows per pane\n// pixel. It is 1 whenever the stage IS the fit-width content (image, docx,\n// pptx, uniform-width PDFs: stage = pane − constant padding), but a stage\n// that is WIDER than its fit basis grows faster than the pane — a PDF fits\n// its FIRST page while the stage spans its WIDEST page, so a mixed-width\n// document has slope maxBase/fitBase > 1. A unit-slope assumption there\n// under-scales the first frame by (slope − 1)·delta/stage — measured as a\n// ~7px content step at the anchor-hold frame of a 355-page prospectus.\nexport function getFileViewerFitWidthVisualScale({\n  liveInlineSize,\n  stageInlineSize,\n  stageInlineSlope = 1,\n  targetInlineSize,\n}: {\n  liveInlineSize: number;\n  stageInlineSize: number;\n  stageInlineSlope?: number;\n  targetInlineSize: number;\n}) {\n  if (\n    stageInlineSize <= 0 ||\n    !Number.isFinite(liveInlineSize) ||\n    !Number.isFinite(targetInlineSize)\n  ) {\n    return 1;\n  }\n  const slope =\n    Number.isFinite(stageInlineSlope) && stageInlineSlope > 0\n      ? stageInlineSlope\n      : 1;\n  return (\n    Math.max(1, stageInlineSize + slope * (liveInlineSize - targetInlineSize)) /\n    stageInlineSize\n  );\n}\n\n// Capture side of the motion anchor: the probe content line's on-screen block\n// offset relative to the scroll box, taken just before a motion (or retarget)\n// commits. When a motion is already in flight the DOM is the settled layout\n// PLUS the live transform, so the capture applies that transform — otherwise\n// a retarget would solve continuity against a picture the reader never saw.\nexport function captureFileViewerFitWidthAnchorScreenOffset({\n  lastAnchorBlock,\n  liveFrame,\n  probeStageOffset,\n  scrollTop,\n  stageInlineSize,\n  stageInlinePadding = 0,\n  stageBlockSlope = 1,\n}: {\n  lastAnchorBlock: number | null;\n  liveFrame: FileViewerMotionFrame | null;\n  probeStageOffset: number;\n  scrollTop: number;\n  stageInlineSize: number;\n  stageInlinePadding?: number;\n  /** The BLOCK-axis slope — anchor capture/solve is block-axis math. */\n  stageBlockSlope?: number;\n}) {\n  const untransformed = probeStageOffset - scrollTop;\n  if (!liveFrame || liveFrame.phase !== \"sliding\") return untransformed;\n\n  const liveScale = getFileViewerFitWidthVisualScale({\n    liveInlineSize: liveFrame.layoutInlineSize,\n    stageInlineSize: getFileViewerFitWidthContentInlineSize({\n      stageInlinePadding,\n      stageInlineSize,\n    }),\n    stageInlineSlope: stageBlockSlope,\n    targetInlineSize: liveFrame.toInlineSize,\n  });\n  if (Math.abs(1 - liveScale) <= 0.001) return untransformed;\n\n  return (\n    liveScale * probeStageOffset +\n    (1 - liveScale) * (lastAnchorBlock ?? 0) -\n    scrollTop\n  );\n}\n\n// Solve side: the anchor block offset that puts the probe content line back on\n// its captured screen position under the NEW layout model at the motion's\n// first-frame scale. Exact regardless of how the rebase clamped or how the\n// old/new layout models relate (measured page sizes, constant gaps/padding).\n// Returns null when the motion is degenerate (caller falls back to the live\n// reading marker).\nexport function resolveFileViewerFitWidthMotionAnchorBlock({\n  fromInlineSize,\n  probeScreenOffset,\n  probeStageOffset,\n  scrollTop,\n  stageInlineSize,\n  stageInlinePadding = 0,\n  stageBlockSlope = 1,\n  toInlineSize,\n}: {\n  fromInlineSize: number | null;\n  probeScreenOffset: number;\n  probeStageOffset: number;\n  scrollTop: number;\n  stageInlineSize: number;\n  stageInlinePadding?: number;\n  /** The BLOCK-axis slope — anchor capture/solve is block-axis math. */\n  stageBlockSlope?: number;\n  toInlineSize: number | null;\n}) {\n  if (fromInlineSize == null || toInlineSize == null) return null;\n\n  const startScale = getFileViewerFitWidthVisualScale({\n    liveInlineSize: fromInlineSize,\n    stageInlineSize: getFileViewerFitWidthContentInlineSize({\n      stageInlinePadding,\n      stageInlineSize,\n    }),\n    stageInlineSlope: stageBlockSlope,\n    targetInlineSize: toInlineSize,\n  });\n  if (!Number.isFinite(startScale) || Math.abs(1 - startScale) <= 0.001) {\n    return null;\n  }\n\n  return (\n    (probeScreenOffset + scrollTop - startScale * probeStageOffset) /\n    (1 - startScale)\n  );\n}\n\nfunction getFileViewerFitWidthSurfaceMotionTransform({\n  align,\n  anchorBlockProperty,\n  direction,\n  frame,\n  stageInlineSize,\n  stageOuterInlinePadding,\n  stageInlinePadding,\n  stageInlineSlope,\n  stageBlockSlope,\n}: {\n  align: FileViewerDocumentAlign;\n  anchorBlockProperty: string;\n  direction: FileViewerInlineDirection;\n  frame: FileViewerMotionFrame;\n  stageInlineSize: number;\n  stageOuterInlinePadding: number;\n  stageInlinePadding: number;\n  stageInlineSlope: number;\n  stageBlockSlope: number;\n}) {\n  if (\n    stageInlineSize <= 0 ||\n    frame.layoutInlineSize <= 0 ||\n    frame.toInlineSize <= 0\n  ) {\n    return \"\";\n  }\n\n  // Fit-width renderers size their stage as an affine function of the\n  // available width (stage = slope × width − constant padding), so the\n  // in-flight visual stage is the settled stage plus the scaled live width\n  // delta. At the first frame this resolves to exactly the pre-toggle stage\n  // size, and at the last frame to the settled stage — identity. Each axis\n  // carries its own slope: they differ when the stage's inline box holds\n  // constant padding while its block stack scales with the content.\n  const contentInlineSize = getFileViewerFitWidthContentInlineSize({\n    stageInlinePadding,\n    stageInlineSize,\n  });\n  const inlineScale = getFileViewerFitWidthVisualScale({\n    liveInlineSize: frame.layoutInlineSize,\n    stageInlineSize: contentInlineSize,\n    stageInlineSlope,\n    targetInlineSize: frame.toInlineSize,\n  });\n  const blockScale = getFileViewerFitWidthVisualScale({\n    liveInlineSize: frame.layoutInlineSize,\n    stageInlineSize: contentInlineSize,\n    stageInlineSlope: stageBlockSlope,\n    targetInlineSize: frame.toInlineSize,\n  });\n  const visualStageInlineSize =\n    inlineScale * contentInlineSize + stageInlinePadding;\n  const availableStageInlineSize = Math.max(\n    1,\n    frame.layoutInlineSize - Math.max(0, stageOuterInlinePadding),\n  );\n  const settledMargin = getFileViewerStageInlineMargin({\n    align,\n    availableInlineSize: availableStageInlineSize,\n    direction,\n    stageInlineSize,\n  });\n  const visualMargin = getFileViewerStageInlineMargin({\n    align,\n    availableInlineSize: availableStageInlineSize,\n    direction,\n    stageInlineSize: visualStageInlineSize,\n  });\n  // The padding is constant in both endpoint layouts. Scaling the outer stage\n  // would scale that inset too, making the visible page briefly too wide or\n  // narrow on the first frame. Rebase the symmetric start inset so the inner\n  // content edge, not the transparent wrapper edge, is pixel-continuous.\n  const inlinePaddingStart = stageInlinePadding / 2;\n  const translateX =\n    visualMargin - settledMargin + (1 - inlineScale) * inlinePaddingStart;\n\n  if (Math.abs(frame.layoutInlineSize - frame.toInlineSize) <= 0.001) {\n    return \"\";\n  }\n\n  const formattedInlineScale = formatFileViewerMotionScale(inlineScale);\n  const formattedBlockScale = formatFileViewerMotionScale(blockScale);\n  const formattedTranslateX = formatFileViewerMotionPixel(translateX);\n  // Scale about the stage origin; the anchor term keeps the reading line\n  // fixed on the block axis: y' = s·y + (1 − s)·anchor equals y at\n  // y = anchor.\n  const translateY = `calc((1 - ${formattedBlockScale}) * var(${anchorBlockProperty}, 0px))`;\n  const formattedScale =\n    formattedInlineScale === formattedBlockScale\n      ? formattedInlineScale\n      : `${formattedInlineScale}, ${formattedBlockScale}`;\n\n  return `translate3d(${formattedTranslateX}px, ${translateY}, 0) scale(${formattedScale})`;\n}\n\nfunction getFileViewerFitWidthContentInlineSize({\n  stageInlinePadding,\n  stageInlineSize,\n}: {\n  stageInlinePadding: number;\n  stageInlineSize: number;\n}) {\n  const padding = Number.isFinite(stageInlinePadding)\n    ? Math.max(0, stageInlinePadding)\n    : 0;\n  return Math.max(1, stageInlineSize - padding);\n}\n\n// Physical LEFT offset of the stage box inside the available inline size —\n// translateX shifts along the physical X axis, so the model must speak\n// physical-left in both directions. Stages align with physical auto margins\n// (mx-auto for center, ml-auto for end, plain flow for start), so:\n// - free space ≥ 0: center splits it; end pins right in both directions\n//   (ml-auto is physical); start follows flow (left in LTR, right in RTL).\n// - free space < 0 (the settled stage overflows the live container — the\n//   close leg's early frames): auto margins collapse to 0 and CSS resolves\n//   the over-constraint against the direction's end edge, pinning the box to\n//   the start edge — left edge at 0 in LTR, at the negative free space in\n//   RTL. The old unconditional max(0, …) clamp encoded only the LTR half and\n//   made the RTL close leg overshoot by the overflow amount.\nfunction getFileViewerStageInlineMargin({\n  align,\n  availableInlineSize,\n  direction,\n  stageInlineSize,\n}: {\n  align: FileViewerDocumentAlign;\n  availableInlineSize: number;\n  direction: FileViewerInlineDirection;\n  stageInlineSize: number;\n}) {\n  const freeInlineSize = availableInlineSize - stageInlineSize;\n  if (freeInlineSize < 0) return direction === \"rtl\" ? freeInlineSize : 0;\n\n  switch (align) {\n    case \"start\":\n      return direction === \"rtl\" ? freeInlineSize : 0;\n    case \"end\":\n      return freeInlineSize;\n    case \"center\":\n      return freeInlineSize / 2;\n  }\n}\n\nfunction formatFileViewerMotionPixel(value: number) {\n  return Number.isFinite(value) ? Number(value.toFixed(3)) : 0;\n}\n\nfunction formatFileViewerMotionScale(value: number) {\n  return Number.isFinite(value) ? String(Number(value.toFixed(6))) : \"1\";\n}\n",
      "type": "registry:ui",
      "target": "@ui/file-viewer-fit-width-motion.ts"
    }
  ],
  "type": "registry:ui"
}
