{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "code-viewer",
  "title": "Code Viewer",
  "description": "A fixed-line code and log viewer with line numbers, custom virtualization, source-line highlights, retry, zoom, and download.",
  "dependencies": [
    "lucide-react",
    "prismjs@^1.30.0"
  ],
  "devDependencies": [
    "@types/prismjs@^1.26.6"
  ],
  "registryDependencies": [
    "button",
    "dropdown-menu",
    "@retab/scroll-area",
    "@retab/skeleton",
    "@retab/utils",
    "@retab/viewer-controls",
    "@retab/use-mount-effect",
    "@retab/use-keyed-layout-effect"
  ],
  "files": [
    {
      "path": "registry/new-york-v4/ui/code-viewer.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\n\nimport { CodeViewerFallback } from \"./code-viewer-chrome\";\nimport { CodeViewerContent } from \"./code-viewer-content\";\nimport type { CodeViewerHandle, CodeViewerProps } from \"./code-viewer-types\";\nimport { PlainTextViewerFrame } from \"./plain-text-viewer-frame\";\n\nexport type {\n  CodeDocumentSource,\n  CodeLineRange,\n  CodeViewerHandle,\n  CodeViewerProps,\n} from \"./code-viewer-types\";\n\nexport type CodeResourceContentProps = Omit<CodeViewerProps, \"source\"> & {\n  resource: ViewerResource;\n};\n\nexport const CodeViewer = React.forwardRef<CodeViewerHandle, CodeViewerProps>(\n  function CodeViewer(props, ref) {\n    return (\n      <PlainTextViewerFrame\n        props={props}\n        forwardedRef={ref}\n        clientFallbackPolicy=\"always\"\n        contentResetPolicy=\"inline-retry\"\n        Fallback={CodeViewerFallback}\n        Content={CodeViewerContent}\n      />\n    );\n  },\n);\n\nexport const CodeResourceContent = React.forwardRef<\n  CodeViewerHandle,\n  CodeResourceContentProps\n>(function CodeResourceContent({ resource, ...props }, ref) {\n  return (\n    <PlainTextViewerFrame\n      props={props}\n      resource={resource}\n      forwardedRef={ref}\n      clientFallbackPolicy=\"always\"\n      contentResetPolicy=\"inline-retry\"\n      Fallback={CodeViewerFallback}\n      Content={CodeViewerContent}\n    />\n  );\n});\n",
      "type": "registry:ui",
      "target": "@ui/code-viewer.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/code-viewer-chrome.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { type ViewerDownloadAction } from \"@/lib/viewer-download-actions\";\n\nimport { Skeleton } from \"./skeleton\";\nimport { TextCodeViewerFrame } from \"./text-code-viewer-chrome\";\nimport {\n  ViewerControls,\n  ViewerControlsSkeleton,\n  type ViewerControlsState,\n} from \"./viewer-controls\";\n\nexport function CodeViewerFrame({\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-muted/20\"\n      className={className}\n      dataSlot=\"code-viewer\"\n      framedClassName=\"rounded-xl border bg-muted/30\"\n    >\n      {children}\n    </TextCodeViewerFrame>\n  );\n}\n\nexport function CodeViewerControls({\n  lineCount,\n  fontScale,\n  downloadAction,\n  onZoomOut,\n  onZoomIn,\n  onResetZoom,\n}: {\n  lineCount: number;\n  fontScale: number;\n  downloadAction?: ViewerDownloadAction | null;\n  onZoomOut: () => void;\n  onZoomIn: () => void;\n  onResetZoom: () => void;\n}) {\n  const controlsState = codeViewerControlsState({\n    lineCount,\n    fontScale,\n    downloadAction,\n    onZoomOut,\n    onZoomIn,\n    onResetZoom,\n  });\n\n  return (\n    <ViewerControls\n      title={controlsState.title}\n      zoom={controlsState.zoom}\n      downloads={controlsState.downloads}\n    />\n  );\n}\n\nexport function codeViewerControlsState({\n  lineCount,\n  fontScale,\n  downloadAction,\n  onZoomOut,\n  onZoomIn,\n  onResetZoom,\n}: {\n  lineCount: number;\n  fontScale: number;\n  downloadAction?: ViewerDownloadAction | null;\n  onZoomOut: () => void;\n  onZoomIn: () => void;\n  onResetZoom: () => void;\n}): ViewerControlsState {\n  return {\n    title: `${lineCount} line${lineCount === 1 ? \"\" : \"s\"}`,\n    zoom: {\n      scale: fontScale,\n      onZoomOut,\n      onZoomIn,\n      onFit: onResetZoom,\n      fitLabel: \"Reset zoom\",\n    },\n    downloads: downloadAction ? [downloadAction] : [],\n  };\n}\n\nexport function CodeViewerFallback({\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    <CodeViewerFrame 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-2 overflow-hidden p-4\"\n        data-slot=\"code-body-skeleton\"\n      >\n        {Array.from({ length: 12 }, (_, index) => (\n          <Skeleton\n            key={index}\n            className=\"h-4\"\n            style={{ width: `${40 + ((index * 13) % 55)}%` }}\n          />\n        ))}\n      </div>\n    </CodeViewerFrame>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/code-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/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/code-viewer-content.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { useKeyedLayoutEffect } from \"@/hooks/use-keyed-layout-effect\";\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\nimport { useMountEffect } from \"@/hooks/use-mount-effect\";\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\n\nimport {\n  CodeViewerControls,\n  codeViewerControlsState,\n  CodeViewerFrame,\n} from \"./code-viewer-chrome\";\nimport { scrollTopForLineRangeMetrics } from \"./code-viewer-layout\";\nimport { getCodeLongLineSelectionText } from \"./code-viewer-long-lines\";\nimport { useCodeProjectionScheduler } from \"./code-viewer-projection-scheduler\";\nimport { createCodeProjector } from \"./code-viewer-projector\";\nimport {\n  clampCodeViewerScale,\n  CODE_VIEWER_BASE_LINE_PX,\n  CODE_VIEWER_BLOCK_PADDING,\n} from \"./code-viewer-scale\";\nimport { createCodeSyntax } from \"./code-viewer-syntax\";\nimport { useCodeViewerSyntaxStyle } from \"./code-viewer-syntax-style\";\nimport type {\n  CodeLineRange,\n  CodeViewerHandle,\n  CodeViewerProps,\n} from \"./code-viewer-types\";\nimport { CodeViewerViewport } from \"./code-viewer-viewport\";\nimport { normalizeTextLineRange } from \"./line-ranges\";\nimport {\n  readTextDocument,\n  resolvedTextViewerBounds,\n} from \"./plain-text-resource\";\nimport { useViewerControlsRegistration } from \"./viewer-controls\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\ntype CodeReadingAnchor = {\n  lineIndex: number;\n  offsetPx: number;\n};\n\ntype NativeFindCodeChunk = {\n  endLine: number;\n  startLine: number;\n  text: string;\n};\n\ntype CodeIdleWindow = Window &\n  typeof globalThis & {\n    cancelIdleCallback?: Window[\"cancelIdleCallback\"];\n    requestIdleCallback?: Window[\"requestIdleCallback\"];\n  };\n\nconst CODE_VIEWER_DEFERRED_SYNTAX_LINE_COUNT = 500;\nconst CODE_VIEWER_NATIVE_FIND_CHUNK_SIZE = 128;\n\ntype CodeViewerContentProps = Omit<CodeViewerProps, \"source\"> & {\n  resource: ViewerResource;\n  retryVersion: number;\n  forwardedRef?: React.ForwardedRef<CodeViewerHandle>;\n};\n\nexport function CodeViewerContent({\n  resource,\n  className,\n  controls = true,\n  download = true,\n  highlight,\n  bare = false,\n  maxBytes,\n  maxLines,\n  retryVersion,\n  forwardedRef,\n}: CodeViewerContentProps) {\n  const bounds = resolvedTextViewerBounds({ maxBytes, maxLines });\n  const textDocument = readTextDocument({\n    content: resource.content,\n    retryVersion,\n    bounds,\n  });\n  const textLines = textDocument.lines;\n  const [syntaxVersion, setSyntaxVersion] = React.useState(0);\n  const syntax = React.useMemo(\n    () =>\n      createCodeSyntax(resource, {\n        deferTokens: textLines.length > CODE_VIEWER_DEFERRED_SYNTAX_LINE_COUNT,\n        onTokensChanged: () => setSyntaxVersion((version) => version + 1),\n      }),\n    [resource, textLines.length],\n  );\n  const syntaxIdentity =\n    syntaxVersion === 0\n      ? syntax.identity\n      : `${syntax.identity}\\u0000${syntaxVersion}`;\n  const highlightStart = highlight?.start;\n  const highlightEnd = highlight?.end;\n  const highlightRange = React.useMemo(\n    () =>\n      normalizeTextLineRange(\n        highlightStart == null || highlightEnd == null\n          ? null\n          : { start: highlightStart, end: highlightEnd },\n        textLines.length,\n      ),\n    [highlightStart, highlightEnd, textLines.length],\n  );\n  const downloadAction = download ? resource.originalDownload : null;\n\n  const [fontScale, setFontScale] = React.useState(1);\n  const viewportRef = React.useRef<HTMLDivElement | null>(null);\n  const rowHostRef = React.useRef<HTMLPreElement | null>(null);\n  const pendingScrollAnchorRef = React.useRef<CodeReadingAnchor | null>(null);\n  const projector = React.useMemo(() => createCodeProjector(), []);\n  const lineHeight = CODE_VIEWER_BASE_LINE_PX * fontScale;\n  const contentIdentity = React.useMemo(\n    () =>\n      codeContentIdentity({\n        contentKey: resource.content.key,\n        maxBytes: bounds.maxBytes,\n        maxLines: bounds.maxLines,\n        retryVersion,\n      }),\n    [bounds.maxBytes, bounds.maxLines, resource.content.key, retryVersion],\n  );\n  const gutterWidth = `calc(${String(textLines.length).length + 1}ch + 1.25rem)`;\n  const layoutIdentity = codeLayoutIdentity({ gutterWidth, lineHeight });\n\n  useCodeViewerSyntaxStyle();\n\n  const commitFontScale = React.useCallback(\n    (nextScale: number) => {\n      const clampedScale = clampCodeViewerScale(nextScale);\n      if (clampedScale === fontScale) return;\n\n      pendingScrollAnchorRef.current = captureCodeReadingAnchor({\n        lineCount: textLines.length,\n        lineHeight,\n        projector,\n        viewportElement: viewportRef.current,\n      });\n      setFontScale(clampedScale);\n    },\n    [fontScale, lineHeight, projector, textLines.length],\n  );\n\n  const zoom = React.useCallback(\n    (factor: number) => commitFontScale(fontScale * factor),\n    [commitFontScale, fontScale],\n  );\n  const onZoomOut = React.useCallback(() => zoom(1 / 1.2), [zoom]);\n  const onZoomIn = React.useCallback(() => zoom(1.2), [zoom]);\n  const onResetZoom = React.useCallback(\n    () => commitFontScale(1),\n    [commitFontScale],\n  );\n\n  useKeyedMountEffect(\n    joinEffectKey([\"code-anchor\", lineHeight, textLines.length]),\n    () => {\n      const anchor = pendingScrollAnchorRef.current;\n      const viewportElement = viewportRef.current;\n      if (!anchor || !viewportElement) return;\n\n      pendingScrollAnchorRef.current = null;\n      projector.scrollToLogical({\n        lineCount: textLines.length,\n        lineHeight,\n        logicalScrollTop: restoreCodeReadingAnchor({\n          anchor,\n          lineCount: textLines.length,\n          lineHeight,\n        }),\n        viewport: viewportElement,\n      });\n    },\n  );\n\n  const scrollLineRange = React.useCallback(\n    (range: CodeLineRange | null, options?: ScrollToOptions) => {\n      const viewportElement = viewportRef.current;\n      const normalizedRange = normalizeTextLineRange(range, textLines.length);\n      if (!viewportElement || !normalizedRange) return;\n\n      projector.scrollToLogical({\n        behavior: options?.behavior ?? \"smooth\",\n        lineCount: textLines.length,\n        lineHeight,\n        logicalScrollTop: scrollTopForLineRangeMetrics({\n          startLine: normalizedRange.start,\n          endLine: normalizedRange.end,\n          lineHeight,\n          paddingStart: CODE_VIEWER_BLOCK_PADDING,\n          viewportHeight: viewportElement.clientHeight,\n        }),\n        viewport: viewportElement,\n      });\n    },\n    [lineHeight, projector, textLines.length],\n  );\n\n  React.useImperativeHandle(\n    forwardedRef ?? null,\n    () => ({\n      scrollToLineRange: scrollLineRange,\n      getViewportElement: () => viewportRef.current,\n    }),\n    [scrollLineRange],\n  );\n\n  useKeyedMountEffect(\n    joinEffectKey([\"code-highlight-scroll\", highlightRange, scrollLineRange]),\n    () => {\n      if (!highlightRange) return;\n      scrollLineRange(highlightRange, { behavior: \"smooth\" });\n    },\n  );\n\n  const project = React.useCallback(() => {\n    const rowHost = rowHostRef.current;\n    const viewport = viewportRef.current;\n    if (!rowHost || !viewport) return false;\n\n    return projector.project({\n      contentIdentity,\n      gutterWidth,\n      highlightRange,\n      layoutIdentity,\n      lineHeight,\n      rowHost,\n      syntax,\n      syntaxIdentity,\n      textLines,\n      viewport,\n    });\n  }, [\n    contentIdentity,\n    gutterWidth,\n    highlightRange,\n    layoutIdentity,\n    lineHeight,\n    projector,\n    syntax,\n    syntaxIdentity,\n    textLines,\n  ]);\n\n  const copyLongLineSelection = React.useCallback(\n    (event: React.ClipboardEvent<HTMLPreElement>) => {\n      const rowHost = rowHostRef.current;\n      if (!rowHost) return;\n\n      const selectedText = getCodeLongLineSelectionText({\n        rowHost,\n        selection: window.getSelection(),\n        textLines,\n      });\n      if (selectedText == null) return;\n\n      event.clipboardData.setData(\"text/plain\", selectedText);\n      event.preventDefault();\n    },\n    [textLines],\n  );\n\n  useCodeProjectionScheduler({\n    project,\n    rowHostRef,\n    viewportRef,\n  });\n\n  useMountEffect(() => () => projector.destroy());\n  useKeyedMountEffect(joinEffectKey([\"code-syntax\", syntax]), () => {\n    setSyntaxVersion(0);\n    return () => syntax.destroy?.();\n  });\n\n  useCodeControlsRegistration({\n    downloadAction,\n    fontScale,\n    lineCount: textLines.length,\n    onResetZoom,\n    onZoomIn,\n    onZoomOut,\n  });\n\n  return (\n    <CodeViewerFrame className={className} bare={bare}>\n      {controls ? (\n        <CodeViewerControls\n          lineCount={textLines.length}\n          fontScale={fontScale}\n          downloadAction={downloadAction}\n          onZoomOut={onZoomOut}\n          onZoomIn={onZoomIn}\n          onResetZoom={onResetZoom}\n        />\n      ) : null}\n      <DeferredNativeFindIndex\n        lineCount={textLines.length}\n        lines={textLines}\n        scrollToLineRange={scrollLineRange}\n      />\n      <CodeViewerViewport\n        fontScale={fontScale}\n        gutterWidth={gutterWidth}\n        lineCount={textLines.length}\n        lineHeight={lineHeight}\n        onCopy={copyLongLineSelection}\n        rowHostRef={rowHostRef}\n        viewportRef={viewportRef}\n      />\n    </CodeViewerFrame>\n  );\n}\n\nfunction DeferredNativeFindIndex({\n  lines,\n  lineCount,\n  scrollToLineRange,\n}: {\n  lines: readonly string[];\n  lineCount: number;\n  scrollToLineRange: (\n    range: CodeLineRange | null,\n    options?: ScrollToOptions,\n  ) => void;\n}) {\n  const [isReady, setIsReady] = React.useState(false);\n\n  useKeyedMountEffect(joinEffectKey([\"code-native-find\", lines]), () => {\n    setIsReady(false);\n    const show = () => setIsReady(true);\n    if (typeof window === \"undefined\") return;\n    const browserWindow = window as CodeIdleWindow;\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      lineCount={lineCount}\n      lines={lines}\n      scrollToLineRange={scrollToLineRange}\n    />\n  );\n}\n\nfunction NativeFindIndex({\n  lines,\n  lineCount,\n  scrollToLineRange,\n}: {\n  lines: readonly string[];\n  lineCount: number;\n  scrollToLineRange: (\n    range: CodeLineRange | null,\n    options?: ScrollToOptions,\n  ) => void;\n}) {\n  const entries = React.useMemo(() => chunkNativeFindLines(lines), [lines]);\n\n  return (\n    <div\n      aria-hidden=\"true\"\n      className=\"pointer-events-none h-0 w-0 overflow-hidden opacity-0\"\n      data-native-find-indexed-chunks={entries.length}\n      data-native-find-indexed-lines={lines.length}\n      data-slot=\"code-native-find-index\"\n    >\n      {entries.map((entry) => (\n        <NativeFindEntry\n          key={entry.startLine}\n          entry={entry}\n          lineCount={lineCount}\n          scrollToLineRange={scrollToLineRange}\n        />\n      ))}\n    </div>\n  );\n}\n\nfunction NativeFindEntry({\n  entry,\n  lineCount,\n  scrollToLineRange,\n}: {\n  entry: NativeFindCodeChunk;\n  lineCount: number;\n  scrollToLineRange: (\n    range: CodeLineRange | null,\n    options?: ScrollToOptions,\n  ) => void;\n}) {\n  const ref = React.useRef<HTMLSpanElement | null>(null);\n\n  useKeyedLayoutEffect(\n    joinEffectKey([\n      \"code-native-find-entry\",\n      entry.startLine,\n      entry.endLine,\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: entry.endLine,\n              start: entry.startLine,\n            },\n            lineCount,\n          ),\n          { behavior: \"auto\" },\n        );\n        if (typeof requestAnimationFrame === \"function\") {\n          requestAnimationFrame(() => {\n            element.setAttribute(\"hidden\", \"until-found\");\n          });\n          return;\n        }\n        element.setAttribute(\"hidden\", \"until-found\");\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=\"block h-px w-px overflow-hidden whitespace-pre\"\n      data-native-find-end-line={entry.endLine}\n      data-native-find-start-line={entry.startLine}\n    >\n      {entry.text || \" \"}\n    </span>\n  );\n}\n\nfunction chunkNativeFindLines(lines: readonly string[]): NativeFindCodeChunk[] {\n  const chunks: NativeFindCodeChunk[] = [];\n  for (\n    let startIndex = 0;\n    startIndex < lines.length;\n    startIndex += CODE_VIEWER_NATIVE_FIND_CHUNK_SIZE\n  ) {\n    const endIndex = Math.min(\n      lines.length,\n      startIndex + CODE_VIEWER_NATIVE_FIND_CHUNK_SIZE,\n    );\n    chunks.push({\n      endLine: endIndex,\n      startLine: startIndex + 1,\n      text: lines.slice(startIndex, endIndex).join(\"\\n\"),\n    });\n  }\n  return chunks;\n}\n\nfunction useCodeControlsRegistration({\n  downloadAction,\n  fontScale,\n  lineCount,\n  onResetZoom,\n  onZoomIn,\n  onZoomOut,\n}: {\n  downloadAction: ViewerResource[\"originalDownload\"] | null;\n  fontScale: number;\n  lineCount: number;\n  onResetZoom: () => void;\n  onZoomIn: () => void;\n  onZoomOut: () => void;\n}) {\n  const onControlsChange = useViewerControlsRegistration();\n  const controlsState = React.useMemo(\n    () =>\n      codeViewerControlsState({\n        downloadAction,\n        fontScale,\n        lineCount,\n        onResetZoom,\n        onZoomIn,\n        onZoomOut,\n      }),\n    [downloadAction, fontScale, lineCount, onResetZoom, onZoomIn, onZoomOut],\n  );\n\n  useKeyedMountEffect(\n    joinEffectKey([\"code-controls\", onControlsChange, controlsState]),\n    () => {\n      if (!onControlsChange) return;\n      onControlsChange(controlsState);\n      return () => onControlsChange(null);\n    },\n  );\n}\n\nfunction captureCodeReadingAnchor({\n  lineCount,\n  lineHeight,\n  projector,\n  viewportElement,\n}: {\n  lineCount: number;\n  lineHeight: number;\n  projector: ReturnType<typeof createCodeProjector>;\n  viewportElement: HTMLDivElement | null;\n}): CodeReadingAnchor | null {\n  if (!viewportElement || lineCount <= 0 || lineHeight <= 0) return null;\n\n  const scrollTop = projector.getLogicalScrollTop({\n    lineCount,\n    lineHeight,\n    viewport: viewportElement,\n  });\n  const contentTop = Math.max(0, scrollTop - CODE_VIEWER_BLOCK_PADDING);\n  const lineIndex = Math.min(\n    lineCount - 1,\n    Math.max(0, Math.floor(contentTop / lineHeight)),\n  );\n\n  return {\n    lineIndex,\n    offsetPx: Math.max(0, contentTop - lineIndex * lineHeight),\n  };\n}\n\nfunction restoreCodeReadingAnchor({\n  anchor,\n  lineCount,\n  lineHeight,\n}: {\n  anchor: CodeReadingAnchor;\n  lineCount: number;\n  lineHeight: number;\n}) {\n  if (lineCount <= 0 || lineHeight <= 0) return 0;\n\n  const lineIndex = Math.min(lineCount - 1, Math.max(0, anchor.lineIndex));\n  return (\n    CODE_VIEWER_BLOCK_PADDING +\n    lineIndex * lineHeight +\n    Math.min(anchor.offsetPx, Math.max(0, lineHeight - 1))\n  );\n}\n\nfunction codeContentIdentity({\n  contentKey,\n  maxBytes,\n  maxLines,\n  retryVersion,\n}: {\n  contentKey: string;\n  maxBytes: number;\n  maxLines: number;\n  retryVersion: number;\n}) {\n  return [contentKey, retryVersion, maxBytes, maxLines].join(\"\\u0000\");\n}\n\nfunction codeLayoutIdentity({\n  gutterWidth,\n  lineHeight,\n}: {\n  gutterWidth: string;\n  lineHeight: number;\n}) {\n  return [lineHeight, gutterWidth].join(\"\\u0000\");\n}\n",
      "type": "registry:ui",
      "target": "@ui/code-viewer-content.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/code-viewer-projector.ts",
      "content": "import {\n  CODE_VIEWER_BLOCK_PADDING,\n  CODE_VIEWER_INITIAL_VIEWPORT_HEIGHT,\n  CODE_VIEWER_OVERSCAN_PX,\n} from \"./code-viewer-scale\";\nimport type { CodeSyntax, CodeTokenLeaf } from \"./code-viewer-syntax\";\nimport {\n  getCodeLogicalScrollTop,\n  getCodePagedLayoutTop,\n  getCodePhysicalScrollSize,\n  getCodeVirtualLineWindow,\n  getCodeVirtualTotalSize,\n  resolveCodePhysicalScrollPosition,\n  type CodeVirtualLine,\n} from \"./code-viewer-virtualization\";\nimport { getCodeLineRenderText } from \"./code-viewer-long-lines\";\nimport { isLineInRange, type NormalizedTextLineRange } from \"./line-ranges\";\n\n// Opaque fills mixed from the theme's opaque `--foreground`/`--background`\n// tokens (the `--muted`/`--accent` tokens are alpha-based, so they cannot mask\n// scrolled content). Shared with the viewport's full-height gutter rail.\nexport const CODE_GUTTER_BACKGROUND =\n  \"color-mix(in oklab, var(--foreground) 3%, var(--background))\";\nconst CODE_HIGHLIGHT_BACKGROUND =\n  \"color-mix(in oklab, var(--foreground) 8%, var(--background))\";\nconst CODE_HIGHLIGHT_ACCENT_SHADOW = \"inset 2px 0 0 0 var(--primary)\";\nconst CODE_HIGHLIGHT_NUMBER_COLOR =\n  \"color-mix(in oklab, var(--foreground) 70%, transparent)\";\n\nexport type CodeProjectionIdentity = {\n  contentIdentity: string;\n  layoutIdentity: string;\n  syntaxIdentity: string;\n};\n\nexport type CodeProjectionInput = CodeProjectionIdentity & {\n  rowHost: HTMLPreElement;\n  viewport: HTMLDivElement;\n  textLines: readonly string[];\n  lineHeight: number;\n  gutterWidth: string;\n  highlightRange: NormalizedTextLineRange | null;\n  syntax: CodeSyntax;\n};\n\nexport type CodeProjector = {\n  getLogicalScrollTop(input: CodeProjectionScrollInput): number;\n  project(input: CodeProjectionInput): boolean;\n  scrollToLogical(input: CodeProjectionScrollToInput): void;\n  destroy(): void;\n};\n\nexport type CodeProjectionScrollInput = {\n  lineCount: number;\n  lineHeight: number;\n  viewport: HTMLDivElement;\n};\n\nexport type CodeProjectionScrollToInput = CodeProjectionScrollInput & {\n  behavior?: ScrollBehavior;\n  logicalScrollTop: number;\n};\n\nexport type CodeProjectionMetrics = {\n  contentPatches: number;\n  layoutPatches: number;\n  noops: number;\n  projections: number;\n  rowsCreated: number;\n  rowsRemoved: number;\n  rowsReused: number;\n  tokenSpanRebuilds: number;\n  visibleEnd: number;\n  visibleStart: number;\n};\n\nexport type CodeProjectorOptions = {\n  metrics?: CodeProjectionMetrics;\n};\n\ntype CodeRowCache = {\n  contentIdentity: string;\n  contentSpan: HTMLSpanElement;\n  gutterSpan: HTMLSpanElement;\n  layoutIdentity: string;\n  row: HTMLDivElement;\n};\n\ntype VisibleRange = {\n  end: number;\n  start: number;\n};\n\ntype LastProjection = CodeProjectionIdentity & {\n  highlightIdentity: string;\n  horizontalScrollLeft: number;\n  renderedWindowHeight: number;\n  renderedWindowStickyOffset: number;\n  renderedWindowTop: number;\n  logicalScrollTop: number;\n  scrollPageOffset: number;\n  totalHeight: string;\n  visibleEnd: number;\n  visibleStart: number;\n};\n\ntype CodeRenderedWindow = {\n  height: number;\n  rowOffset: number;\n  stickyOffset: number;\n  top: number;\n};\n\nconst MAX_RECYCLED_CODE_ROWS = 512;\n\nexport function createCodeProjectionMetrics(): CodeProjectionMetrics {\n  return {\n    contentPatches: 0,\n    layoutPatches: 0,\n    noops: 0,\n    projections: 0,\n    rowsCreated: 0,\n    rowsRemoved: 0,\n    rowsReused: 0,\n    tokenSpanRebuilds: 0,\n    visibleEnd: 0,\n    visibleStart: 0,\n  };\n}\n\nexport function createCodeProjector(\n  options: CodeProjectorOptions = {},\n): CodeProjector {\n  let identity: CodeProjectionIdentity | null = null;\n  let lastProjection: LastProjection | null = null;\n  let metrics = options.metrics;\n  let rowHost: HTMLPreElement | null = null;\n  let recycledRows: CodeRowCache[] = [];\n  const rowsByLineIndex = new Map<number, CodeRowCache>();\n  let scrollPageOffset = 0;\n  let visibleRange: VisibleRange | null = null;\n\n  return {\n    getLogicalScrollTop(input) {\n      return getCodeLogicalScrollTop({\n        physicalScrollTop: input.viewport.scrollTop,\n        scrollPageOffset,\n        totalSize: codeTotalSize(input),\n        viewportHeight: codeViewportHeight(input.viewport),\n      });\n    },\n    project(input) {\n      metrics = options.metrics;\n      incrementMetric(metrics, \"projections\");\n\n      if (rowHost !== input.rowHost) {\n        clearRows();\n        rowHost = input.rowHost;\n      }\n\n      const nextIdentity = codeProjectionIdentity(input);\n      if (\n        !identity ||\n        identity.contentIdentity !== nextIdentity.contentIdentity\n      ) {\n        identity = nextIdentity;\n        clearRows();\n      } else {\n        identity = nextIdentity;\n      }\n\n      const totalSize = codeTotalSize({\n        lineCount: input.textLines.length,\n        lineHeight: input.lineHeight,\n        viewport: input.viewport,\n      });\n      const viewportHeight = codeViewportHeight(input.viewport);\n      const previousProjection = lastProjection;\n      const logicalScrollTop = getCodeLogicalScrollTop({\n        physicalScrollTop: input.viewport.scrollTop,\n        scrollPageOffset,\n        totalSize,\n        viewportHeight,\n      });\n      const physicalScrollPosition = resolveCodePhysicalScrollPosition({\n        logicalScrollTop,\n        scrollPageOffset,\n        totalSize,\n        viewportHeight,\n      });\n      const previousScrollPageOffset = scrollPageOffset;\n      scrollPageOffset = physicalScrollPosition.scrollPageOffset;\n\n      if (\n        physicalScrollPosition.physicalScrollTop !== input.viewport.scrollTop\n      ) {\n        input.viewport.scrollTop = physicalScrollPosition.physicalScrollTop;\n      }\n\n      const physicalTotalSize = getCodePhysicalScrollSize({\n        totalSize,\n        viewportHeight,\n      });\n      const totalHeight = `${physicalTotalSize}px`;\n      const fitPerfectly = shouldFitCodePerfectly({\n        previousProjection,\n        logicalScrollTop,\n        viewportHeight,\n      });\n\n      const virtualWindow = getCodeVirtualLineWindow({\n        lineCount: input.textLines.length,\n        lineHeight: input.lineHeight,\n        overscanPx: fitPerfectly\n          ? getCodeFitPerfectlyOverscanPx(input.lineHeight)\n          : CODE_VIEWER_OVERSCAN_PX,\n        paddingStart: CODE_VIEWER_BLOCK_PADDING,\n        scrollTop: logicalScrollTop,\n        viewportHeight,\n      });\n      const visibleLines = virtualWindow.lines;\n      const renderedWindow = codeRenderedWindow({\n        physicalTotalSize,\n        rowHost: input.rowHost,\n        scrollPageOffset,\n        totalSize,\n        viewportHeight,\n        visibleLines,\n      });\n      const nextVisibleRange = codeVisibleRange(visibleLines);\n      const nextProjection = codeLastProjection({\n        input,\n        logicalScrollTop,\n        renderedWindow,\n        scrollPageOffset,\n        totalHeight,\n        visibleRange: nextVisibleRange,\n      });\n      if (\n        lastProjection &&\n        isSameCodeProjection(lastProjection, nextProjection) &&\n        isRenderedDomValid(nextVisibleRange)\n      ) {\n        incrementMetric(metrics, \"noops\");\n        return false;\n      }\n      lastProjection = nextProjection;\n      setMetric(metrics, \"visibleStart\", nextVisibleRange.start);\n      setMetric(metrics, \"visibleEnd\", nextVisibleRange.end);\n\n      syncCodeScrollLayers({\n        renderedWindow,\n        rowHost: input.rowHost,\n        totalHeight,\n      });\n\n      syncVisibleRows({\n        input,\n        nextVisibleRange,\n        previousScrollPageOffset,\n        renderedWindow,\n        totalSize,\n        viewportHeight,\n        visibleLines,\n      });\n      visibleRange = nextVisibleRange;\n      return fitPerfectly;\n    },\n    scrollToLogical(input) {\n      const totalSize = codeTotalSize(input);\n      const viewportHeight = codeViewportHeight(input.viewport);\n      const physicalScrollPosition = resolveCodePhysicalScrollPosition({\n        logicalScrollTop: input.logicalScrollTop,\n        scrollPageOffset,\n        totalSize,\n        viewportHeight,\n      });\n      scrollPageOffset = physicalScrollPosition.scrollPageOffset;\n      if (typeof input.viewport.scrollTo === \"function\") {\n        input.viewport.scrollTo({\n          top: physicalScrollPosition.physicalScrollTop,\n          behavior: input.behavior,\n        });\n      } else {\n        input.viewport.scrollTop = physicalScrollPosition.physicalScrollTop;\n      }\n    },\n    destroy() {\n      clearRows();\n      recycledRows = [];\n      rowHost = null;\n      identity = null;\n      scrollPageOffset = 0;\n    },\n  };\n\n  function syncVisibleRows({\n    input,\n    nextVisibleRange,\n    previousScrollPageOffset,\n    renderedWindow,\n    totalSize,\n    viewportHeight,\n    visibleLines,\n  }: {\n    input: CodeProjectionInput;\n    nextVisibleRange: VisibleRange;\n    previousScrollPageOffset: number;\n    renderedWindow: CodeRenderedWindow;\n    totalSize: number;\n    viewportHeight: number;\n    visibleLines: readonly CodeVirtualLine[];\n  }) {\n    if (\n      previousScrollPageOffset === scrollPageOffset &&\n      applyPartialRender({\n        input,\n        nextVisibleRange,\n        renderedWindow,\n        totalSize,\n        viewportHeight,\n        visibleLines,\n      })\n    ) {\n      return;\n    }\n\n    clearMountedRows();\n    syncVisibleRowOrder({\n      input,\n      renderedWindow,\n      totalSize,\n      viewportHeight,\n      visibleLines,\n    });\n  }\n\n  function syncVisibleRowOrder({\n    input,\n    renderedWindow,\n    totalSize,\n    viewportHeight,\n    visibleLines,\n  }: {\n    input: CodeProjectionInput;\n    renderedWindow: CodeRenderedWindow;\n    totalSize: number;\n    viewportHeight: number;\n    visibleLines: readonly CodeVirtualLine[];\n  }) {\n    let cursor = input.rowHost.firstChild;\n\n    for (const visibleLine of visibleLines) {\n      const row = prepareCodeRow({\n        input,\n        renderedWindow,\n        totalSize,\n        viewportHeight,\n        visibleLine,\n      });\n      if (row !== cursor) {\n        input.rowHost.insertBefore(row, cursor);\n      }\n      cursor = row.nextSibling;\n    }\n\n    while (cursor) {\n      const nextCursor = cursor.nextSibling;\n      cursor.parentNode?.removeChild(cursor);\n      cursor = nextCursor;\n    }\n  }\n\n  function applyPartialRender({\n    input,\n    nextVisibleRange,\n    renderedWindow,\n    totalSize,\n    viewportHeight,\n    visibleLines,\n  }: {\n    input: CodeProjectionInput;\n    nextVisibleRange: VisibleRange;\n    renderedWindow: CodeRenderedWindow;\n    totalSize: number;\n    viewportHeight: number;\n    visibleLines: readonly CodeVirtualLine[];\n  }): boolean {\n    const previousRange = visibleRange;\n    if (!previousRange) return false;\n    if (!isRenderedDomValid(previousRange)) return false;\n\n    const overlapStart = Math.max(previousRange.start, nextVisibleRange.start);\n    const overlapEnd = Math.min(previousRange.end, nextVisibleRange.end);\n    if (overlapStart >= overlapEnd) {\n      return false;\n    }\n\n    removeCodeRowRange(previousRange.start, overlapStart);\n    removeCodeRowRange(overlapEnd, previousRange.end);\n\n    syncVisibleRowOrder({\n      input,\n      renderedWindow,\n      totalSize,\n      viewportHeight,\n      visibleLines,\n    });\n    return true;\n  }\n\n  function clearRows() {\n    for (const row of rowsByLineIndex.values()) {\n      recycleCodeRow(row);\n    }\n    rowHost?.replaceChildren();\n    rowsByLineIndex.clear();\n    lastProjection = null;\n    visibleRange = null;\n  }\n\n  function clearMountedRows() {\n    if (!visibleRange) return;\n    removeCodeRowRange(visibleRange.start, visibleRange.end);\n    visibleRange = null;\n  }\n\n  function removeCodeRowRange(start: number, end: number) {\n    for (\n      let index = Math.max(0, start);\n      index < Math.max(start, end);\n      index++\n    ) {\n      const row = rowsByLineIndex.get(index);\n      if (!row) continue;\n      row.row.remove();\n      incrementMetric(metrics, \"rowsRemoved\");\n      recycleCodeRow(row);\n      rowsByLineIndex.delete(index);\n    }\n  }\n\n  function isRenderedDomValid(range: VisibleRange) {\n    if (!rowHost) return false;\n    const expectedLength = Math.max(0, range.end - range.start);\n    if (rowHost.children.length !== expectedLength) return false;\n\n    for (let offset = 0; offset < expectedLength; offset += 1) {\n      const index = range.start + offset;\n      const element = rowHost.children[offset];\n      const row = rowsByLineIndex.get(index);\n      if (!(element instanceof HTMLDivElement) || row?.row !== element) {\n        return false;\n      }\n      if (element.dataset.lineIndex !== String(index)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  function recycleCodeRow(row: CodeRowCache) {\n    row.contentIdentity = \"\";\n    row.layoutIdentity = \"\";\n    if (recycledRows.length < MAX_RECYCLED_CODE_ROWS) {\n      recycledRows.push(row);\n    }\n  }\n\n  function prepareCodeRow({\n    input,\n    renderedWindow,\n    totalSize,\n    viewportHeight,\n    visibleLine,\n  }: {\n    input: CodeProjectionInput;\n    renderedWindow: CodeRenderedWindow;\n    totalSize: number;\n    viewportHeight: number;\n    visibleLine: CodeVirtualLine;\n  }): HTMLDivElement {\n    const lineNumber = visibleLine.index + 1;\n    const text = input.textLines[visibleLine.index] ?? \"\";\n    const isHighlighted = isLineInRange(lineNumber, input.highlightRange);\n    const layoutIdentity = [\n      lineNumber,\n      input.layoutIdentity,\n      isHighlighted ? \"highlighted\" : \"\",\n    ].join(\"\\u0000\");\n    const contentIdentity = codeRowContentIdentity({\n      syntax: input.syntax,\n      text,\n    });\n\n    let row = rowsByLineIndex.get(visibleLine.index);\n    if (!row) {\n      row = recycledRows.pop();\n      if (row) {\n        incrementMetric(metrics, \"rowsReused\");\n      } else {\n        row = createCodeRow();\n        incrementMetric(metrics, \"rowsCreated\");\n      }\n      rowsByLineIndex.set(visibleLine.index, row);\n    }\n\n    setStyleValue(row.row.style, \"height\", `${visibleLine.size}px`);\n    setStyleValue(\n      row.row.style,\n      \"transform\",\n      `translateY(${\n        getCodePagedLayoutTop({\n          logicalTop: visibleLine.start,\n          scrollPageOffset,\n          totalSize,\n          viewportHeight,\n        }) - renderedWindow.rowOffset\n      }px)`,\n    );\n\n    if (row.layoutIdentity !== layoutIdentity) {\n      row.layoutIdentity = layoutIdentity;\n      patchCodeRowLayout({\n        gutterWidth: input.gutterWidth,\n        isHighlighted,\n        metrics,\n        lineNumber,\n        row,\n      });\n    }\n\n    if (row.contentIdentity !== contentIdentity) {\n      row.contentIdentity = contentIdentity;\n      patchCodeRowContent({\n        metrics,\n        row,\n        syntax: input.syntax,\n        text,\n      });\n    }\n\n    return row.row;\n  }\n}\n\nfunction codeLastProjection({\n  input,\n  logicalScrollTop,\n  renderedWindow,\n  scrollPageOffset,\n  totalHeight,\n  visibleRange,\n}: {\n  input: CodeProjectionInput;\n  logicalScrollTop: number;\n  renderedWindow: CodeRenderedWindow;\n  scrollPageOffset: number;\n  totalHeight: string;\n  visibleRange: VisibleRange;\n}): LastProjection {\n  return {\n    contentIdentity: input.contentIdentity,\n    highlightIdentity: codeHighlightIdentity(input.highlightRange),\n    horizontalScrollLeft: input.viewport.scrollLeft,\n    layoutIdentity: input.layoutIdentity,\n    logicalScrollTop,\n    renderedWindowHeight: renderedWindow.height,\n    renderedWindowStickyOffset: renderedWindow.stickyOffset,\n    renderedWindowTop: renderedWindow.top,\n    scrollPageOffset,\n    syntaxIdentity: input.syntaxIdentity,\n    totalHeight,\n    visibleEnd: visibleRange.end,\n    visibleStart: visibleRange.start,\n  };\n}\n\nfunction codeHighlightIdentity(range: NormalizedTextLineRange | null) {\n  return range ? `${range.start}:${range.end}` : \"\";\n}\n\nfunction isSameCodeProjection(previous: LastProjection, next: LastProjection) {\n  return (\n    previous.contentIdentity === next.contentIdentity &&\n    previous.highlightIdentity === next.highlightIdentity &&\n    previous.horizontalScrollLeft === next.horizontalScrollLeft &&\n    previous.layoutIdentity === next.layoutIdentity &&\n    previous.renderedWindowHeight === next.renderedWindowHeight &&\n    previous.renderedWindowStickyOffset === next.renderedWindowStickyOffset &&\n    previous.renderedWindowTop === next.renderedWindowTop &&\n    previous.scrollPageOffset === next.scrollPageOffset &&\n    previous.syntaxIdentity === next.syntaxIdentity &&\n    previous.totalHeight === next.totalHeight &&\n    previous.visibleEnd === next.visibleEnd &&\n    previous.visibleStart === next.visibleStart\n  );\n}\n\nfunction codeProjectionIdentity({\n  contentIdentity,\n  layoutIdentity,\n  syntaxIdentity,\n}: CodeProjectionIdentity): CodeProjectionIdentity {\n  return {\n    contentIdentity,\n    layoutIdentity,\n    syntaxIdentity,\n  };\n}\n\nfunction codeVisibleRange(\n  visibleLines: readonly CodeVirtualLine[],\n): VisibleRange {\n  const start = visibleLines[0]?.index ?? 0;\n  const end = visibleLines.length\n    ? visibleLines[visibleLines.length - 1]!.index + 1\n    : start;\n  return { end, start };\n}\n\nfunction codeTotalSize({\n  lineCount,\n  lineHeight,\n}: {\n  lineCount: number;\n  lineHeight: number;\n  viewport?: HTMLDivElement;\n}) {\n  return getCodeVirtualTotalSize({\n    lineCount,\n    lineHeight,\n  });\n}\n\nfunction codeViewportHeight(viewport: HTMLDivElement) {\n  return viewport.clientHeight || CODE_VIEWER_INITIAL_VIEWPORT_HEIGHT;\n}\n\nfunction shouldFitCodePerfectly({\n  previousProjection,\n  logicalScrollTop,\n  viewportHeight,\n}: {\n  previousProjection: LastProjection | null;\n  logicalScrollTop: number;\n  viewportHeight: number;\n}) {\n  if (!previousProjection) return false;\n  return (\n    Math.abs(logicalScrollTop - previousProjection.logicalScrollTop) >\n    viewportHeight + CODE_VIEWER_OVERSCAN_PX * 2\n  );\n}\n\nfunction getCodeFitPerfectlyOverscanPx(lineHeight: number) {\n  return Math.max(\n    CODE_VIEWER_BLOCK_PADDING,\n    Number.isFinite(lineHeight) ? lineHeight : 0,\n  );\n}\n\nfunction codeRenderedWindow({\n  physicalTotalSize,\n  rowHost,\n  scrollPageOffset,\n  totalSize,\n  viewportHeight,\n  visibleLines,\n}: {\n  physicalTotalSize: number;\n  rowHost: HTMLPreElement;\n  scrollPageOffset: number;\n  totalSize: number;\n  viewportHeight: number;\n  visibleLines: readonly CodeVirtualLine[];\n}): CodeRenderedWindow {\n  const renderedWindowElement = getCodeRenderedWindowElement(rowHost);\n  if (!renderedWindowElement) {\n    return {\n      height: physicalTotalSize,\n      rowOffset: 0,\n      stickyOffset: 0,\n      top: 0,\n    };\n  }\n\n  const firstLine = visibleLines[0];\n  const lastLine = visibleLines[visibleLines.length - 1];\n  if (!firstLine || !lastLine) {\n    return {\n      height: 0,\n      rowOffset: 0,\n      stickyOffset: 0,\n      top: 0,\n    };\n  }\n\n  const top = getCodePagedLayoutTop({\n    logicalTop: firstLine.start,\n    scrollPageOffset,\n    totalSize,\n    viewportHeight,\n  });\n  const bottom = getCodePagedLayoutTop({\n    logicalTop: lastLine.start + lastLine.size,\n    scrollPageOffset,\n    totalSize,\n    viewportHeight,\n  });\n  const height = Math.max(0, bottom - top);\n\n  return {\n    height,\n    rowOffset: top,\n    stickyOffset: Math.min(0, viewportHeight - height),\n    top,\n  };\n}\n\nfunction syncCodeScrollLayers({\n  renderedWindow,\n  rowHost,\n  totalHeight,\n}: {\n  renderedWindow: CodeRenderedWindow;\n  rowHost: HTMLPreElement;\n  totalHeight: string;\n}) {\n  const renderedWindowElement = getCodeRenderedWindowElement(rowHost);\n  if (!renderedWindowElement) {\n    setStyleValue(rowHost.style, \"height\", totalHeight);\n    if (rowHost.parentElement instanceof HTMLElement) {\n      setStyleValue(rowHost.parentElement.style, \"height\", totalHeight);\n    }\n    return;\n  }\n\n  const height = `${renderedWindow.height}px`;\n  const top = `${renderedWindow.top}px`;\n  const stickyOffset = `${renderedWindow.stickyOffset}px`;\n  const offsetElement = getCodeRenderedWindowOffsetElement(\n    renderedWindowElement,\n  );\n\n  setStyleValue(rowHost.style, \"height\", height);\n  if (offsetElement) setStyleValue(offsetElement.style, \"height\", top);\n  setStyleValue(renderedWindowElement.style, \"height\", height);\n  setStyleValue(renderedWindowElement.style, \"margin-top\", \"\");\n  setStyleValue(renderedWindowElement.style, \"top\", stickyOffset);\n  setStyleValue(renderedWindowElement.style, \"bottom\", stickyOffset);\n\n  if (renderedWindowElement.parentElement instanceof HTMLElement) {\n    setStyleValue(renderedWindowElement.parentElement.style, \"height\", totalHeight);\n  }\n}\n\nfunction getCodeRenderedWindowElement(rowHost: HTMLPreElement) {\n  const element = rowHost.parentElement;\n  if (\n    element instanceof HTMLElement &&\n    element.dataset.codeRenderWindow != null\n  ) {\n    return element;\n  }\n  return null;\n}\n\nfunction getCodeRenderedWindowOffsetElement(renderedWindowElement: HTMLElement) {\n  const element = renderedWindowElement.previousElementSibling;\n  if (\n    element instanceof HTMLElement &&\n    element.dataset.codeRenderOffset != null\n  ) {\n    return element;\n  }\n  return null;\n}\n\nfunction codeRowContentIdentity({\n  syntax,\n  text,\n}: {\n  syntax: CodeSyntax;\n  text: string;\n}) {\n  return [text, syntax.identity, syntax.getLineVersion(text)].join(\"\\u0000\");\n}\n\nfunction createCodeRow(): CodeRowCache {\n  const row = document.createElement(\"div\");\n  const gutterSpan = document.createElement(\"span\");\n  const contentSpan = document.createElement(\"span\");\n\n  row.className = codeRowClassName();\n  row.style.position = \"absolute\";\n  row.style.top = \"0\";\n  row.style.left = \"0\";\n\n  gutterSpan.className =\n    \"sticky left-0 z-10 flex-shrink-0 border-r px-2 pr-3 text-right text-muted-foreground/60 select-none\";\n  // The gutter must paint an opaque background so horizontally-scrolled code\n  // never shows through the sticky line-number column. The theme's `--muted`\n  // token is alpha-based (translucent), so the fill is mixed from the opaque\n  // `--foreground`/`--background` pair instead.\n  gutterSpan.style.backgroundColor = CODE_GUTTER_BACKGROUND;\n  gutterSpan.dataset.codeGutter = \"\";\n  gutterSpan.setAttribute(\"aria-hidden\", \"true\");\n  contentSpan.className = \"whitespace-pre px-2\";\n\n  row.append(gutterSpan, contentSpan);\n\n  return {\n    contentIdentity: \"\",\n    contentSpan,\n    gutterSpan,\n    layoutIdentity: \"\",\n    row,\n  };\n}\n\nfunction patchCodeRowLayout({\n  gutterWidth,\n  isHighlighted,\n  metrics,\n  lineNumber,\n  row,\n}: {\n  gutterWidth: string;\n  isHighlighted: boolean;\n  metrics: CodeProjectionMetrics | undefined;\n  lineNumber: number;\n  row: CodeRowCache;\n}) {\n  incrementMetric(metrics, \"layoutPatches\");\n  row.row.dataset.lineIndex = String(lineNumber - 1);\n  row.row.dataset.lineNumber = String(lineNumber);\n  setStyleValue(row.gutterSpan.style, \"width\", gutterWidth);\n  setTextContent(row.gutterSpan, String(lineNumber));\n\n  // Highlighted range: a continuous opaque band (no per-row borders) with a\n  // single left accent stripe on the gutter that merges across adjacent lines.\n  setStyleValue(\n    row.row.style,\n    \"background-color\",\n    isHighlighted ? CODE_HIGHLIGHT_BACKGROUND : \"\",\n  );\n  setStyleValue(\n    row.gutterSpan.style,\n    \"background-color\",\n    isHighlighted ? CODE_HIGHLIGHT_BACKGROUND : CODE_GUTTER_BACKGROUND,\n  );\n  setStyleValue(\n    row.gutterSpan.style,\n    \"box-shadow\",\n    isHighlighted ? CODE_HIGHLIGHT_ACCENT_SHADOW : \"\",\n  );\n  setStyleValue(\n    row.gutterSpan.style,\n    \"color\",\n    isHighlighted ? CODE_HIGHLIGHT_NUMBER_COLOR : \"\",\n  );\n}\n\nfunction patchCodeRowContent({\n  metrics,\n  row,\n  syntax,\n  text,\n}: {\n  metrics: CodeProjectionMetrics | undefined;\n  row: CodeRowCache;\n  syntax: CodeSyntax;\n  text: string;\n}) {\n  incrementMetric(metrics, \"contentPatches\");\n  const renderText = getCodeLineRenderText(text);\n  if (renderText.isTruncated) {\n    row.row.dataset.codeLineTruncated = \"\";\n    row.contentSpan.dataset.codeLineTruncated = \"\";\n    row.contentSpan.setAttribute(\n      \"aria-label\",\n      `${text.length} character line preview; ${renderText.omittedCharacterCount} middle characters omitted.`,\n    );\n    row.contentSpan.title =\n      \"Long line preview. Copying the selected row copies the complete line.\";\n  } else {\n    delete row.row.dataset.codeLineTruncated;\n    delete row.contentSpan.dataset.codeLineTruncated;\n    row.contentSpan.removeAttribute(\"aria-label\");\n    row.contentSpan.removeAttribute(\"title\");\n  }\n\n  patchCodeContent(\n    row.contentSpan,\n    renderText.isTruncated ? null : syntax.getLineTokens(text),\n    renderText.text,\n    metrics,\n  );\n}\n\nfunction patchCodeContent(\n  contentSpan: HTMLSpanElement,\n  leaves: readonly CodeTokenLeaf[] | null,\n  text: string,\n  metrics: CodeProjectionMetrics | undefined,\n) {\n  if (text === \"\") {\n    contentSpan.replaceChildren();\n    contentSpan.textContent = \" \";\n    return;\n  }\n  if (!leaves) {\n    setTextContent(contentSpan, text);\n    return;\n  }\n\n  contentSpan.replaceChildren();\n  incrementMetric(metrics, \"tokenSpanRebuilds\");\n  const fragment = document.createDocumentFragment();\n  for (const leaf of leaves) {\n    if (!leaf.kind) {\n      fragment.append(document.createTextNode(leaf.text));\n      continue;\n    }\n    const span = document.createElement(\"span\");\n    span.className = \"cv-token-\" + leaf.kind;\n    span.textContent = leaf.text;\n    fragment.append(span);\n  }\n  contentSpan.append(fragment);\n}\n\nfunction codeRowClassName() {\n  return \"absolute top-0 left-0 flex min-w-full\";\n}\n\nfunction setStyleValue(\n  style: CSSStyleDeclaration,\n  propertyName: string,\n  value: string,\n) {\n  if (style.getPropertyValue(propertyName) !== value) {\n    style.setProperty(propertyName, value);\n  }\n}\n\nfunction setTextContent(element: HTMLElement, text: string) {\n  if (element.textContent !== text) {\n    element.textContent = text;\n  }\n}\n\nfunction incrementMetric(\n  metrics: CodeProjectionMetrics | undefined,\n  key: keyof CodeProjectionMetrics,\n) {\n  if (!metrics) return;\n  metrics[key] += 1;\n}\n\nfunction setMetric(\n  metrics: CodeProjectionMetrics | undefined,\n  key: keyof CodeProjectionMetrics,\n  value: number,\n) {\n  if (!metrics) return;\n  metrics[key] = value;\n}\n",
      "type": "registry:ui",
      "target": "@ui/code-viewer-projector.ts"
    },
    {
      "path": "registry/new-york-v4/ui/code-viewer-projection-scheduler.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\n\nimport { joinEffectKey } from \"@/lib/effect-key\";\nimport {\n  restoreTextViewerScrollInteractions,\n  suspendTextViewerScrollInteractions,\n  TEXT_SCROLL_INTERACTION_RESTORE_DELAY_MS,\n  type ScrollInteractionSnapshot,\n} from \"./text-viewer-scroll-interactions\";\n\nexport function useCodeProjectionScheduler({\n  project,\n  rowHostRef,\n  viewportRef,\n}: {\n  project: () => boolean | void;\n  rowHostRef?: React.RefObject<HTMLPreElement | null>;\n  viewportRef: React.RefObject<HTMLDivElement | null>;\n}) {\n  const scheduledProjectionRef = React.useRef(0);\n  const scrollInteractionRestoreRef = React.useRef(0);\n  const scrollInteractionSnapshotRef =\n    React.useRef<ScrollInteractionSnapshot | null>(null);\n\n  const scheduleProjection = React.useCallback(() => {\n    if (scheduledProjectionRef.current) return;\n    const runProjection = () => {\n      scheduledProjectionRef.current = requestAnimationFrame(() => {\n        scheduledProjectionRef.current = 0;\n        if (project()) {\n          runProjection();\n        }\n      });\n    };\n    runProjection();\n  }, [project]);\n\n  useKeyedMountEffect(\n    joinEffectKey([\"code-project\", project, scheduleProjection]),\n    () => {\n      if (project()) {\n        scheduleProjection();\n      }\n      return () => {\n        cancelScheduledProjection(scheduledProjectionRef);\n      };\n    },\n  );\n\n  useKeyedMountEffect(\n    joinEffectKey([\n      \"code-project-listeners\",\n      scheduleProjection,\n      rowHostRef,\n      viewportRef,\n    ]),\n    () => {\n      const viewport = viewportRef.current;\n      if (!viewport) return;\n\n      const handleScroll = () => {\n        suspendTextViewerScrollInteractions({\n          getInteractionTarget: () => rowHostRef?.current,\n          getOverflowTarget: () => rowHostRef?.current?.parentElement,\n          snapshotRef: scrollInteractionSnapshotRef,\n        });\n        if (scrollInteractionRestoreRef.current) {\n          window.clearTimeout(scrollInteractionRestoreRef.current);\n        }\n        scrollInteractionRestoreRef.current = window.setTimeout(() => {\n          scrollInteractionRestoreRef.current = 0;\n          restoreTextViewerScrollInteractions(scrollInteractionSnapshotRef);\n        }, TEXT_SCROLL_INTERACTION_RESTORE_DELAY_MS);\n        scheduleProjection();\n      };\n\n      viewport.addEventListener(\"scroll\", handleScroll, {\n        passive: true,\n      });\n      const observer =\n        typeof ResizeObserver === \"undefined\"\n          ? null\n          : new ResizeObserver(scheduleProjection);\n      observer?.observe(viewport);\n\n      return () => {\n        viewport.removeEventListener(\"scroll\", handleScroll);\n        observer?.disconnect();\n        cancelScheduledProjection(scheduledProjectionRef);\n        if (scrollInteractionRestoreRef.current) {\n          window.clearTimeout(scrollInteractionRestoreRef.current);\n          scrollInteractionRestoreRef.current = 0;\n        }\n        restoreTextViewerScrollInteractions(scrollInteractionSnapshotRef);\n      };\n    },\n  );\n}\n\nfunction cancelScheduledProjection(ref: React.MutableRefObject<number>) {\n  if (!ref.current) return;\n  cancelAnimationFrame(ref.current);\n  ref.current = 0;\n}\n",
      "type": "registry:ui",
      "target": "@ui/code-viewer-projection-scheduler.ts"
    },
    {
      "path": "registry/new-york-v4/ui/code-viewer-layout.ts",
      "content": "import type { NormalizedTextLineRange } from \"./line-ranges\";\n\nexport const LINE_SCROLL_HEADROOM = 64;\n\nexport interface LineRangeMetrics {\n  startLine: number;\n  endLine: number;\n  lineHeight: number;\n  viewportHeight: number;\n  paddingStart?: number;\n}\n\nexport function scrollTopForLineRangeMetrics({\n  startLine,\n  endLine,\n  lineHeight,\n  viewportHeight,\n  paddingStart = 0,\n}: LineRangeMetrics) {\n  const rangeTop = paddingStart + (startLine - 1) * lineHeight;\n  const rangeBottom = paddingStart + endLine * lineHeight;\n  const rangeHeight = rangeBottom - rangeTop;\n  const targetTop =\n    rangeHeight <= viewportHeight\n      ? rangeTop - (viewportHeight - rangeHeight) / 2\n      : rangeTop - LINE_SCROLL_HEADROOM;\n\n  return Math.max(0, targetTop);\n}\n\nexport function scrollLineRangeMetricsIntoView({\n  viewportElement,\n  range,\n  lineHeight,\n  paddingStart,\n  options,\n}: {\n  viewportElement: HTMLDivElement | null;\n  range: NormalizedTextLineRange | null;\n  lineHeight: number;\n  paddingStart?: number;\n  options?: ScrollToOptions;\n}) {\n  if (!viewportElement || !range) return;\n  if (typeof viewportElement.scrollTo !== \"function\") return;\n\n  viewportElement.scrollTo({\n    top: scrollTopForLineRangeMetrics({\n      startLine: range.start,\n      endLine: range.end,\n      lineHeight,\n      paddingStart,\n      viewportHeight: viewportElement.clientHeight,\n    }),\n    behavior: \"smooth\",\n    ...options,\n  });\n}\n",
      "type": "registry:ui",
      "target": "@ui/code-viewer-layout.ts"
    },
    {
      "path": "registry/new-york-v4/ui/code-viewer-scale.ts",
      "content": "export const CODE_VIEWER_BASE_FONT_PX = 12;\nexport const CODE_VIEWER_BASE_LINE_PX = 20;\nexport const CODE_VIEWER_MIN_SCALE = 0.1;\nexport const CODE_VIEWER_MAX_SCALE = 5;\nexport const CODE_VIEWER_OVERSCAN_PX = 1000;\nexport const CODE_VIEWER_BLOCK_PADDING = 8;\nexport const CODE_VIEWER_INITIAL_VIEWPORT_HEIGHT = 600;\nexport const CODE_VIEWER_SCROLL_REBASE_CONTAINER_PX = 12_000_000;\nexport const CODE_VIEWER_SCROLL_REBASE_TRIGGER_PX = 1_000_000;\nexport const CODE_VIEWER_SCROLL_REBASE_TARGET_PX = 2_000_000;\nexport const CODE_VIEWER_SCROLL_REBASE_TARGET_BOTTOM_PX =\n  CODE_VIEWER_SCROLL_REBASE_CONTAINER_PX - CODE_VIEWER_SCROLL_REBASE_TARGET_PX;\nexport const CODE_VIEWER_SCROLL_REBASE_THRESHOLD_PX =\n  CODE_VIEWER_SCROLL_REBASE_CONTAINER_PX -\n  CODE_VIEWER_SCROLL_REBASE_TRIGGER_PX;\nexport const CODE_VIEWER_LINE_CHECKPOINT_INTERVAL = 4096;\n\nexport function clampCodeViewerScale(value: number) {\n  return Math.min(\n    CODE_VIEWER_MAX_SCALE,\n    Math.max(CODE_VIEWER_MIN_SCALE, value),\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/code-viewer-scale.ts"
    },
    {
      "path": "registry/new-york-v4/ui/code-viewer-types.ts",
      "content": "import type {\n  BlobViewerSource,\n  TextSource,\n  UrlViewerSource,\n} from \"@/lib/viewer-source\";\n\nimport type { TextLineRange } from \"./text-viewer-ranges\";\nimport type { TextViewerBounds } from \"./text-viewer-resource\";\n\nexport type CodeLineRange = TextLineRange;\n\nexport interface CodeViewerHandle {\n  scrollToLineRange: (range: CodeLineRange, options?: ScrollToOptions) => void;\n  getViewportElement: () => HTMLDivElement | null;\n}\n\nexport type CodeDocumentSource =\n  | UrlViewerSource\n  | BlobViewerSource\n  | TextSource;\n\nexport interface CodeViewerProps extends TextViewerBounds {\n  source: CodeDocumentSource;\n  className?: string;\n  controls?: boolean;\n  download?: boolean;\n  /** 1-based inclusive line range to highlight, or null. */\n  highlight?: CodeLineRange | null;\n  /** Drop the outer border/rounded/background so the viewer fills its container. */\n  bare?: boolean;\n}\n",
      "type": "registry:ui",
      "target": "@ui/code-viewer-types.ts"
    },
    {
      "path": "registry/new-york-v4/ui/code-viewer-syntax.ts",
      "content": "import type { ViewerResource } from \"@/lib/viewer-resource\";\n\nimport {\n  type CodeSyntaxWorkerRequest,\n  type CodeSyntaxWorkerResponse,\n  type CodeTokenLeaf,\n  shouldTokenizeCodeLine,\n} from \"./code-viewer-syntax-protocol\";\nimport {\n  ensureCodePrismLanguage,\n  isCodePrismLanguageLoaded,\n  isCodePrismLanguageSupported,\n  tokenizeCodeLine,\n} from \"./code-viewer-syntax-prism\";\nimport { createCodeSyntaxWorker } from \"./code-viewer-syntax-worker\";\n\nexport type { CodeTokenLeaf } from \"./code-viewer-syntax-protocol\";\n\nexport type CodeSyntax = {\n  identity: string;\n  destroy?: () => void;\n  getLineVersion(line: string): number;\n  getLineTokens(line: string): readonly CodeTokenLeaf[] | null;\n  preload?: () => Promise<void>;\n};\n\nexport type CodeSyntaxMode = \"auto\" | \"main-thread\" | \"worker\";\n\nexport type CodeSyntaxOptions = {\n  deferTokens?: boolean;\n  onTokensChanged?: () => void;\n  syntaxMode?: CodeSyntaxMode;\n  createWorker?: () => Worker;\n};\n\nconst CODE_DEFERRED_TOKENIZE_BATCH_SIZE = 12;\nconst CODE_DEFERRED_TOKENIZE_BUDGET_MS = 6;\nexport const CODE_GLOBAL_TOKEN_CACHE_LIMIT = 1024;\nconst CODE_WORKER_TOKENIZE_BATCH_SIZE = 64;\n\ntype CodeSyntaxIdleWindow = Window &\n  typeof globalThis & {\n    cancelIdleCallback?: Window[\"cancelIdleCallback\"];\n    requestIdleCallback?: Window[\"requestIdleCallback\"];\n  };\n\ntype CodeSyntaxTaskHandle =\n  | { kind: \"idle\"; id: number }\n  | { kind: \"timeout\"; id: number };\n\ntype CodeSyntaxTaskDeadline = {\n  timeRemaining?: () => number;\n};\n\ntype CodeSyntaxNotifyHandle = { id: number };\n\ntype CodeSyntaxWorkerFactory = () => Worker;\n\ntype CodeSyntaxWorkerRelease = () => void;\n\ntype CodeSyntaxWorkerSubscription = {\n  isReleased: boolean;\n  onDone: () => void;\n  onError: (line: string) => void;\n  onTokens: (line: string, tokens: readonly CodeTokenLeaf[]) => void;\n  pendingCount: number;\n};\n\ntype CodeSyntaxWorkerJob = {\n  key: string;\n  languageId: string;\n  line: string;\n  status: \"active\" | \"pending\";\n  subscribers: Set<CodeSyntaxWorkerSubscription>;\n};\n\ntype CodeSyntaxWorkerBatch = {\n  generation: number;\n  jobs: CodeSyntaxWorkerJob[];\n  languageId: string;\n  requestId: number;\n};\n\ntype CodeSyntaxWorkerSlot = {\n  activeBatch: CodeSyntaxWorkerBatch | null;\n  worker: Worker;\n};\n\ntype CodeSyntaxWorkerPool = {\n  createWorker: CodeSyntaxWorkerFactory;\n  dispatchHandle: CodeSyntaxTaskHandle | null;\n  jobsByKey: Map<string, CodeSyntaxWorkerJob>;\n  maxWorkers: number;\n  pendingJobs: Map<string, CodeSyntaxWorkerJob>;\n  requestId: number;\n  slots: CodeSyntaxWorkerSlot[];\n};\n\nconst globalTokenCache = new Map<string, readonly CodeTokenLeaf[]>();\nconst globalWorkerPoolsByFactory = new WeakMap<\n  CodeSyntaxWorkerFactory,\n  CodeSyntaxWorkerPool\n>();\nconst globalWorkerPools = new Set<CodeSyntaxWorkerPool>();\nconst CODE_GLOBAL_WORKER_POOL_SIZE = 2;\n\n// File extension -> Prism language id. Prism does not map extensions to\n// languages, so the viewer keeps the small explicit map.\nconst LANGUAGE_BY_EXTENSION: Record<string, string> = {\n  json: \"json\",\n  json5: \"json\",\n  js: \"javascript\",\n  mjs: \"javascript\",\n  cjs: \"javascript\",\n  jsx: \"jsx\",\n  ts: \"typescript\",\n  mts: \"typescript\",\n  cts: \"typescript\",\n  tsx: \"tsx\",\n  py: \"python\",\n  yaml: \"yaml\",\n  yml: \"yaml\",\n  sh: \"bash\",\n  bash: \"bash\",\n  zsh: \"bash\",\n  sql: \"sql\",\n  go: \"go\",\n  rs: \"rust\",\n  java: \"java\",\n  md: \"markdown\",\n  markdown: \"markdown\",\n  css: \"css\",\n  html: \"markup\",\n  htm: \"markup\",\n  xml: \"markup\",\n  svg: \"markup\",\n};\n\n// MIME -> Prism language id, used only for inline sources with no extension.\nconst LANGUAGE_BY_MIME: Record<string, string> = {\n  \"application/json\": \"json\",\n  \"text/javascript\": \"javascript\",\n  \"application/javascript\": \"javascript\",\n  \"text/typescript\": \"typescript\",\n  \"application/typescript\": \"typescript\",\n  \"text/x-python\": \"python\",\n  \"application/x-python\": \"python\",\n  \"text/yaml\": \"yaml\",\n  \"application/yaml\": \"yaml\",\n  \"application/x-yaml\": \"yaml\",\n  \"text/x-sh\": \"bash\",\n  \"application/x-sh\": \"bash\",\n  \"application/sql\": \"sql\",\n  \"text/markdown\": \"markdown\",\n  \"text/css\": \"css\",\n  \"text/html\": \"markup\",\n  \"application/xml\": \"markup\",\n  \"text/xml\": \"markup\",\n};\n\nexport const CODE_VIEWER_SYNTAX_STYLE = `\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\nexport function createCodeSyntax(\n  resource: ViewerResource,\n  options: CodeSyntaxOptions = {},\n): CodeSyntax {\n  const detectedLanguageId = codeLanguageId(resource);\n  if (!detectedLanguageId || !isCodePrismLanguageSupported(detectedLanguageId)) {\n    return {\n      identity: \"plain\",\n      getLineVersion: () => 0,\n      getLineTokens: () => null,\n    };\n  }\n  const languageId = detectedLanguageId;\n\n  const tokenVersions = new Map<string, number>();\n  const asyncTokenSnapshots = new Map<string, readonly CodeTokenLeaf[]>();\n  const pendingLines = new Set<string>();\n  const workerReleases = new Set<CodeSyntaxWorkerRelease>();\n  const workerFactory = options.createWorker ?? createCodeSyntaxWorker;\n  const useWorker = shouldUseWorker(options);\n  let flushHandle: CodeSyntaxTaskHandle | null = null;\n  let notifyHandle: CodeSyntaxNotifyHandle | null = null;\n  let grammarPromise: Promise<void> | null = null;\n  let isGrammarReady = isCodePrismLanguageLoaded(languageId);\n  let isGrammarFailed = false;\n  let isWorkerFailed = false;\n  let isDestroyed = false;\n  let hasPendingTokenChanges = false;\n\n  if (!useWorker) {\n    void preloadMainThreadGrammar();\n  }\n\n  return {\n    destroy,\n    getLineVersion,\n    getLineTokens,\n    identity: languageId,\n    preload: preloadMainThreadGrammar,\n  };\n\n  function destroy() {\n    isDestroyed = true;\n    pendingLines.clear();\n    cancelFlush();\n    cancelNotification();\n    for (const releaseWorker of workerReleases) {\n      releaseWorker();\n    }\n    workerReleases.clear();\n  }\n\n  function getLineVersion(line: string) {\n    return tokenVersions.get(line) ?? 0;\n  }\n\n  function getLineTokens(line: string) {\n    if (isDestroyed) return null;\n    if (!shouldTokenizeCodeLine(line) || isGrammarFailed) return null;\n\n    const cachedTokens = getGlobalLineTokens(languageId, line);\n    if (cachedTokens) return cachedTokens;\n\n    if (!shouldTokenizeInWorker() && isGrammarReady && !options.deferTokens) {\n      const tokens = tokenizeCodeLine(languageId, line);\n      if (tokens) {\n        return setGlobalLineTokens(languageId, line, tokens);\n      }\n    }\n\n    pendingLines.add(line);\n    scheduleTokenization();\n    return null;\n  }\n\n  function scheduleTokenization() {\n    if (isDestroyed || pendingLines.size === 0) return;\n    if (shouldTokenizeInWorker()) {\n      scheduleWorkerTokenization();\n      return;\n    }\n    scheduleMainThreadTokenization();\n  }\n\n  function scheduleWorkerTokenization() {\n    if (!shouldTokenizeInWorker() || flushHandle) return;\n    flushHandle = scheduleCodeSyntaxTask(() => {\n      flushHandle = null;\n      flushWorkerTokenBatch();\n    });\n  }\n\n  function flushWorkerTokenBatch() {\n    if (!shouldTokenizeInWorker() || isDestroyed) return;\n\n    const lines = takePendingLines(CODE_WORKER_TOKENIZE_BATCH_SIZE);\n    if (lines.length === 0) return;\n\n    const uncachedLines: string[] = [];\n    let didCacheTokens = false;\n    for (const line of lines) {\n      const cachedTokens = getGlobalLineTokens(languageId, line);\n      if (cachedTokens) {\n        didCacheTokens =\n          cacheAsyncLineTokens(line, cachedTokens) || didCacheTokens;\n      } else {\n        uncachedLines.push(line);\n      }\n    }\n    if (didCacheTokens) queueTokenChangeNotification();\n    if (uncachedLines.length === 0) {\n      scheduleTokenization();\n      return;\n    }\n\n    let didFinishWorkerRequest = false;\n    let releaseWorker: CodeSyntaxWorkerRelease | null = null;\n    releaseWorker = requestCodeSyntaxWorkerTokens({\n      createWorker: workerFactory,\n      languageId,\n      lines: uncachedLines,\n      onDone: () => {\n        didFinishWorkerRequest = true;\n        if (releaseWorker) workerReleases.delete(releaseWorker);\n        scheduleTokenization();\n      },\n      onError: (line) => {\n        if (isDestroyed) return;\n        isWorkerFailed = true;\n        pendingLines.add(line);\n        scheduleMainThreadTokenization();\n      },\n      onTokens: (line, tokens) => {\n        if (isDestroyed) return;\n        if (cacheAsyncLineTokens(line, tokens)) queueTokenChangeNotification();\n      },\n    });\n    if (!didFinishWorkerRequest) workerReleases.add(releaseWorker);\n    scheduleTokenization();\n  }\n\n  function scheduleMainThreadTokenization() {\n    if (isDestroyed || flushHandle || isGrammarFailed) return;\n    if (!isGrammarReady) {\n      void preloadMainThreadGrammar();\n      return;\n    }\n    flushHandle = scheduleCodeSyntaxTask(flushMainThreadTokenBatch);\n  }\n\n  async function preloadMainThreadGrammar() {\n    if (shouldTokenizeInWorker() || isGrammarReady || isGrammarFailed) return;\n    if (!grammarPromise) {\n      grammarPromise = ensureCodePrismLanguage(languageId)\n        .then(() => {\n          if (isDestroyed) return;\n          isGrammarReady = true;\n          scheduleMainThreadTokenization();\n        })\n        .catch(() => {\n          if (isDestroyed) return;\n          isGrammarFailed = true;\n          pendingLines.clear();\n        });\n    }\n    await grammarPromise;\n  }\n\n  function flushMainThreadTokenBatch(deadline?: CodeSyntaxTaskDeadline) {\n    flushHandle = null;\n    if (isDestroyed || isGrammarFailed || !isGrammarReady) return;\n\n    const startedAt = codeSyntaxNow();\n    let processedLineCount = 0;\n    while (pendingLines.size > 0) {\n      const pendingLine = pendingLines.values().next().value;\n      if (pendingLine == null) break;\n      pendingLines.delete(pendingLine);\n\n      const cachedTokens = getGlobalLineTokens(languageId, pendingLine);\n      if (cachedTokens) {\n        hasPendingTokenChanges =\n          cacheAsyncLineTokens(pendingLine, cachedTokens) ||\n          hasPendingTokenChanges;\n      } else {\n        const tokens = tokenizeCodeLine(languageId, pendingLine);\n        if (tokens) {\n          hasPendingTokenChanges =\n            cacheAsyncLineTokens(pendingLine, tokens) || hasPendingTokenChanges;\n        }\n      }\n\n      processedLineCount += 1;\n      if (\n        shouldYieldDeferredTokenization({\n          deadline,\n          processedLineCount,\n          startedAt,\n        })\n      ) {\n        break;\n      }\n    }\n\n    if (pendingLines.size > 0) {\n      scheduleMainThreadTokenization();\n      return;\n    }\n\n    if (hasPendingTokenChanges) {\n      hasPendingTokenChanges = false;\n      queueTokenChangeNotification();\n    }\n  }\n\n  function takePendingLines(limit: number) {\n    const lines: string[] = [];\n    for (const line of pendingLines) {\n      pendingLines.delete(line);\n      lines.push(line);\n      if (lines.length >= limit) break;\n    }\n    return lines;\n  }\n\n  function cacheAsyncLineTokens(\n    line: string,\n    tokens: readonly CodeTokenLeaf[],\n  ) {\n    const cachedTokens = setGlobalLineTokens(languageId, line, tokens);\n    const previousTokens = asyncTokenSnapshots.get(line);\n    if (\n      previousTokens &&\n      areCodeTokenLeavesEqual(previousTokens, cachedTokens)\n    ) {\n      return false;\n    }\n\n    asyncTokenSnapshots.set(line, cachedTokens);\n    tokenVersions.set(line, (tokenVersions.get(line) ?? 0) + 1);\n    return true;\n  }\n\n  function shouldTokenizeInWorker() {\n    return useWorker && !isWorkerFailed;\n  }\n\n  function queueTokenChangeNotification() {\n    if (isDestroyed || notifyHandle) return;\n    notifyHandle = scheduleCodeSyntaxNotification(() => {\n      notifyHandle = null;\n      if (!isDestroyed) options.onTokensChanged?.();\n    });\n  }\n\n  function cancelFlush() {\n    if (!flushHandle) return;\n    cancelCodeSyntaxTask(flushHandle);\n    flushHandle = null;\n  }\n\n  function cancelNotification() {\n    if (!notifyHandle) return;\n    cancelCodeSyntaxNotification(notifyHandle);\n    notifyHandle = null;\n  }\n}\n\nexport function clearCodeSyntaxGlobalTokenCacheForTests() {\n  globalTokenCache.clear();\n  for (const pool of globalWorkerPools) {\n    cancelCodeSyntaxWorkerPoolDispatch(pool);\n    for (const slot of pool.slots) {\n      slot.worker.terminate();\n    }\n    pool.slots = [];\n    pool.pendingJobs.clear();\n    pool.jobsByKey.clear();\n  }\n}\n\nfunction shouldUseWorker(options: CodeSyntaxOptions) {\n  if (options.syntaxMode === \"main-thread\") return false;\n  if (options.syntaxMode === \"worker\") return true;\n  return typeof Worker !== \"undefined\";\n}\n\nfunction getGlobalLineTokens(languageId: string, line: string) {\n  const key = codeGlobalTokenCacheKey(languageId, line);\n  const tokens = globalTokenCache.get(key);\n  if (!tokens) return null;\n\n  globalTokenCache.delete(key);\n  globalTokenCache.set(key, tokens);\n  return tokens;\n}\n\nfunction setGlobalLineTokens(\n  languageId: string,\n  line: string,\n  tokens: readonly CodeTokenLeaf[],\n) {\n  const key = codeGlobalTokenCacheKey(languageId, line);\n  const cachedTokens = getGlobalLineTokens(languageId, line);\n  if (cachedTokens && areCodeTokenLeavesEqual(cachedTokens, tokens)) {\n    return cachedTokens;\n  }\n\n  globalTokenCache.delete(key);\n  globalTokenCache.set(key, tokens);\n  while (globalTokenCache.size > CODE_GLOBAL_TOKEN_CACHE_LIMIT) {\n    const firstKey = globalTokenCache.keys().next().value;\n    if (firstKey === undefined) return tokens;\n    globalTokenCache.delete(firstKey);\n  }\n  return tokens;\n}\n\nfunction codeGlobalTokenCacheKey(languageId: string, line: string) {\n  return `${languageId}\\0${line}`;\n}\n\nfunction requestCodeSyntaxWorkerTokens({\n  createWorker,\n  languageId,\n  lines,\n  onDone,\n  onError,\n  onTokens,\n}: {\n  createWorker: CodeSyntaxWorkerFactory;\n  languageId: string;\n  lines: readonly string[];\n  onDone: () => void;\n  onError: (line: string) => void;\n  onTokens: (line: string, tokens: readonly CodeTokenLeaf[]) => void;\n}): CodeSyntaxWorkerRelease {\n  const pool = getCodeSyntaxWorkerPool(createWorker);\n  const subscription: CodeSyntaxWorkerSubscription = {\n    isReleased: false,\n    onDone,\n    onError,\n    onTokens,\n    pendingCount: 0,\n  };\n  const jobs: CodeSyntaxWorkerJob[] = [];\n\n  for (const line of lines) {\n    const cachedTokens = getGlobalLineTokens(languageId, line);\n    if (cachedTokens) {\n      onTokens(line, cachedTokens);\n      continue;\n    }\n\n    const key = codeGlobalTokenCacheKey(languageId, line);\n    let job = pool.jobsByKey.get(key);\n    if (!job) {\n      job = {\n        key,\n        languageId,\n        line,\n        status: \"pending\",\n        subscribers: new Set(),\n      };\n      pool.jobsByKey.set(key, job);\n      pool.pendingJobs.set(key, job);\n    }\n    job.subscribers.add(subscription);\n    subscription.pendingCount += 1;\n    jobs.push(job);\n  }\n\n  if (subscription.pendingCount === 0) {\n    onDone();\n    return () => undefined;\n  }\n\n  dispatchCodeSyntaxWorkerPool(pool);\n\n  return () => {\n    if (subscription.isReleased) return;\n    subscription.isReleased = true;\n    for (const job of jobs) {\n      releaseCodeSyntaxWorkerSubscription(pool, job, subscription);\n    }\n  };\n}\n\nfunction getCodeSyntaxWorkerPool(createWorker: CodeSyntaxWorkerFactory) {\n  let pool = globalWorkerPoolsByFactory.get(createWorker);\n  if (!pool) {\n    pool = {\n      createWorker,\n      dispatchHandle: null,\n      jobsByKey: new Map(),\n      maxWorkers:\n        createWorker === createCodeSyntaxWorker\n          ? CODE_GLOBAL_WORKER_POOL_SIZE\n          : 1,\n      pendingJobs: new Map(),\n      requestId: 0,\n      slots: [],\n    };\n    globalWorkerPoolsByFactory.set(createWorker, pool);\n    globalWorkerPools.add(pool);\n  }\n  return pool;\n}\n\nfunction scheduleCodeSyntaxWorkerPoolDispatch(pool: CodeSyntaxWorkerPool) {\n  if (pool.pendingJobs.size === 0 || pool.dispatchHandle) return;\n  pool.dispatchHandle = scheduleCodeSyntaxTask(() => {\n    pool.dispatchHandle = null;\n    dispatchCodeSyntaxWorkerPool(pool);\n  });\n}\n\nfunction cancelCodeSyntaxWorkerPoolDispatch(pool: CodeSyntaxWorkerPool) {\n  if (!pool.dispatchHandle) return;\n  cancelCodeSyntaxTask(pool.dispatchHandle);\n  pool.dispatchHandle = null;\n}\n\nfunction dispatchCodeSyntaxWorkerPool(pool: CodeSyntaxWorkerPool) {\n  while (pool.pendingJobs.size > 0) {\n    const slot = getIdleCodeSyntaxWorkerSlot(pool);\n    if (!slot) return;\n\n    const jobs = takeCodeSyntaxWorkerJobs(pool, CODE_WORKER_TOKENIZE_BATCH_SIZE);\n    if (jobs.length === 0) return;\n\n    pool.requestId += 1;\n    const requestId = pool.requestId;\n    const languageId = jobs[0]?.languageId ?? \"\";\n    const batch: CodeSyntaxWorkerBatch = {\n      generation: requestId,\n      jobs,\n      languageId,\n      requestId,\n    };\n    slot.activeBatch = batch;\n\n    const request: CodeSyntaxWorkerRequest = {\n      type: \"tokenize\",\n      generation: batch.generation,\n      languageId,\n      lines: jobs.map((job) => job.line),\n      requestId,\n    };\n    slot.worker.postMessage(request);\n  }\n}\n\nfunction getIdleCodeSyntaxWorkerSlot(pool: CodeSyntaxWorkerPool) {\n  const idleSlot = pool.slots.find((slot) => !slot.activeBatch);\n  if (idleSlot) return idleSlot;\n  if (pool.slots.length >= pool.maxWorkers) return null;\n\n  try {\n    const slot: CodeSyntaxWorkerSlot = {\n      activeBatch: null,\n      worker: pool.createWorker(),\n    };\n    slot.worker.onmessage = (event: MessageEvent<CodeSyntaxWorkerResponse>) => {\n      handleCodeSyntaxWorkerPoolMessage(pool, slot, event.data);\n    };\n    slot.worker.onerror = () => {\n      handleCodeSyntaxWorkerPoolFailure(pool, slot);\n    };\n    slot.worker.onmessageerror = () => {\n      handleCodeSyntaxWorkerPoolFailure(pool, slot);\n    };\n    pool.slots.push(slot);\n    return slot;\n  } catch {\n    failPendingCodeSyntaxWorkerJobs(pool);\n    return null;\n  }\n}\n\nfunction takeCodeSyntaxWorkerJobs(\n  pool: CodeSyntaxWorkerPool,\n  limit: number,\n) {\n  const firstJob = pool.pendingJobs.values().next().value;\n  if (!firstJob) return [];\n\n  const jobs: CodeSyntaxWorkerJob[] = [];\n  for (const job of pool.pendingJobs.values()) {\n    if (job.languageId !== firstJob.languageId) continue;\n    pool.pendingJobs.delete(job.key);\n    job.status = \"active\";\n    jobs.push(job);\n    if (jobs.length >= limit) break;\n  }\n  return jobs;\n}\n\nfunction handleCodeSyntaxWorkerPoolMessage(\n  pool: CodeSyntaxWorkerPool,\n  slot: CodeSyntaxWorkerSlot,\n  message: CodeSyntaxWorkerResponse,\n) {\n  const batch = slot.activeBatch;\n  if (\n    !batch ||\n    message.generation !== batch.generation ||\n    message.languageId !== batch.languageId ||\n    message.requestId !== batch.requestId\n  ) {\n    return;\n  }\n\n  slot.activeBatch = null;\n\n  if (message.type === \"error\") {\n    terminateCodeSyntaxWorkerSlot(pool, slot);\n    finishCodeSyntaxWorkerJobsWithError(pool, batch.jobs);\n    scheduleCodeSyntaxWorkerPoolDispatch(pool);\n    return;\n  }\n\n  const resultsByLine = new Map(\n    message.results.map((result) => [result.line, result.tokens] as const),\n  );\n\n  for (const job of batch.jobs) {\n    pool.jobsByKey.delete(job.key);\n    const tokens = resultsByLine.get(job.line) ?? null;\n    if (tokens) setGlobalLineTokens(job.languageId, job.line, tokens);\n    for (const subscriber of job.subscribers) {\n      if (!subscriber.isReleased && tokens) {\n        subscriber.onTokens(job.line, tokens);\n      }\n      completeCodeSyntaxWorkerSubscription(subscriber);\n    }\n    job.subscribers.clear();\n  }\n\n  dispatchCodeSyntaxWorkerPool(pool);\n}\n\nfunction handleCodeSyntaxWorkerPoolFailure(\n  pool: CodeSyntaxWorkerPool,\n  slot: CodeSyntaxWorkerSlot,\n) {\n  const batch = slot.activeBatch;\n  slot.activeBatch = null;\n  terminateCodeSyntaxWorkerSlot(pool, slot);\n  if (batch) finishCodeSyntaxWorkerJobsWithError(pool, batch.jobs);\n  scheduleCodeSyntaxWorkerPoolDispatch(pool);\n}\n\nfunction terminateCodeSyntaxWorkerSlot(\n  pool: CodeSyntaxWorkerPool,\n  slot: CodeSyntaxWorkerSlot,\n) {\n  slot.worker.terminate();\n  pool.slots = pool.slots.filter((candidate) => candidate !== slot);\n}\n\nfunction failPendingCodeSyntaxWorkerJobs(pool: CodeSyntaxWorkerPool) {\n  const jobs = Array.from(pool.pendingJobs.values());\n  pool.pendingJobs.clear();\n  finishCodeSyntaxWorkerJobsWithError(pool, jobs);\n}\n\nfunction finishCodeSyntaxWorkerJobsWithError(\n  pool: CodeSyntaxWorkerPool,\n  jobs: readonly CodeSyntaxWorkerJob[],\n) {\n  for (const job of jobs) {\n    pool.jobsByKey.delete(job.key);\n    pool.pendingJobs.delete(job.key);\n    for (const subscriber of job.subscribers) {\n      if (!subscriber.isReleased) subscriber.onError(job.line);\n      completeCodeSyntaxWorkerSubscription(subscriber);\n    }\n    job.subscribers.clear();\n  }\n}\n\nfunction releaseCodeSyntaxWorkerSubscription(\n  pool: CodeSyntaxWorkerPool,\n  job: CodeSyntaxWorkerJob,\n  subscriber: CodeSyntaxWorkerSubscription,\n) {\n  job.subscribers.delete(subscriber);\n  if (job.subscribers.size > 0 || job.status === \"active\") return;\n  pool.pendingJobs.delete(job.key);\n  pool.jobsByKey.delete(job.key);\n}\n\nfunction completeCodeSyntaxWorkerSubscription(\n  subscriber: CodeSyntaxWorkerSubscription,\n) {\n  if (subscriber.isReleased) return;\n  subscriber.pendingCount -= 1;\n  if (subscriber.pendingCount <= 0) {\n    subscriber.isReleased = true;\n    subscriber.onDone();\n  }\n}\n\nfunction scheduleCodeSyntaxTask(\n  callback: (deadline?: CodeSyntaxTaskDeadline) => void,\n): CodeSyntaxTaskHandle {\n  const browserWindow = window as CodeSyntaxIdleWindow;\n  if (browserWindow.requestIdleCallback) {\n    return {\n      kind: \"idle\",\n      id: browserWindow.requestIdleCallback(callback, { timeout: 80 }),\n    };\n  }\n\n  return {\n    kind: \"timeout\",\n    id: browserWindow.setTimeout(() => callback(), 0),\n  };\n}\n\nfunction cancelCodeSyntaxTask(handle: CodeSyntaxTaskHandle) {\n  const browserWindow = window as CodeSyntaxIdleWindow;\n  if (handle.kind === \"idle\") {\n    browserWindow.cancelIdleCallback?.(handle.id);\n    return;\n  }\n  browserWindow.clearTimeout(handle.id);\n}\n\nfunction scheduleCodeSyntaxNotification(\n  callback: () => void,\n): CodeSyntaxNotifyHandle {\n  return { id: window.setTimeout(callback, 0) };\n}\n\nfunction cancelCodeSyntaxNotification(handle: CodeSyntaxNotifyHandle) {\n  window.clearTimeout(handle.id);\n}\n\nfunction areCodeTokenLeavesEqual(\n  first: readonly CodeTokenLeaf[],\n  second: readonly CodeTokenLeaf[],\n) {\n  if (first === second) return true;\n  if (first.length !== second.length) return false;\n  for (let index = 0; index < first.length; index += 1) {\n    const firstLeaf = first[index];\n    const secondLeaf = second[index];\n    if (\n      firstLeaf?.kind !== secondLeaf?.kind ||\n      firstLeaf?.text !== secondLeaf?.text\n    ) {\n      return false;\n    }\n  }\n  return true;\n}\n\nfunction shouldYieldDeferredTokenization({\n  deadline,\n  processedLineCount,\n  startedAt,\n}: {\n  deadline?: CodeSyntaxTaskDeadline;\n  processedLineCount: number;\n  startedAt: number;\n}) {\n  if (processedLineCount <= 0) return false;\n  if (deadline?.timeRemaining && deadline.timeRemaining() <= 1) return true;\n  if (processedLineCount >= CODE_DEFERRED_TOKENIZE_BATCH_SIZE) return true;\n  return codeSyntaxNow() - startedAt >= CODE_DEFERRED_TOKENIZE_BUDGET_MS;\n}\n\nfunction codeSyntaxNow() {\n  return typeof performance === \"undefined\" ? Date.now() : performance.now();\n}\n\nfunction codeLanguageId(resource: ViewerResource): string | null {\n  const extension = resource.fileName.toLowerCase().split(\".\").pop();\n  const byExtension = extension ? LANGUAGE_BY_EXTENSION[extension] : undefined;\n  if (byExtension) return byExtension;\n\n  const mimeType = resource.content.mimeType\n    ?.toLowerCase()\n    .split(\";\")[0]\n    .trim();\n  return (mimeType && LANGUAGE_BY_MIME[mimeType]) ?? null;\n}\n",
      "type": "registry:ui",
      "target": "@ui/code-viewer-syntax.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/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-worker.ts",
      "content": "export function createCodeSyntaxWorker(): Worker {\n  return new Worker(new URL(\"./code-viewer-syntax.worker.ts\", import.meta.url), {\n    type: \"module\",\n  });\n}\n",
      "type": "registry:ui",
      "target": "@ui/code-viewer-syntax-worker.ts"
    },
    {
      "path": "registry/new-york-v4/ui/code-viewer-syntax.worker.ts",
      "content": "import {\n  type CodeSyntaxWorkerRequest,\n  type CodeSyntaxWorkerResponse,\n  shouldTokenizeCodeLine,\n} from \"./code-viewer-syntax-protocol\";\nimport {\n  ensureCodePrismLanguage,\n  tokenizeCodeLine,\n} from \"./code-viewer-syntax-prism\";\n\nconst workerSelf = self as unknown as {\n  onmessage: ((event: MessageEvent<CodeSyntaxWorkerRequest>) => void) | null;\n  postMessage(message: CodeSyntaxWorkerResponse): void;\n};\n\nfunction post(message: CodeSyntaxWorkerResponse) {\n  workerSelf.postMessage(message);\n}\n\nasync function tokenizeInWorker(request: CodeSyntaxWorkerRequest) {\n  await ensureCodePrismLanguage(request.languageId);\n  post({\n    type: \"tokens\",\n    requestId: request.requestId,\n    generation: request.generation,\n    languageId: request.languageId,\n    results: request.lines.map((line) => ({\n      line,\n      tokens: shouldTokenizeCodeLine(line)\n        ? tokenizeCodeLine(request.languageId, line)\n        : null,\n    })),\n  });\n}\n\nworkerSelf.onmessage = (event: MessageEvent<CodeSyntaxWorkerRequest>) => {\n  const request = event.data;\n  if (request.type !== \"tokenize\") return;\n\n  void tokenizeInWorker(request).catch((error) => {\n    post({\n      type: \"error\",\n      requestId: request.requestId,\n      generation: request.generation,\n      languageId: request.languageId,\n      message: error instanceof Error ? error.message : String(error),\n    });\n  });\n};\n",
      "type": "registry:ui",
      "target": "@ui/code-viewer-syntax.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/code-viewer-syntax-style.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { CODE_VIEWER_SYNTAX_STYLE } from \"./code-viewer-syntax\";\n\nconst CODE_VIEWER_SYNTAX_STYLE_ID = \"retab-code-viewer-syntax-style\";\n\nexport function useCodeViewerSyntaxStyle() {\n  React.useInsertionEffect(() => {\n    let style = document.getElementById(CODE_VIEWER_SYNTAX_STYLE_ID);\n    if (style) return;\n\n    style = document.createElement(\"style\");\n    style.id = CODE_VIEWER_SYNTAX_STYLE_ID;\n    style.textContent = CODE_VIEWER_SYNTAX_STYLE;\n    document.head.append(style);\n  }, []);\n}\n",
      "type": "registry:ui",
      "target": "@ui/code-viewer-syntax-style.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/code-viewer-virtualization.ts",
      "content": "import {\n  CODE_VIEWER_BLOCK_PADDING,\n  CODE_VIEWER_LINE_CHECKPOINT_INTERVAL,\n  CODE_VIEWER_SCROLL_REBASE_CONTAINER_PX,\n  CODE_VIEWER_SCROLL_REBASE_TARGET_BOTTOM_PX,\n  CODE_VIEWER_SCROLL_REBASE_TARGET_PX,\n  CODE_VIEWER_SCROLL_REBASE_THRESHOLD_PX,\n  CODE_VIEWER_SCROLL_REBASE_TRIGGER_PX,\n} from \"./code-viewer-scale\";\n\nexport interface CodeVirtualLine {\n  index: number;\n  key: number;\n  size: number;\n  start: number;\n}\n\nexport interface CodeVirtualLineWindow {\n  lineCount: number;\n  lineHeight: number;\n  overscanPx: number;\n  paddingStart: number;\n  scrollTop: number;\n  viewportHeight: number;\n}\n\nexport interface CodeVirtualPixelWindow {\n  bottom: number;\n  top: number;\n}\n\nexport interface CodeVirtualLineWindowResult {\n  lines: CodeVirtualLine[];\n  pixelWindow: CodeVirtualPixelWindow;\n}\n\nexport interface CodeLineCheckpoint {\n  index: number;\n  start: number;\n}\n\nexport interface CodeScrollRebaseState {\n  scrollPageOffset: number;\n}\n\nexport interface CodeScrollRebasePosition extends CodeScrollRebaseState {\n  physicalScrollTop: number;\n}\n\nexport function getCodeVirtualLines({\n  lineCount,\n  lineHeight,\n  overscanPx,\n  paddingStart,\n  scrollTop,\n  viewportHeight,\n}: CodeVirtualLineWindow): CodeVirtualLine[] {\n  return getCodeVirtualLineWindow({\n    lineCount,\n    lineHeight,\n    overscanPx,\n    paddingStart,\n    scrollTop,\n    viewportHeight,\n  }).lines;\n}\n\nexport function getCodeVirtualLineWindow({\n  lineCount,\n  lineHeight,\n  overscanPx,\n  paddingStart,\n  scrollTop,\n  viewportHeight,\n}: CodeVirtualLineWindow): CodeVirtualLineWindowResult {\n  const safeLineCount = safeCount(lineCount);\n  const safeLineHeight = safeSize(lineHeight);\n  if (safeLineCount === 0) {\n    return {\n      lines: [],\n      pixelWindow: { bottom: 0, top: 0 },\n    };\n  }\n\n  const safeScrollTop = Math.max(0, finiteNumber(scrollTop));\n  const safeViewportHeight = Math.max(0, finiteNumber(viewportHeight));\n  const safePaddingStart = safePadding(paddingStart);\n  const totalSize = getCodeVirtualTotalSize({\n    lineCount: safeLineCount,\n    lineHeight: safeLineHeight,\n    paddingStart: safePaddingStart,\n  });\n  const window = getCodeVirtualPixelWindow({\n    overscanPx,\n    scrollHeight: totalSize,\n    scrollTop: safeScrollTop,\n    viewportHeight: safeViewportHeight,\n  });\n  const start = getCodeLineIndexAtOffset({\n    lineCount: safeLineCount,\n    lineHeight: safeLineHeight,\n    offset: window.top,\n    paddingStart: safePaddingStart,\n  });\n  const end = Math.min(\n    safeLineCount,\n    getCodeLineIndexAfterOffset({\n      lineCount: safeLineCount,\n      lineHeight: safeLineHeight,\n      offset: window.bottom,\n      paddingStart: safePaddingStart,\n    }),\n  );\n\n  return {\n    lines: Array.from({ length: end - start }, (_, offset) => {\n      const index = start + offset;\n      return {\n        index,\n        key: index,\n        size: safeLineHeight,\n        start: safePaddingStart + index * safeLineHeight,\n      };\n    }),\n    pixelWindow: window,\n  };\n}\n\nexport function getCodeLineCheckpoint({\n  lineCount,\n  lineHeight,\n  offset,\n  paddingStart = CODE_VIEWER_BLOCK_PADDING,\n}: {\n  lineCount: number;\n  lineHeight: number;\n  offset: number;\n  paddingStart?: number;\n}): CodeLineCheckpoint {\n  const safeLineCount = safeCount(lineCount);\n  if (safeLineCount === 0) {\n    return {\n      index: 0,\n      start: safePadding(paddingStart),\n    };\n  }\n\n  const safeLineHeight = safeSize(lineHeight);\n  const safePaddingStart = safePadding(paddingStart);\n  const lineIndex = clamp(\n    Math.floor(Math.max(0, finiteNumber(offset) - safePaddingStart) / safeLineHeight),\n    0,\n    safeLineCount - 1,\n  );\n  const checkpointIndex =\n    Math.floor(lineIndex / CODE_VIEWER_LINE_CHECKPOINT_INTERVAL) *\n    CODE_VIEWER_LINE_CHECKPOINT_INTERVAL;\n\n  return {\n    index: checkpointIndex,\n    start: safePaddingStart + checkpointIndex * safeLineHeight,\n  };\n}\n\nexport function getCodeLineIndexAtOffset({\n  lineCount,\n  lineHeight,\n  offset,\n  paddingStart = CODE_VIEWER_BLOCK_PADDING,\n}: {\n  lineCount: number;\n  lineHeight: number;\n  offset: number;\n  paddingStart?: number;\n}) {\n  const safeLineCount = safeCount(lineCount);\n  if (safeLineCount === 0) return 0;\n\n  const safeLineHeight = safeSize(lineHeight);\n  const checkpoint = getCodeLineCheckpoint({\n    lineCount: safeLineCount,\n    lineHeight: safeLineHeight,\n    offset,\n    paddingStart,\n  });\n  const indexFromCheckpoint = Math.floor(\n    Math.max(0, finiteNumber(offset) - checkpoint.start) / safeLineHeight,\n  );\n\n  return clamp(checkpoint.index + indexFromCheckpoint, 0, safeLineCount - 1);\n}\n\nexport function getCodeLineIndexAfterOffset({\n  lineCount,\n  lineHeight,\n  offset,\n  paddingStart = CODE_VIEWER_BLOCK_PADDING,\n}: {\n  lineCount: number;\n  lineHeight: number;\n  offset: number;\n  paddingStart?: number;\n}) {\n  const safeLineCount = safeCount(lineCount);\n  if (safeLineCount === 0) return 0;\n\n  const safeLineHeight = safeSize(lineHeight);\n  const checkpoint = getCodeLineCheckpoint({\n    lineCount: safeLineCount,\n    lineHeight: safeLineHeight,\n    offset,\n    paddingStart,\n  });\n  const indexFromCheckpoint = Math.ceil(\n    Math.max(0, finiteNumber(offset) - checkpoint.start) / safeLineHeight,\n  );\n\n  return clamp(checkpoint.index + indexFromCheckpoint, 0, safeLineCount);\n}\n\nexport function getCodeVirtualPixelWindow({\n  overscanPx,\n  scrollHeight,\n  scrollTop,\n  viewportHeight,\n}: {\n  overscanPx: number;\n  scrollHeight: number;\n  scrollTop: number;\n  viewportHeight: number;\n}): CodeVirtualPixelWindow {\n  const safeOverscanPx = safePadding(overscanPx);\n  const safeViewportHeight = Math.max(0, finiteNumber(viewportHeight));\n  const windowHeight = safeViewportHeight + safeOverscanPx * 2;\n  const safeScrollHeight = Math.max(0, finiteNumber(scrollHeight));\n\n  if (windowHeight >= safeScrollHeight) {\n    return { bottom: safeScrollHeight, top: 0 };\n  }\n\n  const scrollCenter = Math.max(0, finiteNumber(scrollTop)) + safeViewportHeight / 2;\n  let top = scrollCenter - windowHeight / 2;\n  let bottom = top + windowHeight;\n\n  if (top < 0) {\n    top = 0;\n    bottom = windowHeight;\n  }\n  if (bottom > safeScrollHeight) {\n    bottom = safeScrollHeight;\n    top = safeScrollHeight - windowHeight;\n  }\n\n  return {\n    bottom: Math.ceil(Math.max(bottom, top)),\n    top: Math.floor(Math.max(0, top)),\n  };\n}\n\nexport function getCodeVirtualTotalSize({\n  lineCount,\n  lineHeight,\n  paddingEnd = CODE_VIEWER_BLOCK_PADDING,\n  paddingStart = CODE_VIEWER_BLOCK_PADDING,\n}: {\n  lineCount: number;\n  lineHeight: number;\n  paddingEnd?: number;\n  paddingStart?: number;\n}) {\n  return (\n    safePadding(paddingStart) +\n    safeCount(lineCount) * safeSize(lineHeight) +\n    safePadding(paddingEnd)\n  );\n}\n\nexport function getCodePhysicalScrollSize({\n  totalSize,\n  viewportHeight,\n}: {\n  totalSize: number;\n  viewportHeight: number;\n}) {\n  const safeTotalSize = safeSize(totalSize);\n  return shouldRebaseCodeScroll({ totalSize: safeTotalSize, viewportHeight })\n    ? Math.min(safeTotalSize, CODE_VIEWER_SCROLL_REBASE_CONTAINER_PX)\n    : safeTotalSize;\n}\n\nexport function getCodeLogicalScrollTop({\n  physicalScrollTop,\n  scrollPageOffset,\n  totalSize,\n  viewportHeight,\n}: {\n  physicalScrollTop: number;\n  scrollPageOffset: number;\n  totalSize: number;\n  viewportHeight: number;\n}) {\n  return clamp(\n    finiteNumber(physicalScrollTop) + safePadding(scrollPageOffset),\n    0,\n    getCodeMaxLogicalScrollTop({ totalSize, viewportHeight }),\n  );\n}\n\nexport function resolveCodePhysicalScrollPosition({\n  logicalScrollTop,\n  scrollPageOffset,\n  totalSize,\n  viewportHeight,\n}: {\n  logicalScrollTop: number;\n  scrollPageOffset: number;\n  totalSize: number;\n  viewportHeight: number;\n}): CodeScrollRebasePosition {\n  const safeLogicalScrollTop = clamp(\n    finiteNumber(logicalScrollTop),\n    0,\n    getCodeMaxLogicalScrollTop({ totalSize, viewportHeight }),\n  );\n\n  if (!shouldRebaseCodeScroll({ totalSize, viewportHeight })) {\n    return {\n      physicalScrollTop: clamp(\n        safeLogicalScrollTop,\n        0,\n        getCodeMaxPhysicalScrollTop({ totalSize, viewportHeight }),\n      ),\n      scrollPageOffset: 0,\n    };\n  }\n\n  const currentPageOffset = clampCodeScrollPageOffset({\n    scrollPageOffset,\n    totalSize,\n    viewportHeight,\n  });\n  const physicalScrollTop = safeLogicalScrollTop - currentPageOffset;\n  const maxPhysicalScrollTop = getCodeMaxPhysicalScrollTop({\n    totalSize,\n    viewportHeight,\n  });\n  const maxPageOffset = getCodeMaxScrollPageOffset({ totalSize, viewportHeight });\n  const shouldMoveDown =\n    physicalScrollTop > CODE_VIEWER_SCROLL_REBASE_THRESHOLD_PX &&\n    currentPageOffset < maxPageOffset;\n  const shouldMoveUp =\n    physicalScrollTop < CODE_VIEWER_SCROLL_REBASE_TRIGGER_PX &&\n    currentPageOffset > 0;\n\n  if (\n    physicalScrollTop < 0 ||\n    physicalScrollTop > maxPhysicalScrollTop ||\n    shouldMoveDown ||\n    shouldMoveUp\n  ) {\n    return resolveCodeScrollPageWindow({\n      logicalScrollTop: safeLogicalScrollTop,\n      preferredPhysicalScrollTop: shouldMoveUp\n        ? Math.min(\n            CODE_VIEWER_SCROLL_REBASE_TARGET_BOTTOM_PX,\n            maxPhysicalScrollTop,\n          )\n        : CODE_VIEWER_SCROLL_REBASE_TARGET_PX,\n      totalSize,\n      viewportHeight,\n    });\n  }\n\n  return {\n    physicalScrollTop: roundCodeScrollPixel(\n      clamp(physicalScrollTop, 0, maxPhysicalScrollTop),\n    ),\n    scrollPageOffset: currentPageOffset,\n  };\n}\n\nexport function getCodePagedLayoutTop({\n  logicalTop,\n  scrollPageOffset,\n  totalSize,\n  viewportHeight,\n}: {\n  logicalTop: number;\n  scrollPageOffset: number;\n  totalSize: number;\n  viewportHeight: number;\n}) {\n  if (!shouldRebaseCodeScroll({ totalSize, viewportHeight })) {\n    return finiteNumber(logicalTop);\n  }\n  return Math.max(0, finiteNumber(logicalTop) - safePadding(scrollPageOffset));\n}\n\nfunction shouldRebaseCodeScroll({\n  totalSize,\n  viewportHeight,\n}: {\n  totalSize: number;\n  viewportHeight: number;\n}) {\n  return (\n    getCodeMaxLogicalScrollTop({ totalSize, viewportHeight }) >\n    CODE_VIEWER_SCROLL_REBASE_THRESHOLD_PX\n  );\n}\n\nfunction getCodeMaxLogicalScrollTop({\n  totalSize,\n  viewportHeight,\n}: {\n  totalSize: number;\n  viewportHeight: number;\n}) {\n  return Math.max(safeSize(totalSize) - Math.max(0, finiteNumber(viewportHeight)), 0);\n}\n\nfunction getCodeMaxPhysicalScrollTop({\n  totalSize,\n  viewportHeight,\n}: {\n  totalSize: number;\n  viewportHeight: number;\n}) {\n  return Math.max(\n    getCodePhysicalScrollSize({ totalSize, viewportHeight }) -\n      Math.max(0, finiteNumber(viewportHeight)),\n    0,\n  );\n}\n\nfunction getCodeMaxScrollPageOffset({\n  totalSize,\n  viewportHeight,\n}: {\n  totalSize: number;\n  viewportHeight: number;\n}) {\n  return Math.max(\n    getCodeMaxLogicalScrollTop({ totalSize, viewportHeight }) -\n      getCodeMaxPhysicalScrollTop({ totalSize, viewportHeight }),\n    0,\n  );\n}\n\nfunction clampCodeScrollPageOffset({\n  scrollPageOffset,\n  totalSize,\n  viewportHeight,\n}: {\n  scrollPageOffset: number;\n  totalSize: number;\n  viewportHeight: number;\n}) {\n  return clamp(\n    safePadding(scrollPageOffset),\n    0,\n    getCodeMaxScrollPageOffset({ totalSize, viewportHeight }),\n  );\n}\n\nfunction resolveCodeScrollPageWindow({\n  logicalScrollTop,\n  preferredPhysicalScrollTop,\n  totalSize,\n  viewportHeight,\n}: {\n  logicalScrollTop: number;\n  preferredPhysicalScrollTop: number;\n  totalSize: number;\n  viewportHeight: number;\n}): CodeScrollRebasePosition {\n  let physicalScrollTop = roundCodeScrollPixel(\n    clamp(\n      finiteNumber(preferredPhysicalScrollTop),\n      0,\n      getCodeMaxPhysicalScrollTop({ totalSize, viewportHeight }),\n    ),\n  );\n  let scrollPageOffset = clampCodeScrollPageOffset({\n    scrollPageOffset: logicalScrollTop - physicalScrollTop,\n    totalSize,\n    viewportHeight,\n  });\n\n  physicalScrollTop = roundCodeScrollPixel(\n    clamp(\n      logicalScrollTop - scrollPageOffset,\n      0,\n      getCodeMaxPhysicalScrollTop({ totalSize, viewportHeight }),\n    ),\n  );\n  scrollPageOffset = clampCodeScrollPageOffset({\n    scrollPageOffset: logicalScrollTop - physicalScrollTop,\n    totalSize,\n    viewportHeight,\n  });\n\n  return { physicalScrollTop, scrollPageOffset };\n}\n\nfunction finiteNumber(value: number) {\n  return Number.isFinite(value) ? value : 0;\n}\n\nfunction roundCodeScrollPixel(value: number) {\n  if (typeof window === \"undefined\") return Math.round(value);\n  const ratio = window.devicePixelRatio || 1;\n  return Math.round(value * ratio) / ratio;\n}\n\nfunction safeCount(value: number) {\n  return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;\n}\n\nfunction safeSize(value: number) {\n  return Number.isFinite(value) && value > 0 ? value : 1;\n}\n\nfunction safePadding(value: number) {\n  return Number.isFinite(value) && 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/code-viewer-virtualization.ts"
    },
    {
      "path": "registry/new-york-v4/ui/code-viewer-viewport.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { CODE_GUTTER_BACKGROUND } from \"./code-viewer-projector\";\nimport {\n  CODE_VIEWER_BASE_FONT_PX,\n  CODE_VIEWER_INITIAL_VIEWPORT_HEIGHT,\n} from \"./code-viewer-scale\";\nimport {\n  getCodePhysicalScrollSize,\n  getCodeVirtualTotalSize,\n} from \"./code-viewer-virtualization\";\nimport { ScrollArea } from \"./scroll-area\";\n\nconst CODE_VIEWER_DEFAULT_VIEWPORT_WIDTH = 800;\n\nexport function CodeViewerViewport({\n  fontScale,\n  gutterWidth,\n  lineCount,\n  lineHeight,\n  onCopy,\n  rowHostRef,\n  viewportRef,\n}: {\n  fontScale: number;\n  gutterWidth: string;\n  lineCount: number;\n  lineHeight: number;\n  onCopy?: React.ClipboardEventHandler<HTMLPreElement>;\n  rowHostRef: React.RefObject<HTMLPreElement | null>;\n  viewportRef: React.RefObject<HTMLDivElement | null>;\n}) {\n  const fontSize = `${CODE_VIEWER_BASE_FONT_PX * fontScale}px`;\n  const totalSize = getCodeVirtualTotalSize({\n    lineCount,\n    lineHeight,\n  });\n  const physicalTotalSize = getCodePhysicalScrollSize({\n    totalSize,\n    viewportHeight: CODE_VIEWER_INITIAL_VIEWPORT_HEIGHT,\n  });\n\n  return (\n    <div className=\"bg-background relative min-h-0 flex-1\">\n      {/* Fixed full-height gutter rail, behind the scrolling content: the\n          line-number column and its divider always reach the bottom of the\n          viewport and never move while scrolling, because it lives outside the\n          scroll container. The per-row gutters paint the numbers — and mask\n          horizontally-scrolled code — on top of it. The viewport itself is\n          transparent so this shows through below the last line. */}\n      <div\n        aria-hidden\n        data-code-gutter-rail=\"\"\n        className=\"pointer-events-none absolute inset-y-0 left-0 z-0 border-r font-mono\"\n        style={{\n          width: gutterWidth,\n          backgroundColor: CODE_GUTTER_BACKGROUND,\n          fontSize,\n        }}\n      />\n      <ScrollArea\n        className=\"absolute inset-0 z-10\"\n        viewportProps={{ style: { overflowAnchor: \"none\" } }}\n        viewportRef={viewportRef}\n      >\n        <div\n          data-code-scroll-spacer=\"\"\n          className=\"relative w-max min-w-full font-mono\"\n          style={{\n            contain: \"layout style\",\n            fontSize,\n            height: physicalTotalSize,\n            lineHeight: `${lineHeight}px`,\n            minWidth: CODE_VIEWER_DEFAULT_VIEWPORT_WIDTH,\n          }}\n        >\n          <div\n            aria-hidden\n            data-code-render-offset=\"\"\n            style={{\n              contain: \"layout size\",\n              height: 0,\n            }}\n          />\n          <div\n            data-code-render-window=\"\"\n            className=\"w-full\"\n            style={{\n              bottom: 0,\n              contain: \"layout style inline-size\",\n              display: \"flex\",\n              flexDirection: \"column\",\n              height: physicalTotalSize,\n              isolation: \"isolate\",\n              position: \"sticky\",\n              top: 0,\n            }}\n          >\n            <pre\n              onCopy={onCopy}\n              ref={rowHostRef}\n              className=\"relative w-full\"\n              suppressHydrationWarning\n              style={{\n                fontSize,\n                height: physicalTotalSize,\n                lineHeight: `${lineHeight}px`,\n              }}\n            />\n          </div>\n        </div>\n      </ScrollArea>\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/code-viewer-viewport.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-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/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/code-viewer-long-lines.ts",
      "content": "export const CODE_VIEWER_LONG_LINE_RENDER_MAX = 4096;\n\nconst CODE_VIEWER_LONG_LINE_HEAD_CHARS = 3072;\nconst CODE_VIEWER_LONG_LINE_TAIL_CHARS = 768;\n\nexport type CodeLineRenderText = {\n  isTruncated: boolean;\n  omittedCharacterCount: number;\n  text: string;\n};\n\nexport function getCodeLineRenderText(text: string): CodeLineRenderText {\n  if (text.length <= CODE_VIEWER_LONG_LINE_RENDER_MAX) {\n    return {\n      isTruncated: false,\n      omittedCharacterCount: 0,\n      text,\n    };\n  }\n\n  const omittedCharacterCount =\n    text.length -\n    CODE_VIEWER_LONG_LINE_HEAD_CHARS -\n    CODE_VIEWER_LONG_LINE_TAIL_CHARS;\n\n  return {\n    isTruncated: true,\n    omittedCharacterCount,\n    text:\n      text.slice(0, CODE_VIEWER_LONG_LINE_HEAD_CHARS) +\n      ` ... ${omittedCharacterCount} chars omitted ... ` +\n      text.slice(-CODE_VIEWER_LONG_LINE_TAIL_CHARS),\n  };\n}\n\nexport function getCodeLongLineSelectionText({\n  rowHost,\n  selection,\n  textLines,\n}: {\n  rowHost: HTMLPreElement;\n  selection: Selection | null;\n  textLines: readonly string[];\n}) {\n  if (!selection || selection.isCollapsed || selection.rangeCount === 0) {\n    return null;\n  }\n\n  const selectedLineIndexes = new Set<number>();\n  let includesTruncatedLine = false;\n\n  for (let rangeIndex = 0; rangeIndex < selection.rangeCount; rangeIndex += 1) {\n    const range = selection.getRangeAt(rangeIndex);\n    if (!rangeIntersectsNode(range, rowHost)) continue;\n\n    for (const row of rowHost.querySelectorAll<HTMLElement>(\n      \"[data-line-index]\",\n    )) {\n      if (!rangeIntersectsNode(range, row)) continue;\n\n      const lineIndex = Number(row.dataset.lineIndex);\n      if (\n        Number.isInteger(lineIndex) &&\n        lineIndex >= 0 &&\n        lineIndex < textLines.length\n      ) {\n        selectedLineIndexes.add(lineIndex);\n      }\n      if (row.dataset.codeLineTruncated != null) {\n        includesTruncatedLine = true;\n      }\n    }\n  }\n\n  if (!includesTruncatedLine || selectedLineIndexes.size === 0) return null;\n\n  return Array.from(selectedLineIndexes)\n    .sort((first, second) => first - second)\n    .map((lineIndex) => textLines[lineIndex] ?? \"\")\n    .join(\"\\n\");\n}\n\nfunction rangeIntersectsNode(range: Range, node: Node) {\n  try {\n    if (range.intersectsNode(node)) return true;\n  } catch {\n    // Detached test DOM can make intersectsNode throw even when the selected\n    // content is plainly inside the row.\n  }\n  return (\n    node === range.commonAncestorContainer ||\n    node.contains(range.commonAncestorContainer)\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/code-viewer-long-lines.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"
    }
  ],
  "type": "registry:ui"
}