{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "csv-viewer",
  "title": "CSV Viewer",
  "description": "A fixed-window virtualized CSV/TSV table: sortable columns, sticky header, row numbers, horizontal scroll. Renders straight from raw rows (no table row model) so it stays flat in memory on huge files.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "@retab/csv",
    "@retab/utils",
    "button",
    "dropdown-menu",
    "@retab/viewer-controls",
    "@retab/use-keyed-layout-effect",
    "@retab/use-mount-effect"
  ],
  "files": [
    {
      "path": "registry/new-york-v4/ui/csv-viewer.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\nimport type { CsvDialect, CsvTable } from \"@/lib/csv\";\nimport {\n  createViewerResource,\n  type ViewerResource,\n} from \"@/lib/viewer-resource\";\n\nimport {\n  CsvViewerFrame,\n  CsvViewerHeader,\n  csvViewerStatusNode,\n} from \"./csv-viewer-chrome\";\nimport {\n  csvViewerDownloadActions,\n  defaultCsvDownloadName,\n} from \"./csv-viewer-download\";\nimport { CsvGrid, type CsvGridHandle } from \"./csv-viewer-grid\";\nimport {\n  csvViewerExportFileName,\n  csvViewerSortResetKey,\n  isCsvDocumentSource,\n  resolveCsvViewerDialect,\n  type CsvDocumentSource,\n  type CsvTableSource,\n  type CsvViewerSource,\n} from \"./csv-viewer-resource\";\nimport { useCsvResourceState, type CsvCellAddress } from \"./csv-viewer-state\";\nimport type { CsvViewerHandle, CsvViewerProps } from \"./csv-viewer-types\";\nimport {\n  useViewerControlsRegistration,\n  type ViewerControlsState,\n} from \"./viewer-controls\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nexport type {\n  CsvScrollOptions,\n  CsvViewerHandle,\n  CsvViewerProps,\n} from \"./csv-viewer-types\";\n\nexport type CsvResourceContentProps = Omit<CsvViewerProps, \"source\"> & {\n  resource?: ViewerResource | null;\n  source?: CsvViewerSource;\n};\ntype CsvResourceContentInternalProps = CsvResourceContentProps & {\n  frame?: boolean;\n};\n\nexport type CsvViewerProviderProps = {\n  children: React.ReactNode;\n  resource: ViewerResource;\n};\n\nexport type CsvViewerGridProps = Omit<CsvResourceContentProps, \"resource\">;\n\nexport const CsvViewer = React.forwardRef<CsvViewerHandle, CsvViewerProps>(\n  function CsvViewer({ source, ...props }, ref) {\n    const resource = React.useMemo<ViewerResource | null>(\n      () =>\n        source && isCsvDocumentSource(source)\n          ? createViewerResource(source)\n          : null,\n      [source],\n    );\n    return (\n      <CsvResourceContent\n        {...props}\n        frame\n        ref={ref}\n        resource={resource}\n        source={source}\n      />\n    );\n  },\n);\n\nconst CsvViewerResourceContext = React.createContext<ViewerResource | null>(\n  null,\n);\n\nexport function CsvViewerProvider({\n  children,\n  resource,\n}: CsvViewerProviderProps) {\n  return (\n    <CsvViewerResourceContext.Provider value={resource}>\n      {children}\n    </CsvViewerResourceContext.Provider>\n  );\n}\n\nfunction useCsvViewerResource(): ViewerResource {\n  const resource = React.useContext(CsvViewerResourceContext);\n  if (!resource) {\n    throw new Error(\"CsvViewerGrid must be used within CsvViewerProvider.\");\n  }\n  return resource;\n}\n\nexport const CsvViewerDocument = React.forwardRef<\n  CsvViewerHandle,\n  CsvViewerProps\n>(function CsvViewerDocument({ source, ...props }, ref) {\n  const resource = React.useMemo<ViewerResource | null>(\n    () =>\n      source && isCsvDocumentSource(source)\n        ? createViewerResource(source)\n        : null,\n    [source],\n  );\n  return (\n    <CsvResourceContent\n      {...props}\n      frame={false}\n      ref={ref}\n      resource={resource}\n      source={source}\n    />\n  );\n});\n\nexport const CsvViewerGrid = React.forwardRef<\n  CsvViewerHandle,\n  CsvViewerGridProps\n>(function CsvViewerGrid(props, ref) {\n  const resource = useCsvViewerResource();\n  return (\n    <CsvResourceContent\n      {...props}\n      frame={false}\n      ref={ref}\n      resource={resource}\n    />\n  );\n});\n\nexport const CsvResourceContent = React.forwardRef<\n  CsvViewerHandle,\n  CsvResourceContentInternalProps\n>(function CsvResourceContent(\n  {\n    source,\n    resource = null,\n    dialect: dialectProp,\n    className,\n    controls = true,\n    height = 480,\n    fillHeight = false,\n    frame = true,\n    activeCell,\n    isolateStyles = false,\n  },\n  ref,\n) {\n  const [retryVersion, setRetryVersion] = React.useState(0);\n  const dialect = React.useMemo(\n    () =>\n      resolveCsvViewerDialect({\n        dialect: dialectProp,\n        source,\n        resource,\n      }),\n    [dialectProp, source, resource],\n  );\n  const resourceState = useCsvResourceState({\n    source,\n    resource,\n    dialect,\n    retryVersion,\n  });\n  const gridRef = React.useRef<CsvGridHandle>(null);\n  const [zoom, setZoom] = React.useState(1);\n  const columns = resourceState.columns;\n  const sourceRows = resourceState.sourceRows;\n  const rowStore = resourceState.rowStore;\n  const rowCount = rowStore.rowCount;\n  const canExportTable =\n    resourceState.status === \"ready\" || resourceState.status === \"empty\";\n  const sortResetKey = csvViewerSortResetKey({\n    dialect,\n    source,\n    resource,\n  });\n  const exportFileName = csvViewerExportFileName({\n    dialect,\n    source,\n    resource,\n    fallback: defaultCsvDownloadName,\n  });\n  const downloadActions = React.useMemo(() => {\n    return csvViewerDownloadActions({\n      resource,\n      columns,\n      sourceRows,\n      dialect,\n      fileName: exportFileName,\n      canExportTable,\n    });\n  }, [canExportTable, columns, dialect, exportFileName, resource, sourceRows]);\n\n  React.useImperativeHandle(\n    ref ?? null,\n    () => ({\n      scrollToCell: (cellAddress, options) => {\n        gridRef.current?.scrollToCell(cellAddress, options);\n      },\n      getViewportElement: () => gridRef.current?.getViewportElement() ?? null,\n    }),\n    [],\n  );\n\n  const statusNode = React.useMemo(\n    () =>\n      csvViewerStatusNode({\n        resourceState,\n        resource,\n        rowCount,\n        showDownload: controls,\n        onRetry: () => setRetryVersion((version) => version + 1),\n      }),\n    [controls, resource, resourceState, rowCount],\n  );\n  const zoomOut = React.useCallback(\n    () => setZoom((value) => clampCsvViewerZoom(value / 1.2)),\n    [],\n  );\n  const zoomIn = React.useCallback(\n    () => setZoom((value) => clampCsvViewerZoom(value * 1.2)),\n    [],\n  );\n  const resetZoom = React.useCallback(() => setZoom(1), []);\n  useCsvControlsRegistration({\n    columnCount: columns.length,\n    downloadActions,\n    isLoading: resourceState.status === \"loading\",\n    onResetZoom: resetZoom,\n    onZoomIn: zoomIn,\n    onZoomOut: zoomOut,\n    rowCount,\n    zoom,\n  });\n\n  return (\n    <CsvViewerFrame\n      className={className}\n      fillHeight={fillHeight}\n      frame={frame}\n      zoom={zoom}\n    >\n      <CsvViewerHeader\n        controls={controls}\n        rowCount={rowCount}\n        columnCount={columns.length}\n        isLoading={resourceState.status === \"loading\"}\n        zoom={zoom}\n        onZoomChange={setZoom}\n        downloadActions={downloadActions}\n      />\n      <CsvGrid\n        ref={gridRef}\n        columns={columns}\n        rowStore={rowStore}\n        activeCell={activeCell ?? null}\n        height={height}\n        fillHeight={fillHeight}\n        isolateStyles={isolateStyles}\n        scale={zoom}\n        sortResetKey={sortResetKey}\n        statusNode={statusNode}\n      />\n    </CsvViewerFrame>\n  );\n});\n\nexport {\n  type CsvCellAddress,\n  type CsvDialect,\n  type CsvDocumentSource,\n  type CsvTableSource,\n  type CsvTable,\n  type CsvViewerSource,\n};\n\nfunction useCsvControlsRegistration({\n  columnCount,\n  downloadActions,\n  isLoading,\n  onResetZoom,\n  onZoomIn,\n  onZoomOut,\n  rowCount,\n  zoom,\n}: {\n  columnCount: number;\n  downloadActions: ViewerControlsState[\"downloads\"];\n  isLoading: boolean;\n  onResetZoom: () => void;\n  onZoomIn: () => void;\n  onZoomOut: () => void;\n  rowCount: number;\n  zoom: number;\n}) {\n  const onControlsChange = useViewerControlsRegistration();\n  const rowLabel = `${rowCount.toLocaleString()} row${rowCount === 1 ? \"\" : \"s\"}`;\n  const columnLabel = `${columnCount} column${columnCount === 1 ? \"\" : \"s\"}`;\n  const controlsState = React.useMemo<ViewerControlsState>(\n    () => ({\n      title: isLoading ? `${rowLabel} loading` : rowLabel,\n      subtitle: isLoading ? null : columnLabel,\n      loading: isLoading,\n      zoom: {\n        scale: zoom,\n        onZoomOut,\n        onZoomIn,\n        onFit: onResetZoom,\n        fitLabel: \"Reset zoom\",\n      },\n      downloads: downloadActions,\n    }),\n    [\n      columnLabel,\n      downloadActions,\n      isLoading,\n      onResetZoom,\n      onZoomIn,\n      onZoomOut,\n      rowLabel,\n      zoom,\n    ],\n  );\n\n  useKeyedMountEffect(\n    joinEffectKey([\"csv-controls\", onControlsChange, controlsState]),\n    () => {\n      if (!onControlsChange) return;\n      onControlsChange(controlsState);\n      return () => onControlsChange(null);\n    },\n  );\n}\n\nfunction clampCsvViewerZoom(zoom: number) {\n  return Math.max(0.1, Math.min(5, zoom));\n}\n",
      "type": "registry:ui",
      "target": "@ui/csv-viewer.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/file-viewer-core.ts",
      "content": "import {\n  detectCategory,\n  extensionOf,\n  extractName,\n  resolveViewerDescriptor,\n  textPayloadKey,\n  type FileCategory,\n  type ViewerDescriptor,\n  type ViewerSource,\n} from \"@/lib/viewer-source\";\n\nexport type { FileCategory, ViewerSource };\nexport type FileViewerFallbackSize = { width: number; height: number };\n\nexport interface FileViewerCoreProps {\n  source: ViewerSource;\n  category?: FileCategory;\n  className?: string;\n  /** Intrinsic first-frame size for image/TIFF loading skeletons. */\n  fallbackFrameSize?: FileViewerFallbackSize;\n  /** Intrinsic first-slide size for PPTX loading skeletons. */\n  fallbackSlideSize?: FileViewerFallbackSize;\n  isolateStyles?: boolean;\n}\n\nexport type FileDescriptor = ViewerDescriptor;\n\nexport function resolveFileDescriptor({\n  source,\n  category,\n}: FileViewerCoreProps): FileDescriptor {\n  return resolveViewerDescriptor({\n    source,\n    category,\n  });\n}\n\nexport function descriptorResetKey(descriptor: FileDescriptor): string {\n  return [\n    descriptorIdentityResetKey(descriptor),\n    descriptor.displayName,\n    descriptor.mimeType ?? \"\",\n    descriptor.category,\n  ].join(\"\\u0000\");\n}\n\nfunction descriptorIdentityResetKey(descriptor: FileDescriptor): string {\n  if (\n    descriptor.source.kind === \"text\" &&\n    descriptor.source.identityKey == null\n  ) {\n    return textPayloadKey(descriptor.source.text);\n  }\n  return descriptor.identityKey;\n}\n\nexport function isProseTextDescriptor(descriptor: FileDescriptor): boolean {\n  if (descriptor.category !== \"text\") return false;\n\n  const extension = extensionOf(descriptor.fileName);\n  if (extension === \"txt\" || extension === \"text\") return true;\n  return !extension && descriptor.mimeType?.toLowerCase() === \"text/plain\";\n}\n\nexport { detectCategory, extensionOf, extractName };\n",
      "type": "registry:ui",
      "target": "@ui/file-viewer-core.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/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/csv-viewer-chrome.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport type { ViewerDownloadAction } from \"@/lib/viewer-download-actions\";\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\n\nimport type { CsvResourceState } from \"./csv-viewer-state\";\nimport { ViewerErrorState } from \"./viewer-error\";\nimport { ViewerControls } from \"./viewer-controls\";\n\nexport const CSV_VIEWER_BASE_FONT_SIZE = 13;\n\nexport function CsvViewerFrame({\n  className,\n  frame,\n  fillHeight,\n  zoom,\n  children,\n}: {\n  className?: string;\n  frame: boolean;\n  fillHeight: boolean;\n  zoom: number;\n  children: React.ReactNode;\n}) {\n  return (\n    <div\n      data-slot=\"csv-viewer\"\n      className={cn(\n        \"bg-card flex flex-col overflow-hidden\",\n        frame && \"rounded-xl border\",\n        fillHeight && \"h-full min-h-0 flex-1\",\n        className,\n      )}\n      style={{ fontSize: CSV_VIEWER_BASE_FONT_SIZE * zoom }}\n    >\n      {children}\n    </div>\n  );\n}\n\nexport function CsvViewerHeader({\n  controls,\n  rowCount,\n  columnCount,\n  isLoading,\n  zoom,\n  downloadActions,\n  onZoomChange,\n}: {\n  controls: boolean;\n  rowCount: number;\n  columnCount: number;\n  isLoading: boolean;\n  zoom: number;\n  downloadActions: ViewerDownloadAction[];\n  onZoomChange: (zoom: number) => void;\n}) {\n  const rowLabel = `${rowCount.toLocaleString()} row${rowCount === 1 ? \"\" : \"s\"}`;\n  const columnLabel = `${columnCount} column${columnCount === 1 ? \"\" : \"s\"}`;\n\n  return controls ? (\n    <ViewerControls\n      title={isLoading ? `${rowLabel} loading` : rowLabel}\n      subtitle={isLoading ? null : columnLabel}\n      loading={isLoading}\n      zoom={{\n        scale: zoom,\n        onZoomOut: () => onZoomChange(clampCsvZoom(zoom / 1.2)),\n        onZoomIn: () => onZoomChange(clampCsvZoom(zoom * 1.2)),\n        onFit: () => onZoomChange(1),\n        fitLabel: \"Reset zoom\",\n      }}\n      downloads={downloadActions}\n    />\n  ) : null;\n}\n\nfunction clampCsvZoom(zoom: number) {\n  return Math.max(0.1, Math.min(5, zoom));\n}\n\nexport function csvViewerStatusNode({\n  resourceState,\n  resource,\n  rowCount,\n  showDownload,\n  onRetry,\n}: {\n  resourceState: CsvResourceState;\n  resource: ViewerResource | null;\n  rowCount: number;\n  showDownload: boolean;\n  onRetry: () => void;\n}): React.ReactNode {\n  if (resourceState.status === \"error\") {\n    return (\n      <ViewerErrorState\n        error={resourceState.error}\n        format=\"csv\"\n        sourceKind={resource?.sourceKind}\n        download={showDownload ? resource?.originalDownload : null}\n        variant=\"inline\"\n        onRetry={onRetry}\n      />\n    );\n  }\n\n  if (rowCount === 0 && resourceState.status !== \"loading\") {\n    return (\n      <div className=\"text-muted-foreground flex h-24 items-center justify-center text-xs\">\n        No rows\n      </div>\n    );\n  }\n\n  return null;\n}\n",
      "type": "registry:ui",
      "target": "@ui/csv-viewer-chrome.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/csv-viewer-download.ts",
      "content": "import {\n  isTabDelimited,\n  normalizeCsvDelimiter,\n  type CsvDialect,\n} from \"@/lib/csv\";\nimport type { ViewerDownloadAction } from \"@/lib/viewer-download-actions\";\nimport type { ViewerResource } from \"@/lib/viewer-resource\";\n\nexport function escapeDelimitedField(value: string, delimiter: string): string {\n  const text = value ?? \"\";\n  return text.includes(delimiter) || /[\"\\r\\n]/.test(text)\n    ? `\"${text.replace(/\"/g, '\"\"')}\"`\n    : text;\n}\n\nexport function serializeCsvTable({\n  columns,\n  sourceRows,\n  dialect,\n}: {\n  columns: string[];\n  sourceRows: string[][];\n  dialect: CsvDialect;\n}): string {\n  const delimiter =\n    normalizeCsvDelimiter(dialect.delimiter) ?? dialect.delimiter;\n  const lines = [\n    columns\n      .map((value) => escapeDelimitedField(value, delimiter))\n      .join(delimiter),\n  ];\n  for (const sourceRow of sourceRows) {\n    const row = fitExportRow(sourceRow, columns.length);\n    lines.push(\n      row\n        .map((value) => escapeDelimitedField(value, delimiter))\n        .join(delimiter),\n    );\n  }\n  return lines.join(\"\\r\\n\");\n}\n\nfunction fitExportRow(row: string[], columnCount: number): string[] {\n  const out = row.slice(0, columnCount);\n  while (out.length < columnCount) out.push(\"\");\n  return out;\n}\n\nexport function defaultCsvDownloadName(dialect: CsvDialect): string {\n  return isTabDelimited(dialect) ? \"data.tsv\" : \"data.csv\";\n}\n\nexport function createCsvExportAction({\n  columns,\n  sourceRows,\n  dialect,\n  fileName,\n  isDisabled,\n}: {\n  columns: string[];\n  sourceRows: string[][];\n  dialect: CsvDialect;\n  fileName: string;\n  isDisabled?: boolean;\n}): ViewerDownloadAction {\n  return {\n    id: \"csv-export-table\",\n    label: \"Export table\",\n    fileName,\n    origin: \"derived\",\n    isDisabled,\n    getPayload: () => ({\n      kind: \"text\",\n      text: serializeCsvTable({ columns, sourceRows, dialect }),\n      mimeType: isTabDelimited(dialect)\n        ? \"text/tab-separated-values;charset=utf-8\"\n        : \"text/csv;charset=utf-8\",\n    }),\n  };\n}\n\nexport function csvViewerDownloadActions({\n  resource,\n  columns,\n  sourceRows,\n  dialect,\n  fileName,\n  canExportTable,\n}: {\n  resource: ViewerResource | null;\n  columns: string[];\n  sourceRows: string[][];\n  dialect: CsvDialect;\n  fileName: string;\n  canExportTable: boolean;\n}): ViewerDownloadAction[] {\n  const actions: ViewerDownloadAction[] = [];\n  if (resource) {\n    actions.push({\n      ...resource.originalDownload,\n      label: \"Download original\",\n    });\n  }\n  actions.push(\n    createCsvExportAction({\n      columns,\n      sourceRows,\n      dialect,\n      fileName,\n      isDisabled: !canExportTable,\n    }),\n  );\n  return actions;\n}\n",
      "type": "registry:ui",
      "target": "@ui/csv-viewer-download.ts"
    },
    {
      "path": "registry/new-york-v4/ui/csv-viewer-grid.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { ChevronDown, ChevronUp } from \"lucide-react\";\n\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\nimport { cn } from \"@/lib/utils\";\n\nimport {\n  useCsvRowPatcher,\n  type CsvRowPatchState,\n} from \"./csv-viewer-row-patcher\";\nimport { csvCellClassName } from \"./csv-viewer-cell-classes\";\nimport type { CsvRowStore } from \"./csv-row-store\";\nimport {\n  CSV_SCROLLBAR_CSS,\n  HeaderAwareScrollbar,\n} from \"./csv-viewer-scrollbar\";\nimport {\n  sortCsvRowsInWorker,\n  sortCsvRowsOnMainThread,\n} from \"./csv-viewer-sort-worker\";\nimport type { CsvCellAddress } from \"./csv-viewer-state\";\nimport { CsvStyleScope } from \"./csv-viewer-style-scope\";\nimport { fixedGridColumnWidths } from \"./fixed-grid-columns\";\nimport { getFixedGridCanvasStyle } from \"./fixed-grid-layout\";\nimport {\n  FixedGridNativeFindIndex,\n  type FixedGridNativeFindCellAddress,\n} from \"./fixed-grid-native-find-index\";\nimport { FixedGridRowWindow } from \"./fixed-grid-row-window\";\nimport { getFixedGridRowStyle } from \"./fixed-grid-row-style\";\nimport { buildVirtualGridTemplate } from \"./fixed-grid-template\";\nimport { FixedGridViewport } from \"./fixed-grid-viewport\";\nimport {\n  useFixedGridVirtualization,\n  useFixedRowPool,\n  type FixedGridColumnItem,\n  type FixedGridRowPoolSlot,\n} from \"./fixed-grid-virtualization\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\ntype Row = string[] | undefined;\n\nexport interface CsvGridHandle {\n  scrollToCell: (\n    cellAddress: CsvCellAddress,\n    options?: { behavior?: ScrollBehavior },\n  ) => void;\n  getViewportElement: () => HTMLDivElement | null;\n}\n\nexport interface CsvGridProps {\n  columns: string[];\n  rowStore: CsvRowStore;\n  activeCell: CsvCellAddress | null;\n  height: number;\n  fillHeight: boolean;\n  isolateStyles: boolean;\n  scale: number;\n  sortResetKey: unknown;\n  statusNode: React.ReactNode;\n}\n\nconst CSV_TABLE_LABEL = \"CSV data\";\nconst COLUMN_WIDTH = 180;\nconst COLUMN_OVERSCAN = 2;\nconst JUMP_COLUMN_OVERSCAN = 0;\nconst ROW_HEIGHT = 33;\nconst ROW_NUMBER_WIDTH = 56;\nconst ROW_OVERSCAN = 4;\nconst JUMP_ROW_OVERSCAN = 0;\nconst SMALL_TABLE_ROW_LIMIT = 200;\nconst SMALL_TABLE_COLUMN_LIMIT = 8;\nconst WORKER_SORT_ROW_THRESHOLD = 20_000;\nconst CSV_NATIVE_FIND_MAX_INDEXED_CELLS = 1_000_000;\n\nexport const CsvGrid = React.forwardRef<CsvGridHandle, CsvGridProps>(\n  function CsvGrid(\n    {\n      columns,\n      rowStore,\n      activeCell,\n      height,\n      fillHeight,\n      isolateStyles,\n      scale,\n      sortResetKey,\n      statusNode,\n    },\n    ref,\n  ) {\n    const [sort, setSort] = React.useState<{\n      columnIndex: number;\n      descending: boolean;\n    } | null>(null);\n    const columnShapeKey = React.useMemo(\n      () => columns.join(\"\\u0000\"),\n      [columns],\n    );\n\n    useKeyedMountEffect(\n      joinEffectKey([\"csv-sort-reset\", columnShapeKey, sortResetKey]),\n      () => {\n        setSort(null);\n      },\n    );\n\n    const toggleSort = React.useCallback((columnIndex: number) => {\n      setSort((current) =>\n        !current || current.columnIndex !== columnIndex\n          ? { columnIndex, descending: false }\n          : current.descending\n            ? null\n            : { columnIndex, descending: true },\n      );\n    }, []);\n\n    const rowOrder = useCsvSortedRowOrder({ rowStore, sort });\n\n    const displayIndexByRowIndex = React.useMemo<Map<\n      number,\n      number\n    > | null>(() => {\n      if (!rowOrder) return null;\n      const map = new Map<number, number>();\n      rowOrder.forEach((rowIndex, displayRowIndex) => {\n        map.set(rowIndex, displayRowIndex);\n      });\n      return map;\n    }, [rowOrder]);\n\n    const rowAt = React.useCallback(\n      (displayRowIndex: number): Row => {\n        const sourceRowIndex = rowOrder?.[displayRowIndex] ?? displayRowIndex;\n        return rowStore.getRow(sourceRowIndex);\n      },\n      [rowStore, rowOrder],\n    );\n\n    const rowIndexAt = React.useCallback(\n      (displayRowIndex: number): number =>\n        rowOrder?.[displayRowIndex] ?? displayRowIndex,\n      [rowOrder],\n    );\n\n    const viewportRef = React.useRef<HTMLDivElement>(null);\n    const rowOffsetRef = React.useRef<HTMLDivElement>(null);\n    const rowWindowRef = React.useRef<HTMLDivElement>(null);\n    const [viewportElement, setViewportElement] =\n      React.useState<HTMLDivElement | null>(null);\n    const setViewportRef = React.useCallback((node: HTMLDivElement | null) => {\n      viewportRef.current = node;\n      setViewportElement((current) => (current === node ? current : node));\n    }, []);\n    const columnCount = columns.length;\n    const rowCount = rowStore.rowCount;\n    const columnOffset = 1;\n    const shouldVirtualizeRows = rowCount > SMALL_TABLE_ROW_LIMIT;\n    const shouldVirtualizeColumns = columnCount > SMALL_TABLE_COLUMN_LIMIT;\n    const effectiveRowHeight = Math.max(1, Math.round(ROW_HEIGHT * scale));\n    const effectiveColumnWidth = Math.max(1, Math.round(COLUMN_WIDTH * scale));\n    const effectiveRowNumberWidth = Math.round(ROW_NUMBER_WIDTH * scale);\n    const columnItemsRef = React.useRef<FixedGridColumnItem[]>([]);\n    const viewportClientHeightRef = React.useRef(0);\n    const getRowPatchState = React.useCallback(\n      (): CsvRowPatchState => ({\n        activeCell: activeCell ?? null,\n        columnItems: columnItemsRef.current,\n        effectiveRowHeight,\n        viewportHeight: viewportClientHeightRef.current,\n        rowStore,\n        rowOrder,\n        shouldVirtualizeRows,\n      }),\n      [\n        activeCell,\n        effectiveRowHeight,\n        rowStore,\n        rowOrder,\n        shouldVirtualizeRows,\n      ],\n    );\n    const rowPatcher = useCsvRowPatcher({\n      rowOffsetRef,\n      rowWindowRef,\n      getState: getRowPatchState,\n    });\n    const rowScrollStrategy = React.useMemo(\n      () => ({ handleViewport: rowPatcher.patch }),\n      [rowPatcher],\n    );\n\n    const {\n      virtualRows,\n      totalRowSize,\n      totalColumnSize,\n      columnItems,\n      virtualRowWindow,\n      leftPad,\n      rightPad,\n      scrollToCell,\n      viewportClientHeight,\n    } = useFixedGridVirtualization({\n      rowCount,\n      columnCount,\n      rowSize: effectiveRowHeight,\n      columnSize: effectiveColumnWidth,\n      rowOverscan: ROW_OVERSCAN,\n      columnOverscan: COLUMN_OVERSCAN,\n      jumpRowOverscan: JUMP_ROW_OVERSCAN,\n      jumpColumnOverscan: JUMP_COLUMN_OVERSCAN,\n      minimumRenderedRows: 1,\n      rowScrollStrategy,\n      scrollRef: viewportRef,\n      scrollElement: viewportElement,\n      virtualizeColumns: shouldVirtualizeColumns,\n    });\n\n    columnItemsRef.current = columnItems;\n    viewportClientHeightRef.current = viewportClientHeight;\n\n    React.useImperativeHandle(\n      ref ?? null,\n      () => ({\n        scrollToCell: (cellAddress, options) => {\n          const behavior = options?.behavior ?? \"smooth\";\n          const displayRowIndex =\n            displayIndexByRowIndex?.get(cellAddress.rowIndex) ??\n            cellAddress.rowIndex;\n          scrollToCell({\n            rowIndex: displayRowIndex,\n            columnIndex: cellAddress.columnIndex,\n            behavior,\n            align: \"center\",\n          });\n        },\n        getViewportElement: () => viewportRef.current,\n      }),\n      [displayIndexByRowIndex, scrollToCell],\n    );\n\n    const gridTemplate = React.useMemo(\n      () =>\n        buildVirtualGridTemplate({\n          leadingWidth: effectiveRowNumberWidth,\n          leftPad,\n          columnWidths: fixedGridColumnWidths(columnItems),\n          rightPad,\n        }),\n      [effectiveRowNumberWidth, leftPad, columnItems, rightPad],\n    );\n    const totalWidth = effectiveRowNumberWidth + totalColumnSize;\n    const minimumRowPoolSize =\n      Math.ceil(viewportClientHeight / effectiveRowHeight) +\n      ROW_OVERSCAN * 2 +\n      2;\n    const rowPoolSlots = useFixedRowPool({\n      minimumPoolSize: minimumRowPoolSize,\n      rowCount,\n      virtualRows: virtualRowWindow.items,\n    });\n    const shouldIndexNativeFind =\n      rowCount > 0 &&\n      columnCount > 0 &&\n      (shouldVirtualizeRows || shouldVirtualizeColumns);\n    const nativeFindCellText = React.useCallback(\n      (displayRowIndex: number, columnIndex: number) =>\n        rowAt(displayRowIndex)?.[columnIndex] ?? \"\",\n      [rowAt],\n    );\n    const scrollToNativeFindCell = React.useCallback(\n      ({ rowIndex, columnIndex }: FixedGridNativeFindCellAddress) => {\n        scrollToCell({\n          rowIndex,\n          columnIndex,\n          behavior: \"auto\",\n          align: \"center\",\n        });\n      },\n      [scrollToCell],\n    );\n\n    useKeyedMountEffect(\n      joinEffectKey([\n        \"csv-row-patcher\",\n        rowPatcher,\n        virtualRows,\n        columnItems,\n        shouldVirtualizeRows,\n      ]),\n      () => {\n        // After React commits the canonical row window, push that window's\n        // visibility, position, and text back onto the pooled DOM so any stale\n        // state left by the imperative scroll patcher (a `hidden` row React's\n        // reconciler never re-showed, or a cyclic-column cell it never rewrote)\n        // is cleared. Falls back to a plain cache invalidation when row\n        // virtualization is inactive.\n        if (shouldVirtualizeRows) {\n          rowPatcher.resync(virtualRows);\n        } else {\n          rowPatcher.invalidate();\n        }\n      },\n    );\n\n    return (\n      <div\n        data-slot=\"csv-grid\"\n        role=\"table\"\n        aria-label={CSV_TABLE_LABEL}\n        aria-rowcount={rowCount + 1}\n        aria-colcount={columnCount + columnOffset}\n        className={cn(\"relative\", fillHeight && \"min-h-0 flex-1\")}\n      >\n        <CsvStyleScope\n          isolate={isolateStyles}\n          className={cn(\"relative\", fillHeight && \"h-full min-h-0\")}\n          style={fillHeight ? undefined : { height, maxHeight: \"100%\" }}\n        >\n          <style>{CSV_SCROLLBAR_CSS}</style>\n          <FixedGridNativeFindIndex\n            rowCount={rowCount}\n            columnCount={columnCount}\n            getCellText={nativeFindCellText}\n            onCellMatch={scrollToNativeFindCell}\n            dataSlot=\"csv-native-find-index\"\n            enabled={shouldIndexNativeFind}\n            maxIndexedCells={CSV_NATIVE_FIND_MAX_INDEXED_CELLS}\n          />\n          <FixedGridViewport\n            scrollRef={setViewportRef}\n            dataSlot=\"csv-body\"\n            aria-label={CSV_TABLE_LABEL}\n            tabIndex={0}\n          >\n            <div\n              style={getFixedGridCanvasStyle({\n                width: totalWidth,\n                contain: true,\n              })}\n            >\n              <div\n                role=\"row\"\n                aria-rowindex={1}\n                data-slot=\"csv-header\"\n                className=\"sticky top-0 z-20 grid border-b\"\n                style={{\n                  gridTemplateColumns: gridTemplate,\n                  backgroundColor:\n                    \"color-mix(in oklab, var(--card) 92%, var(--foreground))\",\n                }}\n              >\n                <div\n                  role=\"columnheader\"\n                  aria-colindex={1}\n                  aria-label=\"Row number\"\n                  className=\"sticky left-0 z-10 border-r bg-[color-mix(in_oklab,var(--card)_94%,var(--foreground))]\"\n                  style={{ height: effectiveRowHeight }}\n                />\n                <Spacer width={leftPad} />\n                {columnItems.map((item) => (\n                  <HeaderCell\n                    key={item.index}\n                    name={columns[item.index] || `Column ${item.index + 1}`}\n                    columnIndex={columnOffset + item.index + 1}\n                    height={effectiveRowHeight}\n                    sorted={\n                      sort?.columnIndex === item.index\n                        ? sort.descending\n                          ? \"desc\"\n                          : \"asc\"\n                        : false\n                    }\n                    onToggle={() => toggleSort(item.index)}\n                  />\n                ))}\n                <Spacer width={rightPad} />\n              </div>\n\n              {statusNode ? (\n                statusNode\n              ) : shouldVirtualizeRows ? (\n                <FixedGridRowWindow\n                  role=\"rowgroup\"\n                  rowOffsetRef={rowOffsetRef}\n                  rowWindowRef={rowWindowRef}\n                  totalSize={totalRowSize}\n                  virtualRowWindow={virtualRowWindow}\n                  viewportHeight={viewportClientHeight}\n                  offsetDataSlot=\"csv-row-offset\"\n                  windowDataSlot=\"csv-row-window\"\n                >\n                  {rowPoolSlots.map((slot) => (\n                    <CsvRowSlot\n                      key={slot.slotIndex}\n                      slot={slot}\n                      sourceRowCount={rowCount}\n                      rowAt={rowAt}\n                      rowIndexAt={rowIndexAt}\n                      gridTemplate={gridTemplate}\n                      rowHeight={effectiveRowHeight}\n                      columnOffset={columnOffset}\n                      columnItems={columnItems}\n                      leftPad={leftPad}\n                      rightPad={rightPad}\n                      activeCell={activeCell}\n                    />\n                  ))}\n                </FixedGridRowWindow>\n              ) : (\n                <div role=\"rowgroup\">\n                  {Array.from({ length: rowCount }, (_, displayRowIndex) => (\n                    <CsvRow\n                      key={displayRowIndex}\n                      cells={rowAt(displayRowIndex)}\n                      displayRowIndex={displayRowIndex}\n                      rowIndex={rowIndexAt(displayRowIndex)}\n                      gridTemplate={gridTemplate}\n                      rowHeight={effectiveRowHeight}\n                      columnOffset={columnOffset}\n                      columnItems={columnItems}\n                      leftPad={leftPad}\n                      rightPad={rightPad}\n                      activeColumnIndex={\n                        activeCell?.rowIndex === rowIndexAt(displayRowIndex)\n                          ? activeCell.columnIndex\n                          : null\n                      }\n                    />\n                  ))}\n                </div>\n              )}\n            </div>\n          </FixedGridViewport>\n          <HeaderAwareScrollbar\n            scrollRef={viewportRef}\n            headerHeight={effectiveRowHeight}\n          />\n        </CsvStyleScope>\n      </div>\n    );\n  },\n);\n\nfunction useCsvSortedRowOrder({\n  rowStore,\n  sort,\n}: {\n  rowStore: CsvRowStore;\n  sort: { columnIndex: number; descending: boolean } | null;\n}): number[] | null {\n  const [workerRowOrder, setWorkerRowOrder] = React.useState<number[] | null>(\n    null,\n  );\n  const sourceRows = React.useMemo(\n    () => (sort ? rowStore.materializeRows() : null),\n    [rowStore, sort],\n  );\n  const shouldUseWorker =\n    !!sort &&\n    !!sourceRows &&\n    sourceRows.length >= WORKER_SORT_ROW_THRESHOLD &&\n    typeof Worker !== \"undefined\";\n\n  const workerSortKey =\n    sort && sourceRows && shouldUseWorker\n      ? joinEffectKey([\"csv-worker-sort\", shouldUseWorker, sort, sourceRows])\n      : null;\n  useKeyedMountEffect(workerSortKey, () => {\n    if (!sort || !sourceRows || !shouldUseWorker) {\n      setWorkerRowOrder(null);\n      return;\n    }\n    setWorkerRowOrder(null);\n\n    const controller = new AbortController();\n    void sortCsvRowsInWorker({\n      sourceRows,\n      columnIndex: sort.columnIndex,\n      descending: sort.descending,\n      signal: controller.signal,\n    }).then(\n      (rowOrder) => setWorkerRowOrder(rowOrder),\n      (error) => {\n        if (controller.signal.aborted) return;\n        setWorkerRowOrder(\n          sortCsvRowsOnMainThread({\n            sourceRows,\n            columnIndex: sort.columnIndex,\n            descending: sort.descending,\n          }),\n        );\n      },\n    );\n\n    return () => controller.abort();\n  });\n\n  return React.useMemo(() => {\n    if (!sort || !sourceRows) return null;\n    if (shouldUseWorker) return workerRowOrder;\n    return sortCsvRowsOnMainThread({\n      sourceRows,\n      columnIndex: sort.columnIndex,\n      descending: sort.descending,\n    });\n  }, [shouldUseWorker, sort, sourceRows, workerRowOrder]);\n}\n\nfunction Spacer({ width }: { width: number }) {\n  return <div role=\"presentation\" aria-hidden style={{ width }} />;\n}\n\nfunction HeaderCell({\n  name,\n  columnIndex,\n  height,\n  sorted,\n  onToggle,\n}: {\n  name: string;\n  columnIndex: number;\n  height: number;\n  sorted: \"asc\" | \"desc\" | false;\n  onToggle: () => void;\n}) {\n  return (\n    <div\n      role=\"columnheader\"\n      aria-colindex={columnIndex}\n      aria-sort={\n        sorted === \"asc\"\n          ? \"ascending\"\n          : sorted === \"desc\"\n            ? \"descending\"\n            : \"none\"\n      }\n      data-slot=\"csv-header-cell\"\n      className=\"border-r last:border-r-0\"\n    >\n      <button\n        type=\"button\"\n        onClick={onToggle}\n        className=\"text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:bg-muted flex w-full items-center gap-1 px-3 text-left font-medium transition-colors focus-visible:outline-none\"\n        style={{ height }}\n        title={`Sort by ${name}`}\n      >\n        <span className=\"truncate\">{name}</span>\n        {sorted ? (\n          sorted === \"asc\" ? (\n            <ChevronUp\n              className=\"text-muted-foreground size-3.5 shrink-0\"\n              aria-hidden\n            />\n          ) : (\n            <ChevronDown\n              className=\"text-muted-foreground size-3.5 shrink-0\"\n              aria-hidden\n            />\n          )\n        ) : null}\n      </button>\n    </div>\n  );\n}\n\nfunction CsvRowSlot({\n  slot,\n  sourceRowCount,\n  rowAt,\n  rowIndexAt,\n  gridTemplate,\n  rowHeight,\n  columnOffset,\n  columnItems,\n  leftPad,\n  rightPad,\n  activeCell,\n}: {\n  slot: FixedGridRowPoolSlot;\n  sourceRowCount: number;\n  rowAt: (displayRowIndex: number) => Row;\n  rowIndexAt: (displayRowIndex: number) => number;\n  gridTemplate: string;\n  rowHeight: number;\n  columnOffset: number;\n  columnItems: FixedGridColumnItem[];\n  leftPad: number;\n  rightPad: number;\n  activeCell: CsvCellAddress | null;\n}) {\n  const fallbackDisplayRowIndex =\n    sourceRowCount > 0 ? Math.min(slot.slotIndex, sourceRowCount - 1) : 0;\n  const displayRowIndex = slot.virtualRow?.index ?? fallbackDisplayRowIndex;\n  const rowIndex = rowIndexAt(displayRowIndex);\n\n  return (\n    <CsvRow\n      cells={rowAt(displayRowIndex)}\n      displayRowIndex={displayRowIndex}\n      rowIndex={rowIndex}\n      gridTemplate={gridTemplate}\n      rowHeight={rowHeight}\n      columnOffset={columnOffset}\n      columnItems={columnItems}\n      leftPad={leftPad}\n      rightPad={rightPad}\n      start={slot.virtualRow?.start ?? 0}\n      hidden={slot.isHidden}\n      activeColumnIndex={\n        slot.virtualRow && activeCell?.rowIndex === rowIndex\n          ? activeCell.columnIndex\n          : null\n      }\n    />\n  );\n}\n\nconst CsvRow = React.memo(function CsvRow({\n  cells,\n  displayRowIndex,\n  rowIndex,\n  gridTemplate,\n  rowHeight,\n  columnOffset,\n  columnItems,\n  leftPad,\n  rightPad,\n  start,\n  hidden = false,\n  activeColumnIndex,\n}: {\n  cells: Row | undefined;\n  displayRowIndex: number;\n  rowIndex: number;\n  gridTemplate: string;\n  rowHeight: number;\n  columnOffset: number;\n  columnItems: FixedGridColumnItem[];\n  leftPad: number;\n  rightPad: number;\n  start?: number;\n  hidden?: boolean;\n  activeColumnIndex?: number | null;\n}) {\n  const isVirtualized = start !== undefined;\n  const style: React.CSSProperties = !isVirtualized\n    ? { gridTemplateColumns: gridTemplate, height: rowHeight }\n    : getFixedGridRowStyle({\n        gridTemplate,\n        rowHeight,\n        top: start,\n      });\n  return (\n    <div\n      role=\"row\"\n      aria-rowindex={displayRowIndex + 2}\n      hidden={hidden}\n      data-slot=\"csv-row\"\n      className={cn(\n        \"grid border-b\",\n        !isVirtualized && \"group hover:bg-muted/40\",\n      )}\n      style={style}\n    >\n      <div\n        role=\"rowheader\"\n        aria-colindex={1}\n        data-slot=\"csv-row-number\"\n        className={cn(\n          \"bg-card text-muted-foreground sticky left-0 z-[1] flex items-center justify-end border-r px-2 tabular-nums\",\n          !isVirtualized &&\n            \"group-hover:bg-[color-mix(in_oklab,var(--card)_97%,var(--foreground))]\",\n        )}\n      >\n        {rowIndex + 1}\n      </div>\n      <Spacer width={leftPad} />\n      {columnItems.map((item) => {\n        const text = cells?.[item.index] ?? \"\";\n        const isActive = activeColumnIndex === item.index;\n        return (\n          <div\n            key={item.index}\n            role=\"cell\"\n            aria-colindex={columnOffset + item.index + 1}\n            data-slot=\"csv-cell\"\n            className={csvCellClassName(isActive)}\n            title={isVirtualized ? undefined : text}\n          >\n            <span className=\"truncate\">{text}</span>\n          </div>\n        );\n      })}\n      <Spacer width={rightPad} />\n    </div>\n  );\n});\n",
      "type": "registry:ui",
      "target": "@ui/csv-viewer-grid.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/csv-viewer-row-patcher.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport type { CsvCellAddress } from \"./csv-viewer-state\";\nimport { csvCellClassName } from \"./csv-viewer-cell-classes\";\nimport type { CsvRowStore } from \"./csv-row-store\";\nimport { setFixedGridInverseRowWindowGeometry } from \"./fixed-grid-layout\";\nimport {\n  fixedVirtualItemWindow,\n  fixedVirtualItems,\n  type FixedGridColumnItem,\n  type FixedGridJumpViewportResult,\n  type FixedGridViewport,\n  type FixedGridVirtualItem,\n} from \"./fixed-grid-virtualization\";\n\nexport interface CsvRowPatchState {\n  activeCell: CsvCellAddress | null;\n  columnItems: FixedGridColumnItem[];\n  effectiveRowHeight: number;\n  viewportHeight: number;\n  rowOrder: number[] | null;\n  shouldVirtualizeRows: boolean;\n  rowStore: CsvRowStore;\n}\n\nexport interface CsvRowPatcher {\n  patch: (viewport: FixedGridViewport) => FixedGridJumpViewportResult;\n  resync: (virtualRows: FixedGridVirtualItem[]) => void;\n  invalidate: () => void;\n}\n\ninterface CsvCellHandle {\n  element: HTMLElement;\n  className: string;\n  textNode: Text | null;\n}\n\ninterface CsvRowHandle {\n  element: HTMLElement;\n  rowNumberTextNode: Text | null;\n  cells: CsvCellHandle[];\n  isHidden: boolean;\n  sourceRowIndex: number | null;\n  transform: string;\n}\n\ninterface CsvRowHandleCache {\n  rowWindow: HTMLDivElement;\n  rows: CsvRowHandle[];\n}\n\nconst PATCH_ROW_OVERSCAN = 0;\nconst MINIMUM_PATCH_VISIBLE_ROWS = 1;\nconst TEXT_NODE = 3;\n\nexport function useCsvRowPatcher({\n  rowOffsetRef,\n  rowWindowRef,\n  getState,\n}: {\n  rowOffsetRef: React.RefObject<HTMLDivElement | null>;\n  rowWindowRef: React.RefObject<HTMLDivElement | null>;\n  getState: () => CsvRowPatchState;\n}): CsvRowPatcher {\n  const rowHandleCacheRef = React.useRef<CsvRowHandleCache | null>(null);\n\n  const invalidate = React.useCallback(() => {\n    rowHandleCacheRef.current = null;\n  }, []);\n\n  // Re-assert the canonical (React-owned) row state onto every pooled row after\n  // a canonical commit. During active scroll the imperative patcher mutates\n  // each row's `hidden` attribute, transform, row number, and cell text\n  // directly. When React later commits a window, its reconciler only writes a\n  // DOM property whose value changed in React's *own* remembered vdom, so a\n  // reused slot whose canonical value is unchanged across the commit keeps\n  // whatever the patcher last wrote. That leaks two ways: a previously-hidden\n  // pool row that should now be visible stays `hidden` (a blank gap at the\n  // leading scroll edge), and a cyclic column (e.g. a repeating name) keeps a\n  // stale value from a different row even though its id and row number are\n  // correct. Re-running the canonical window through the same patch routine\n  // pushes the authoritative transform, visibility, and text back onto the DOM\n  // once scrolling settles. This runs only on canonical commits, not per frame.\n  const resync = React.useCallback(\n    (virtualRows: FixedGridVirtualItem[]) => {\n      const rowWindow = rowWindowRef.current;\n      if (!rowWindow) return;\n      const rowOffset = rowOffsetRef.current;\n      if (!rowOffset) return;\n      const cache = readRowHandles(rowWindow);\n      rowHandleCacheRef.current = cache;\n      if (cache.rows.length === 0) return;\n      const state = getState();\n      const window = fixedVirtualItemWindow(virtualRows);\n      setFixedGridInverseRowWindowGeometry({\n        rowOffsetElement: rowOffset,\n        rowWindowElement: rowWindow,\n        viewportHeight: state.viewportHeight,\n        window,\n      });\n      patchRows(cache.rows, window.items, state);\n    },\n    [getState, rowOffsetRef, rowWindowRef],\n  );\n\n  const patch = React.useCallback(\n    (viewport: FixedGridViewport): FixedGridJumpViewportResult => {\n      const state = getState();\n      if (!canPatchRows(viewport, state)) return \"pass\";\n\n      const rowWindow = rowWindowRef.current;\n      if (!rowWindow) return \"pass\";\n      const rowOffset = rowOffsetRef.current;\n      if (!rowOffset) return \"pass\";\n\n      const cache =\n        rowHandleCacheRef.current?.rowWindow === rowWindow\n          ? rowHandleCacheRef.current\n          : readRowHandles(rowWindow);\n      rowHandleCacheRef.current = cache;\n\n      if (cache.rows.length === 0) return \"pass\";\n\n      const nextRows = fixedVirtualItems({\n        count: state.rowStore.rowCount,\n        size: state.effectiveRowHeight,\n        scrollOffset: viewport.scrollTop,\n        viewportSize: viewport.clientHeight,\n        overscan: PATCH_ROW_OVERSCAN,\n        minimumVisibleCount: MINIMUM_PATCH_VISIBLE_ROWS,\n      });\n      if (nextRows.length === 0 || nextRows.length > cache.rows.length) {\n        return \"pass\";\n      }\n\n      if (\n        !canPatchRowHandles(\n          cache.rows,\n          nextRows.length,\n          state.columnItems.length,\n        )\n      ) {\n        return \"pass\";\n      }\n\n      const window = fixedVirtualItemWindow(nextRows);\n      setFixedGridInverseRowWindowGeometry({\n        rowOffsetElement: rowOffset,\n        rowWindowElement: rowWindow,\n        viewportHeight: viewport.clientHeight,\n        window,\n      });\n      patchRows(cache.rows, window.items, state);\n\n      return \"handled\";\n    },\n    [getState, rowOffsetRef, rowWindowRef],\n  );\n\n  return React.useMemo(\n    () => ({ invalidate, patch, resync }),\n    [invalidate, patch, resync],\n  );\n}\n\nfunction patchRows(\n  rowHandles: CsvRowHandle[],\n  virtualRows: ReturnType<typeof fixedVirtualItems>,\n  state: CsvRowPatchState,\n) {\n  for (let handleIndex = 0; handleIndex < rowHandles.length; handleIndex++) {\n    const rowHandle = rowHandles[handleIndex];\n    const virtualRow = virtualRows[handleIndex];\n    if (!virtualRow) {\n      setRowHidden(rowHandle, true);\n      continue;\n    }\n\n    const displayRowIndex = virtualRow.index;\n    const sourceRowIndex = state.rowOrder\n      ? state.rowOrder[displayRowIndex]\n      : displayRowIndex;\n    const sourceRow = state.rowStore.getRow(sourceRowIndex);\n    const transform = `translate3d(0, ${virtualRow.start}px, 0)`;\n\n    setRowHidden(rowHandle, false);\n    setRowTransform(rowHandle, transform);\n\n    if (rowHandle.sourceRowIndex !== sourceRowIndex) {\n      rowHandle.sourceRowIndex = sourceRowIndex;\n      setTextNodeValue(rowHandle.rowNumberTextNode, String(sourceRowIndex + 1));\n      patchCells(rowHandle, sourceRow, sourceRowIndex, state);\n    } else {\n      patchCellActiveState(rowHandle, sourceRowIndex, state);\n    }\n  }\n}\n\nfunction patchCells(\n  rowHandle: CsvRowHandle,\n  sourceRow: string[] | undefined,\n  sourceRowIndex: number,\n  state: CsvRowPatchState,\n) {\n  for (let cellIndex = 0; cellIndex < state.columnItems.length; cellIndex++) {\n    const columnIndex = state.columnItems[cellIndex]?.index;\n    const text =\n      typeof columnIndex === \"number\" ? (sourceRow?.[columnIndex] ?? \"\") : \"\";\n    setTextNodeValue(rowHandle.cells[cellIndex]?.textNode ?? null, text);\n    setCellActive(\n      rowHandle.cells[cellIndex],\n      state.activeCell?.rowIndex === sourceRowIndex &&\n        state.activeCell.columnIndex === columnIndex,\n    );\n  }\n}\n\nfunction patchCellActiveState(\n  rowHandle: CsvRowHandle,\n  sourceRowIndex: number,\n  state: CsvRowPatchState,\n) {\n  for (let cellIndex = 0; cellIndex < state.columnItems.length; cellIndex++) {\n    const columnIndex = state.columnItems[cellIndex]?.index;\n    setCellActive(\n      rowHandle.cells[cellIndex],\n      state.activeCell?.rowIndex === sourceRowIndex &&\n        state.activeCell.columnIndex === columnIndex,\n    );\n  }\n}\n\nfunction canPatchRows(viewport: FixedGridViewport, state: CsvRowPatchState) {\n  return state.shouldVirtualizeRows && !viewport.isJumpingColumns;\n}\n\nfunction canPatchRowHandles(\n  rowHandles: CsvRowHandle[],\n  visibleRowCount: number,\n  cellCount: number,\n) {\n  for (let handleIndex = 0; handleIndex < visibleRowCount; handleIndex++) {\n    const rowHandle = rowHandles[handleIndex];\n    if (!rowHandle?.rowNumberTextNode) return false;\n    for (let cellIndex = 0; cellIndex < cellCount; cellIndex++) {\n      if (!rowHandle.cells[cellIndex]?.textNode) return false;\n    }\n  }\n  return true;\n}\n\nfunction readRowHandles(rowWindow: HTMLDivElement): CsvRowHandleCache {\n  const rows = Array.from(\n    rowWindow.querySelectorAll<HTMLElement>('[data-slot=\"csv-row\"]'),\n  ).map((element) => {\n    const rowNumber = element.querySelector<HTMLElement>(\n      '[data-slot=\"csv-row-number\"]',\n    );\n    const cells = Array.from(\n      element.querySelectorAll<HTMLElement>('[data-slot=\"csv-cell\"]'),\n    ).map((cell) => ({\n      element: cell,\n      className: cell.className,\n      textNode: firstTextNode(cell.firstElementChild ?? cell),\n    }));\n\n    return {\n      element,\n      rowNumberTextNode: firstTextNode(rowNumber),\n      cells,\n      isHidden: element.hidden,\n      sourceRowIndex: null,\n      transform: element.style.transform,\n    };\n  });\n\n  return { rowWindow, rows };\n}\n\nfunction firstTextNode(element: Element | null): Text | null {\n  const node = element?.firstChild;\n  return node?.nodeType === TEXT_NODE ? (node as Text) : null;\n}\n\nfunction setTextNodeValue(textNode: Text | null, value: string) {\n  if (textNode && textNode.nodeValue !== value) textNode.nodeValue = value;\n}\n\nfunction setCellActive(cell: CsvCellHandle | undefined, isActive: boolean) {\n  if (!cell) return;\n  const className = csvCellClassName(isActive);\n  if (cell.className === className) return;\n  cell.element.className = className;\n  cell.className = className;\n}\n\nfunction setRowTransform(row: CsvRowHandle, transform: string) {\n  if (row.transform === transform) return;\n  row.element.style.transform = transform;\n  row.transform = transform;\n}\n\nfunction setRowHidden(row: CsvRowHandle, isHidden: boolean) {\n  if (row.isHidden === isHidden) return;\n  row.element.hidden = isHidden;\n  row.isHidden = isHidden;\n}\n",
      "type": "registry:ui",
      "target": "@ui/csv-viewer-row-patcher.ts"
    },
    {
      "path": "registry/new-york-v4/ui/csv-viewer-cell-classes.ts",
      "content": "export const CSV_CELL_CLASS =\n  \"flex items-center truncate border-r px-3 last:border-r-0\";\n\nexport const CSV_ACTIVE_CELL_CLASS =\n  \"bg-primary/12 ring-1 ring-primary/50 ring-offset-0 ring-inset\";\n\nexport function csvCellClassName(isActive: boolean): string {\n  return isActive\n    ? `${CSV_CELL_CLASS} ${CSV_ACTIVE_CELL_CLASS}`\n    : CSV_CELL_CLASS;\n}\n",
      "type": "registry:ui",
      "target": "@ui/csv-viewer-cell-classes.ts"
    },
    {
      "path": "registry/new-york-v4/ui/csv-row-store.ts",
      "content": "export interface CsvRowStore {\n  readonly rowCount: number;\n  readonly version: number;\n  getRow(index: number): string[] | undefined;\n  materializeRows(): string[][];\n}\n\nexport interface MutableCsvRowStore {\n  appendRows(rows: string[][]): void;\n  padRowsToColumnCount(columnCount: number): void;\n  snapshot(): CsvRowStore;\n  materializeRows(): string[][];\n}\n\nconst EMPTY_CSV_ROW_STORE = createCsvRowStoreFromRows([]);\n\nexport function emptyCsvRowStore(): CsvRowStore {\n  return EMPTY_CSV_ROW_STORE;\n}\n\nexport function createCsvRowStoreFromRows(rows: string[][]): CsvRowStore {\n  return {\n    rowCount: rows.length,\n    version: 0,\n    getRow: (index) => rows[index],\n    materializeRows: () => rows,\n  };\n}\n\nexport function createMutableCsvRowStore(): MutableCsvRowStore {\n  const chunks: string[][][] = [];\n  const starts: number[] = [];\n  let rowCount = 0;\n  let version = 0;\n\n  function appendRows(rows: string[][]) {\n    if (rows.length === 0) return;\n    starts.push(rowCount);\n    chunks.push(rows);\n    rowCount += rows.length;\n    version += 1;\n  }\n\n  function padRowsToColumnCount(columnCount: number) {\n    for (const chunk of chunks) {\n      for (const row of chunk) {\n        while (row.length < columnCount) row.push(\"\");\n      }\n    }\n    version += 1;\n  }\n\n  function getRow(index: number): string[] | undefined {\n    if (!Number.isSafeInteger(index) || index < 0 || index >= rowCount) {\n      return undefined;\n    }\n    const chunkIndex = findChunkIndex(starts, index);\n    const chunk = chunks[chunkIndex];\n    return chunk?.[index - starts[chunkIndex]];\n  }\n\n  function materializeRows(): string[][] {\n    return chunks.flat();\n  }\n\n  function snapshot(): CsvRowStore {\n    const snapshotVersion = version;\n    const snapshotRowCount = rowCount;\n    return {\n      rowCount: snapshotRowCount,\n      version: snapshotVersion,\n      getRow,\n      materializeRows,\n    };\n  }\n\n  return {\n    appendRows,\n    padRowsToColumnCount,\n    snapshot,\n    materializeRows,\n  };\n}\n\nfunction findChunkIndex(starts: number[], rowIndex: number): number {\n  let low = 0;\n  let high = starts.length - 1;\n  while (low <= high) {\n    const mid = Math.floor((low + high) / 2);\n    const start = starts[mid];\n    const nextStart = starts[mid + 1] ?? Number.POSITIVE_INFINITY;\n    if (rowIndex < start) {\n      high = mid - 1;\n    } else if (rowIndex >= nextStart) {\n      low = mid + 1;\n    } else {\n      return mid;\n    }\n  }\n  return 0;\n}\n",
      "type": "registry:ui",
      "target": "@ui/csv-row-store.ts"
    },
    {
      "path": "registry/new-york-v4/ui/csv-viewer-sort.ts",
      "content": "/**\n * Sorting helpers for the CSV grid.\n *\n * Cells are plain strings, but a column is often numeric (or mostly numeric\n * with a few text outliers). `compareCsvCells` keeps numeric-looking values in\n * numeric order while still producing a *total order* over mixed values: all\n * numeric cells sort together (ahead of text), and text cells sort\n * lexicographically. A total order matters because `Array.prototype.sort`\n * requires a consistent comparator — a comparator that is numeric for some\n * pairs and lexicographic for others can be intransitive, which makes the\n * displayed order depend on the original row order.\n */\n\n/** A value is treated as numeric when `Number()` yields a finite-ish number. */\nexport function isNumericCell(value: string): boolean {\n  return value !== \"\" && !Number.isNaN(Number(value));\n}\n\nexport function compareCsvCells(a: string, b: string): number {\n  const aNumeric = isNumericCell(a);\n  const bNumeric = isNumericCell(b);\n  if (aNumeric && bNumeric) {\n    const diff = Number(a) - Number(b);\n    return diff < 0 ? -1 : diff > 0 ? 1 : 0;\n  }\n  // Numeric cells always sort ahead of text cells so the comparator stays a\n  // total order regardless of which pairs are being compared.\n  if (aNumeric) return -1;\n  if (bNumeric) return 1;\n  return a < b ? -1 : a > b ? 1 : 0;\n}\n\nexport type CsvSortKey =\n  | {\n      kind: \"number\";\n      value: number;\n      rowIndex: number;\n    }\n  | {\n      kind: \"text\";\n      value: string;\n      rowIndex: number;\n    };\n\n/**\n * Returns the display order (source-row indices) for a column sort. Ascending\n * order follows `compareCsvCells`; descending negates it. Rows that compare\n * equal always keep their original relative order in *both* directions: a\n * naive `reverse()` of the ascending order would flip tied rows, so equal keys\n * fall back to the source index as a stable tiebreaker.\n */\nexport function sortedRowOrder(\n  sourceRows: string[][],\n  columnIndex: number,\n  descending: boolean,\n): number[] {\n  return sortedRowOrderFromKeys(\n    sourceRows.map((row, rowIndex) =>\n      csvSortKey(row[columnIndex] ?? \"\", rowIndex),\n    ),\n    descending,\n  );\n}\n\nexport function csvSortKey(value: string, rowIndex: number): CsvSortKey {\n  if (isNumericCell(value)) {\n    return {\n      kind: \"number\",\n      value: Number(value),\n      rowIndex,\n    };\n  }\n  return {\n    kind: \"text\",\n    value,\n    rowIndex,\n  };\n}\n\nexport function sortedRowOrderFromKeys(\n  keys: CsvSortKey[],\n  descending: boolean,\n): number[] {\n  const orderedKeys = keys.slice();\n  const direction = descending ? -1 : 1;\n  orderedKeys.sort((a, b) => {\n    const cmp = compareCsvSortKeys(a, b);\n    return cmp !== 0 ? direction * cmp : a.rowIndex - b.rowIndex;\n  });\n  return orderedKeys.map((key) => key.rowIndex);\n}\n\nfunction compareCsvSortKeys(a: CsvSortKey, b: CsvSortKey): number {\n  if (a.kind === \"number\" && b.kind === \"number\") {\n    const diff = a.value - b.value;\n    return diff < 0 ? -1 : diff > 0 ? 1 : 0;\n  }\n  if (a.kind === \"number\") return -1;\n  if (b.kind === \"number\") return 1;\n  return a.value < b.value ? -1 : a.value > b.value ? 1 : 0;\n}\n",
      "type": "registry:ui",
      "target": "@ui/csv-viewer-sort.ts"
    },
    {
      "path": "registry/new-york-v4/ui/csv-viewer-sort-worker.ts",
      "content": "import { sortedRowOrder } from \"./csv-viewer-sort\";\n\nexport class CsvSortWorkerUnavailableError extends Error {\n  constructor(message = \"CSV sort worker unavailable\", options?: ErrorOptions) {\n    super(message, options);\n    this.name = \"CsvSortWorkerUnavailableError\";\n  }\n}\n\nexport interface CsvSortWorkerRequest {\n  sortRequestId: string;\n  sourceRows: string[][];\n  columnIndex: number;\n  descending: boolean;\n}\n\nexport type CsvSortWorkerResponse =\n  | { type: \"rowOrder\"; sortRequestId: string; rowOrder: number[] }\n  | { type: \"error\"; sortRequestId: string; message: string };\n\nexport function createCsvSortWorker(): Worker {\n  return new Worker(new URL(\"./csv-viewer-sort.worker.ts\", import.meta.url), {\n    type: \"module\",\n  });\n}\n\nexport function sortCsvRowsInWorker({\n  sourceRows,\n  columnIndex,\n  descending,\n  signal,\n}: {\n  sourceRows: string[][];\n  columnIndex: number;\n  descending: boolean;\n  signal: AbortSignal;\n}): Promise<number[]> {\n  return new Promise((resolve, reject) => {\n    let worker: Worker;\n    try {\n      worker = createCsvSortWorker();\n    } catch (error) {\n      reject(\n        new CsvSortWorkerUnavailableError(\"CSV sort worker unavailable\", {\n          cause: error,\n        }),\n      );\n      return;\n    }\n\n    const sortRequestId = crypto.randomUUID();\n    const cleanup = () => {\n      signal.removeEventListener(\"abort\", abort);\n      worker.terminate();\n    };\n    const abort = () => {\n      cleanup();\n      reject(new DOMException(\"Aborted\", \"AbortError\"));\n    };\n\n    signal.addEventListener(\"abort\", abort, { once: true });\n    worker.onerror = (event) => {\n      cleanup();\n      reject(new Error(event?.message || \"CSV sort worker failed.\"));\n    };\n    worker.onmessage = (event: MessageEvent<CsvSortWorkerResponse>) => {\n      const message = event.data;\n      if (message.sortRequestId !== sortRequestId) return;\n      if (message.type === \"rowOrder\") {\n        cleanup();\n        resolve(message.rowOrder);\n      } else {\n        cleanup();\n        reject(new Error(message.message || \"CSV sort failed.\"));\n      }\n    };\n\n    worker.postMessage({\n      sortRequestId,\n      sourceRows,\n      columnIndex,\n      descending,\n    } satisfies CsvSortWorkerRequest);\n  });\n}\n\nexport function sortCsvRowsOnMainThread({\n  sourceRows,\n  columnIndex,\n  descending,\n}: {\n  sourceRows: string[][];\n  columnIndex: number;\n  descending: boolean;\n}): number[] {\n  return sortedRowOrder(sourceRows, columnIndex, descending);\n}\n",
      "type": "registry:ui",
      "target": "@ui/csv-viewer-sort-worker.ts"
    },
    {
      "path": "registry/new-york-v4/ui/csv-viewer-sort.worker.ts",
      "content": "import type {\n  CsvSortWorkerRequest,\n  CsvSortWorkerResponse,\n} from \"./csv-viewer-sort-worker\";\nimport { sortedRowOrder } from \"./csv-viewer-sort\";\n\nfunction post(message: CsvSortWorkerResponse) {\n  self.postMessage(message);\n}\n\nself.onmessage = (event: MessageEvent<CsvSortWorkerRequest>) => {\n  const { sortRequestId, sourceRows, columnIndex, descending } = event.data;\n  try {\n    post({\n      type: \"rowOrder\",\n      sortRequestId,\n      rowOrder: sortedRowOrder(sourceRows, columnIndex, descending),\n    });\n  } catch (error) {\n    post({\n      type: \"error\",\n      sortRequestId,\n      message: error instanceof Error ? error.message : String(error),\n    });\n  }\n};\n",
      "type": "registry:ui",
      "target": "@ui/csv-viewer-sort.worker.ts"
    },
    {
      "path": "registry/new-york-v4/ui/fixed-grid-virtualization.ts",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\nimport { useKeyedLayoutEffect } from \"@/hooks/use-keyed-layout-effect\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nconst MINIMUM_ROW_WINDOW = 32;\nconst INITIAL_COLUMN_WINDOW = 8;\nconst MAX_VIRTUAL_ITEMS = 10_000;\nconst MAX_EAGER_COLUMN_ITEMS = 10_000;\n\nexport interface FixedGridColumnItem {\n  index: number;\n  widthPx: number;\n}\n\nexport interface FixedGridVirtualItem {\n  index: number;\n  start: number;\n  size: number;\n  end: number;\n}\n\nexport interface FixedGridRowPoolSlot {\n  slotIndex: number;\n  virtualRow: FixedGridVirtualItem | null;\n  isHidden: boolean;\n}\n\nexport interface FixedGridVirtualItemWindow {\n  end: number;\n  items: FixedGridVirtualItem[];\n  size: number;\n  start: number;\n}\n\nexport interface FixedGridScrollTarget {\n  rowIndex: number;\n  columnIndex: number;\n  align?: \"start\" | \"center\" | \"end\" | \"auto\";\n  behavior?: ScrollBehavior;\n}\n\nexport interface FixedRowScrollTarget {\n  rowIndex: number;\n  align?: \"start\" | \"center\" | \"end\" | \"auto\";\n  behavior?: ScrollBehavior;\n}\n\nexport type FixedGridJumpViewportResult = \"handled\" | \"pass\";\n\nexport interface FixedGridRowScrollStrategy {\n  settleAfterMs?: number;\n  handleViewport: (viewport: FixedGridViewport) => FixedGridJumpViewportResult;\n}\n\nexport function useFixedRowPool({\n  minimumPoolSize = 0,\n  rowCount,\n  virtualRows,\n}: {\n  minimumPoolSize?: number;\n  rowCount: number;\n  virtualRows: FixedGridVirtualItem[];\n}): FixedGridRowPoolSlot[] {\n  const poolSizeRef = React.useRef(0);\n  const safeRowCount = fixedItemCount(rowCount);\n  const safeMinimumPoolSize = fixedItemCount(minimumPoolSize);\n  const nextPoolSize = Math.min(\n    safeRowCount,\n    Math.max(poolSizeRef.current, virtualRows.length, safeMinimumPoolSize),\n  );\n  poolSizeRef.current = nextPoolSize;\n\n  return React.useMemo(\n    () =>\n      Array.from({ length: nextPoolSize }, (_, slotIndex) => {\n        const virtualRow = virtualRows[slotIndex] ?? null;\n        return {\n          slotIndex,\n          virtualRow,\n          isHidden: !virtualRow,\n        };\n      }),\n    [nextPoolSize, virtualRows],\n  );\n}\n\nexport function useFixedGridVirtualization({\n  rowCount,\n  columnCount,\n  rowSize,\n  columnSize,\n  rowOverscan,\n  columnOverscan,\n  jumpRowOverscan = rowOverscan,\n  jumpColumnOverscan = columnOverscan,\n  minimumRenderedRows = MINIMUM_ROW_WINDOW,\n  rowScrollStrategy,\n  scrollRef,\n  scrollElement,\n  virtualizeColumns = true,\n}: {\n  rowCount: number;\n  columnCount: number;\n  rowSize: number;\n  columnSize: number;\n  rowOverscan: number;\n  columnOverscan: number;\n  jumpRowOverscan?: number;\n  jumpColumnOverscan?: number;\n  minimumRenderedRows?: number;\n  rowScrollStrategy?: FixedGridRowScrollStrategy;\n  scrollRef: React.RefObject<HTMLElement | null>;\n  scrollElement?: HTMLElement | null;\n  virtualizeColumns?: boolean;\n}) {\n  const resolvedScrollElement = useResolvedScrollElement({\n    scrollRef,\n    scrollElement,\n  });\n  const viewport = useFixedGridViewport(\n    resolvedScrollElement,\n    rowScrollStrategy,\n    {\n      rowCount,\n      columnCount,\n      rowSize,\n      columnSize,\n    },\n  );\n\n  const totalRowSize = fixedTotalSize(rowCount, rowSize);\n  const totalColumnSize = fixedTotalSize(columnCount, columnSize);\n  const activeRowOverscan = viewport.isJumpingRows\n    ? jumpRowOverscan\n    : rowOverscan;\n  const activeColumnOverscan =\n    viewport.isJumpingColumns || viewport.isJumpingRows\n      ? jumpColumnOverscan\n      : columnOverscan;\n\n  const virtualRows = React.useMemo(\n    () =>\n      fixedVirtualItems({\n        count: rowCount,\n        size: rowSize,\n        scrollOffset: viewport.scrollTop,\n        viewportSize: viewport.clientHeight,\n        overscan: activeRowOverscan,\n        minimumVisibleCount: minimumRenderedRows,\n      }),\n    [\n      rowCount,\n      rowSize,\n      viewport.scrollTop,\n      viewport.clientHeight,\n      activeRowOverscan,\n      minimumRenderedRows,\n    ],\n  );\n  const virtualRowWindow = React.useMemo(\n    () => fixedVirtualItemWindow(virtualRows),\n    [virtualRows],\n  );\n\n  const columnWindow = React.useMemo<{\n    columnItems: FixedGridColumnItem[];\n    leftPad: number;\n    rightPad: number;\n  }>(() => {\n    if (!virtualizeColumns) {\n      const safeColumnCount = fixedItemCount(columnCount);\n      const safeColumnSize = fixedItemSize(columnSize);\n      if (safeColumnCount > MAX_EAGER_COLUMN_ITEMS) {\n        return {\n          columnItems: [],\n          leftPad: 0,\n          rightPad: 0,\n        };\n      }\n      return {\n        columnItems: Array.from({ length: safeColumnCount }, (_, index) => ({\n          index,\n          widthPx: safeColumnSize,\n        })),\n        leftPad: 0,\n        rightPad: 0,\n      };\n    }\n\n    const virtualColumns = fixedVirtualItems({\n      count: columnCount,\n      size: columnSize,\n      scrollOffset: viewport.scrollLeft,\n      viewportSize: viewport.clientWidth,\n      overscan: activeColumnOverscan,\n      minimumVisibleCount: INITIAL_COLUMN_WINDOW,\n    });\n\n    return {\n      columnItems: virtualColumns.map((item) => ({\n        index: item.index,\n        widthPx: item.size,\n      })),\n      leftPad: virtualColumns.length ? virtualColumns[0].start : 0,\n      rightPad: virtualColumns.length\n        ? totalColumnSize - virtualColumns[virtualColumns.length - 1].end\n        : 0,\n    };\n  }, [\n    virtualizeColumns,\n    columnCount,\n    columnSize,\n    viewport.scrollLeft,\n    viewport.clientWidth,\n    activeColumnOverscan,\n    totalColumnSize,\n  ]);\n\n  const scrollToCell = React.useCallback(\n    ({\n      rowIndex,\n      columnIndex,\n      align = \"center\",\n      behavior = \"smooth\",\n    }: FixedGridScrollTarget) => {\n      const scrollElement = scrollRef.current;\n      if (!scrollElement) return;\n      const top = fixedScrollOffset({\n        index: rowIndex,\n        itemSize: rowSize,\n        viewportSize: scrollElement.clientHeight,\n        align,\n      });\n      const left = fixedScrollOffset({\n        index: columnIndex,\n        itemSize: columnSize,\n        viewportSize: scrollElement.clientWidth,\n        align,\n      });\n      if (typeof scrollElement.scrollTo === \"function\") {\n        scrollElement.scrollTo({ top, left, behavior });\n      } else {\n        scrollElement.scrollTop = top;\n        scrollElement.scrollLeft = left;\n      }\n    },\n    [columnSize, rowSize, scrollRef],\n  );\n\n  return {\n    virtualRows,\n    virtualRowWindow,\n    totalRowSize,\n    totalColumnSize,\n    scrollToCell,\n    isJumpingRows: viewport.isJumpingRows,\n    isJumpingColumns: viewport.isJumpingColumns,\n    viewportClientHeight: viewport.clientHeight,\n    ...columnWindow,\n  };\n}\n\nexport function useFixedRowVirtualization({\n  rowCount,\n  rowSize,\n  rowOverscan,\n  jumpRowOverscan = rowOverscan,\n  initialViewportHeight = 0,\n  scrollRef,\n  scrollElement,\n}: {\n  rowCount: number;\n  rowSize: number;\n  rowOverscan: number;\n  jumpRowOverscan?: number;\n  initialViewportHeight?: number;\n  scrollRef: React.RefObject<HTMLElement | null>;\n  scrollElement?: HTMLElement | null;\n}) {\n  const resolvedScrollElement = useResolvedScrollElement({\n    scrollRef,\n    scrollElement,\n  });\n  const [range, setRange] = React.useState(() =>\n    initialViewportHeight > 0\n      ? fixedRowRange({\n          rowCount,\n          rowSize,\n          scrollTop: 0,\n          viewportHeight: initialViewportHeight,\n          rowOverscan,\n        })\n      : { start: 0, end: 0 },\n  );\n  const rangeRef = React.useRef(range);\n  const rafRef = React.useRef(0);\n  const totalRowSize = fixedTotalSize(rowCount, rowSize);\n  const [viewportClientHeight, setViewportClientHeight] = React.useState(() =>\n    fixedViewportMetric(initialViewportHeight),\n  );\n\n  const setMeasuredRange = React.useCallback((next: typeof range) => {\n    const current = rangeRef.current;\n    if (current.start === next.start && current.end === next.end) return;\n    rangeRef.current = next;\n    setRange(next);\n  }, []);\n  const setMeasuredViewportHeight = React.useCallback((next: number) => {\n    setViewportClientHeight((current) => (current === next ? current : next));\n  }, []);\n\n  const measure = React.useCallback(() => {\n    const scrollElement = resolvedScrollElement;\n    const safeRowCount = fixedItemCount(rowCount);\n    const safeRowSize = fixedItemSize(rowSize);\n    if (!scrollElement || safeRowCount <= 0 || safeRowSize <= 0) {\n      setMeasuredRange({ start: 0, end: 0 });\n      return;\n    }\n\n    const scrollTop =\n      Number.isFinite(scrollElement.scrollTop) && scrollElement.scrollTop > 0\n        ? scrollElement.scrollTop\n        : 0;\n    const viewportHeight =\n      Number.isFinite(scrollElement.clientHeight) &&\n      scrollElement.clientHeight > 0\n        ? scrollElement.clientHeight\n        : fixedViewportMetric(initialViewportHeight);\n    setMeasuredViewportHeight(viewportHeight);\n    const firstVisibleRow = clamp(\n      Math.floor(scrollTop / safeRowSize),\n      0,\n      safeRowCount - 1,\n    );\n    const visibleRowCount = Math.ceil(viewportHeight / safeRowSize);\n    const previous = rangeRef.current;\n    const isJumping =\n      Math.abs(firstVisibleRow - previous.start) > visibleRowCount * 0.45;\n    const activeOverscan = fixedOverscan(\n      isJumping ? jumpRowOverscan : rowOverscan,\n    );\n    const uncappedStart = Math.max(0, firstVisibleRow - activeOverscan);\n    const uncappedEnd = Math.min(\n      safeRowCount,\n      firstVisibleRow + visibleRowCount + activeOverscan,\n    );\n    const { start, end } = capVirtualRange({\n      uncappedStart,\n      uncappedEnd,\n      visibleStart: firstVisibleRow,\n      visibleEnd: Math.min(safeRowCount, firstVisibleRow + visibleRowCount),\n      maxItems: MAX_VIRTUAL_ITEMS,\n    });\n\n    if (previous.end > safeRowCount || previous.start >= safeRowCount) {\n      setMeasuredRange({ start, end });\n      return;\n    }\n\n    const bufferRows = Math.max(1, Math.floor(activeOverscan / 2));\n    const visibleStart = firstVisibleRow;\n    const visibleEnd = Math.min(\n      safeRowCount,\n      firstVisibleRow + visibleRowCount,\n    );\n    const hasBeforeBuffer =\n      previous.start === 0 || visibleStart >= previous.start + bufferRows;\n    const hasAfterBuffer =\n      previous.end === safeRowCount || visibleEnd <= previous.end - bufferRows;\n\n    if (hasBeforeBuffer && hasAfterBuffer) return;\n    setMeasuredRange({ start, end });\n  }, [\n    jumpRowOverscan,\n    initialViewportHeight,\n    rowCount,\n    rowOverscan,\n    rowSize,\n    resolvedScrollElement,\n    setMeasuredRange,\n    setMeasuredViewportHeight,\n  ]);\n\n  useKeyedLayoutEffect(joinEffectKey([measure]), () => {\n    measure();\n  });\n\n  useKeyedMountEffect(joinEffectKey([resolvedScrollElement, measure]), () => {\n    const scrollElement = resolvedScrollElement;\n    if (!scrollElement) return;\n\n    const scheduleMeasure = () => {\n      if (rafRef.current) return;\n      let didRun = false;\n      const frame = requestAnimationFrame(() => {\n        didRun = true;\n        rafRef.current = 0;\n        measure();\n      });\n      rafRef.current = didRun ? 0 : frame;\n    };\n\n    scrollElement.addEventListener(\"scroll\", scheduleMeasure, {\n      passive: true,\n    });\n    const observer =\n      typeof ResizeObserver !== \"undefined\"\n        ? new ResizeObserver(scheduleMeasure)\n        : null;\n    observer?.observe(scrollElement);\n    return () => {\n      if (rafRef.current) cancelAnimationFrame(rafRef.current);\n      scrollElement.removeEventListener(\"scroll\", scheduleMeasure);\n      observer?.disconnect();\n    };\n  });\n\n  const virtualRows = React.useMemo(\n    () =>\n      Array.from({ length: range.end - range.start }, (_, offset) => {\n        const index = range.start + offset;\n        const size = fixedItemSize(rowSize);\n        const start = index * size;\n        return {\n          index,\n          start,\n          size,\n          end: start + size,\n        };\n      }),\n    [range, rowSize],\n  );\n  const virtualRowWindow = React.useMemo(\n    () => fixedVirtualItemWindow(virtualRows),\n    [virtualRows],\n  );\n\n  const scrollToRow = React.useCallback(\n    ({\n      rowIndex,\n      align = \"center\",\n      behavior = \"smooth\",\n    }: FixedRowScrollTarget) => {\n      const scrollElement = scrollRef.current;\n      if (!scrollElement) return;\n      const top = fixedScrollOffset({\n        index: rowIndex,\n        itemSize: rowSize,\n        viewportSize: scrollElement.clientHeight,\n        align,\n      });\n      if (typeof scrollElement.scrollTo === \"function\") {\n        scrollElement.scrollTo({ top, behavior });\n      } else {\n        scrollElement.scrollTop = top;\n      }\n    },\n    [rowSize, scrollRef],\n  );\n\n  return {\n    virtualRows,\n    virtualRowWindow,\n    totalRowSize,\n    viewportClientHeight,\n    scrollToRow,\n  };\n}\n\nfunction fixedRowRange({\n  rowCount,\n  rowSize,\n  scrollTop,\n  viewportHeight,\n  rowOverscan,\n}: {\n  rowCount: number;\n  rowSize: number;\n  scrollTop: number;\n  viewportHeight: number;\n  rowOverscan: number;\n}) {\n  const safeRowCount = fixedItemCount(rowCount);\n  const safeRowSize = fixedItemSize(rowSize);\n  if (safeRowCount <= 0 || safeRowSize <= 0) return { start: 0, end: 0 };\n\n  const safeScrollTop =\n    Number.isFinite(scrollTop) && scrollTop > 0 ? scrollTop : 0;\n  const safeViewportHeight = fixedViewportMetric(viewportHeight);\n  const firstVisibleRow = clamp(\n    Math.floor(safeScrollTop / safeRowSize),\n    0,\n    safeRowCount - 1,\n  );\n  const visibleRowCount = Math.ceil(safeViewportHeight / safeRowSize);\n  const activeOverscan = fixedOverscan(rowOverscan);\n  const uncappedStart = Math.max(0, firstVisibleRow - activeOverscan);\n  const uncappedEnd = Math.min(\n    safeRowCount,\n    firstVisibleRow + visibleRowCount + activeOverscan,\n  );\n  return capVirtualRange({\n    uncappedStart,\n    uncappedEnd,\n    visibleStart: firstVisibleRow,\n    visibleEnd: Math.min(safeRowCount, firstVisibleRow + visibleRowCount),\n    maxItems: MAX_VIRTUAL_ITEMS,\n  });\n}\n\nexport interface FixedGridViewport {\n  scrollTop: number;\n  scrollLeft: number;\n  clientHeight: number;\n  clientWidth: number;\n  isJumpingRows: boolean;\n  isJumpingColumns: boolean;\n}\n\ninterface FixedGridLayoutMetrics {\n  rowCount: number;\n  columnCount: number;\n  rowSize: number;\n  columnSize: number;\n}\n\ninterface FixedGridReadingAnchor {\n  rowIndex: number;\n  columnIndex: number;\n  rowOffsetPx: number;\n  columnOffsetPx: number;\n}\n\ninterface FixedVirtualWindow {\n  count: number;\n  size: number;\n  scrollOffset: number;\n  viewportSize: number;\n  overscan: number;\n  minimumVisibleCount?: number;\n}\n\nconst emptyFixedGridViewport: FixedGridViewport = {\n  scrollTop: 0,\n  scrollLeft: 0,\n  clientHeight: 0,\n  clientWidth: 0,\n  isJumpingRows: false,\n  isJumpingColumns: false,\n};\n\nfunction useResolvedScrollElement({\n  scrollRef,\n  scrollElement,\n}: {\n  scrollRef: React.RefObject<HTMLElement | null>;\n  scrollElement?: HTMLElement | null;\n}) {\n  const [resolvedScrollElement, setResolvedScrollElement] =\n    React.useState<HTMLElement | null>(scrollElement ?? scrollRef.current);\n\n  useKeyedLayoutEffect(\n    joinEffectKey([\n      scrollRef,\n      scrollRef.current,\n      scrollElement,\n      resolvedScrollElement,\n    ]),\n    () => {\n      const nextScrollElement = scrollElement ?? scrollRef.current;\n      if (resolvedScrollElement !== nextScrollElement) {\n        setResolvedScrollElement(nextScrollElement);\n      }\n    },\n  );\n\n  return resolvedScrollElement;\n}\n\nfunction useFixedGridViewport(\n  scrollElement: HTMLElement | null | undefined,\n  rowScrollStrategy?: FixedGridRowScrollStrategy,\n  layoutMetrics?: FixedGridLayoutMetrics,\n) {\n  const [viewport, setViewport] = React.useState<FixedGridViewport>(\n    emptyFixedGridViewport,\n  );\n  const committedLayoutMetricsRef = React.useRef<FixedGridLayoutMetrics | null>(\n    layoutMetrics ?? null,\n  );\n\n  useKeyedLayoutEffect(\n    joinEffectKey([\n      scrollElement,\n      rowScrollStrategy,\n      layoutMetrics?.rowCount,\n      layoutMetrics?.columnCount,\n      layoutMetrics?.rowSize,\n      layoutMetrics?.columnSize,\n    ]),\n    () => {\n      const previousLayoutMetrics = committedLayoutMetricsRef.current;\n      committedLayoutMetricsRef.current = layoutMetrics ?? null;\n\n      if (!scrollElement) {\n        setViewport((current) =>\n          fixedGridViewportEqual(current, emptyFixedGridViewport)\n            ? current\n            : emptyFixedGridViewport,\n        );\n        return;\n      }\n\n      let frame = 0;\n      let settleTimeout = 0;\n      let lastScrollTop = scrollElement.scrollTop;\n      let lastScrollLeft = scrollElement.scrollLeft;\n\n      if (\n        previousLayoutMetrics &&\n        layoutMetrics &&\n        didFixedGridItemSizeChange(previousLayoutMetrics, layoutMetrics)\n      ) {\n        const anchor = captureFixedGridReadingAnchor({\n          layoutMetrics: previousLayoutMetrics,\n          scrollElement,\n        });\n        restoreFixedGridReadingAnchor({\n          anchor,\n          layoutMetrics,\n          scrollElement,\n        });\n        lastScrollTop = scrollElement.scrollTop;\n        lastScrollLeft = scrollElement.scrollLeft;\n      }\n\n      const commitViewport = (next: FixedGridViewport) => {\n        setViewport((current) => {\n          return fixedGridViewportEqual(current, next) ? current : next;\n        });\n      };\n\n      const commitSettledViewport = () => {\n        commitViewport({\n          scrollTop: fixedViewportMetric(scrollElement.scrollTop),\n          scrollLeft: fixedViewportMetric(scrollElement.scrollLeft),\n          clientHeight: fixedViewportMetric(scrollElement.clientHeight),\n          clientWidth: fixedViewportMetric(scrollElement.clientWidth),\n          isJumpingRows: false,\n          isJumpingColumns: false,\n        });\n      };\n\n      const scheduleSettledViewport = () => {\n        if (settleTimeout) window.clearTimeout(settleTimeout);\n        settleTimeout = window.setTimeout(() => {\n          settleTimeout = 0;\n          // Scrolling has quiesced: re-read the live scroll metrics so the\n          // canonical React window matches where the grid actually came to rest,\n          // then clear jump flags so settled windows use the full overscan.\n          commitSettledViewport();\n        }, rowScrollStrategy?.settleAfterMs ?? 80);\n      };\n\n      const readViewport = () => {\n        frame = 0;\n        const scrollTop = fixedViewportMetric(scrollElement.scrollTop);\n        const scrollLeft = fixedViewportMetric(scrollElement.scrollLeft);\n        const clientHeight = fixedViewportMetric(scrollElement.clientHeight);\n        const clientWidth = fixedViewportMetric(scrollElement.clientWidth);\n        const rowDelta = Math.abs(scrollTop - lastScrollTop);\n        const columnDelta = Math.abs(scrollLeft - lastScrollLeft);\n        lastScrollTop = scrollTop;\n        lastScrollLeft = scrollLeft;\n\n        const next: FixedGridViewport = {\n          scrollTop,\n          scrollLeft,\n          clientHeight,\n          clientWidth,\n          isJumpingRows: rowDelta > clientHeight * 0.45,\n          isJumpingColumns: columnDelta > clientWidth * 0.45,\n        };\n\n        if (\n          rowDelta > 0 &&\n          rowScrollStrategy?.handleViewport(next) === \"handled\"\n        ) {\n          scheduleSettledViewport();\n          return;\n        }\n\n        commitViewport(next);\n        if (next.isJumpingRows || next.isJumpingColumns) {\n          scheduleSettledViewport();\n          return;\n        }\n        if (settleTimeout) {\n          window.clearTimeout(settleTimeout);\n          settleTimeout = 0;\n        }\n      };\n\n      const scheduleRead = () => {\n        if (frame) return;\n        let didRun = false;\n        const nextFrame = requestAnimationFrame(() => {\n          didRun = true;\n          readViewport();\n        });\n        frame = didRun ? 0 : nextFrame;\n      };\n\n      readViewport();\n      scrollElement.addEventListener(\"scroll\", scheduleRead, { passive: true });\n      const observer =\n        typeof ResizeObserver !== \"undefined\"\n          ? new ResizeObserver(scheduleRead)\n          : null;\n      observer?.observe(scrollElement);\n\n      return () => {\n        if (frame) cancelAnimationFrame(frame);\n        if (settleTimeout) window.clearTimeout(settleTimeout);\n        scrollElement.removeEventListener(\"scroll\", scheduleRead);\n        observer?.disconnect();\n      };\n    },\n  );\n\n  return viewport;\n}\n\nfunction didFixedGridItemSizeChange(\n  previous: FixedGridLayoutMetrics,\n  next: FixedGridLayoutMetrics,\n) {\n  return (\n    previous.rowSize !== next.rowSize || previous.columnSize !== next.columnSize\n  );\n}\n\nfunction captureFixedGridReadingAnchor({\n  layoutMetrics,\n  scrollElement,\n}: {\n  layoutMetrics: FixedGridLayoutMetrics;\n  scrollElement: HTMLElement;\n}): FixedGridReadingAnchor {\n  const rowCount = fixedItemCount(layoutMetrics.rowCount);\n  const columnCount = fixedItemCount(layoutMetrics.columnCount);\n  const rowSize = fixedItemSize(layoutMetrics.rowSize);\n  const columnSize = fixedItemSize(layoutMetrics.columnSize);\n  const scrollTop = fixedViewportMetric(scrollElement.scrollTop);\n  const scrollLeft = fixedViewportMetric(scrollElement.scrollLeft);\n  const rowIndex =\n    rowCount > 0 && rowSize > 0\n      ? clamp(Math.floor(scrollTop / rowSize), 0, rowCount - 1)\n      : 0;\n  const columnIndex =\n    columnCount > 0 && columnSize > 0\n      ? clamp(Math.floor(scrollLeft / columnSize), 0, columnCount - 1)\n      : 0;\n\n  return {\n    rowIndex,\n    columnIndex,\n    rowOffsetPx: Math.max(0, scrollTop - rowIndex * rowSize),\n    columnOffsetPx: Math.max(0, scrollLeft - columnIndex * columnSize),\n  };\n}\n\nfunction restoreFixedGridReadingAnchor({\n  anchor,\n  layoutMetrics,\n  scrollElement,\n}: {\n  anchor: FixedGridReadingAnchor;\n  layoutMetrics: FixedGridLayoutMetrics;\n  scrollElement: HTMLElement;\n}) {\n  const rowCount = fixedItemCount(layoutMetrics.rowCount);\n  const columnCount = fixedItemCount(layoutMetrics.columnCount);\n  const rowSize = fixedItemSize(layoutMetrics.rowSize);\n  const columnSize = fixedItemSize(layoutMetrics.columnSize);\n  const rowIndex = rowCount > 0 ? clamp(anchor.rowIndex, 0, rowCount - 1) : 0;\n  const columnIndex =\n    columnCount > 0 ? clamp(anchor.columnIndex, 0, columnCount - 1) : 0;\n\n  scrollElement.scrollTop =\n    rowIndex * rowSize + Math.min(anchor.rowOffsetPx, Math.max(0, rowSize - 1));\n  scrollElement.scrollLeft =\n    columnIndex * columnSize +\n    Math.min(anchor.columnOffsetPx, Math.max(0, columnSize - 1));\n}\n\nexport function fixedVirtualItems({\n  count,\n  size,\n  scrollOffset,\n  viewportSize,\n  overscan,\n  minimumVisibleCount = 1,\n}: FixedVirtualWindow): FixedGridVirtualItem[] {\n  if (!Number.isFinite(count) || !Number.isFinite(size)) return [];\n  const itemCount = Math.floor(count);\n  if (itemCount <= 0 || size <= 0) return [];\n  const safeScrollOffset =\n    Number.isFinite(scrollOffset) && scrollOffset > 0 ? scrollOffset : 0;\n  const safeViewportSize = Number.isFinite(viewportSize) ? viewportSize : 0;\n  const safeOverscan =\n    Number.isFinite(overscan) && overscan > 0 ? Math.floor(overscan) : 0;\n  const safeMinimumVisibleCount =\n    Number.isFinite(minimumVisibleCount) && minimumVisibleCount > 0\n      ? Math.ceil(minimumVisibleCount)\n      : 1;\n  const effectiveViewportSize = Math.max(\n    safeViewportSize,\n    size * safeMinimumVisibleCount,\n  );\n  const visibleStart = clamp(\n    Math.floor(safeScrollOffset / size),\n    0,\n    itemCount - 1,\n  );\n  const visibleEnd = clamp(\n    Math.ceil((safeScrollOffset + effectiveViewportSize) / size),\n    visibleStart,\n    itemCount - 1,\n  );\n  const uncappedStart = Math.max(0, visibleStart - safeOverscan);\n  const uncappedEndInclusive = Math.min(\n    itemCount - 1,\n    visibleEnd + safeOverscan,\n  );\n  const { start, end } = capVirtualRange({\n    uncappedStart,\n    uncappedEnd: uncappedEndInclusive + 1,\n    visibleStart,\n    visibleEnd: visibleEnd + 1,\n    maxItems: MAX_VIRTUAL_ITEMS,\n  });\n  return Array.from({ length: end - start }, (_, offset) => {\n    const index = start + offset;\n    const itemStart = index * size;\n    return {\n      index,\n      start: itemStart,\n      size,\n      end: itemStart + size,\n    };\n  });\n}\n\nexport function fixedVirtualItemWindow(\n  items: readonly FixedGridVirtualItem[],\n): FixedGridVirtualItemWindow {\n  const start = items[0]?.start ?? 0;\n  const end = items.length ? items[items.length - 1]!.end : start;\n\n  return {\n    end,\n    items: items.map((item) => ({\n      ...item,\n      start: item.start - start,\n      end: item.end - start,\n    })),\n    size: Math.max(0, end - start),\n    start,\n  };\n}\n\nfunction capVirtualRange({\n  uncappedStart,\n  uncappedEnd,\n  visibleStart,\n  visibleEnd,\n  maxItems,\n}: {\n  uncappedStart: number;\n  uncappedEnd: number;\n  visibleStart: number;\n  visibleEnd: number;\n  maxItems: number;\n}) {\n  const length = uncappedEnd - uncappedStart;\n  if (length <= maxItems) return { start: uncappedStart, end: uncappedEnd };\n\n  const visibleLength = Math.max(0, visibleEnd - visibleStart);\n  if (visibleLength >= maxItems) {\n    return {\n      start: visibleStart,\n      end: visibleStart + maxItems,\n    };\n  }\n\n  const remaining = maxItems - visibleLength;\n  const before = Math.min(\n    visibleStart - uncappedStart,\n    Math.floor(remaining / 2),\n  );\n  let start = visibleStart - before;\n  let end = start + maxItems;\n\n  if (end > uncappedEnd) {\n    end = uncappedEnd;\n    start = Math.max(uncappedStart, end - maxItems);\n  }\n\n  return { start, end };\n}\n\nfunction clamp(value: number, min: number, max: number) {\n  return Math.min(max, Math.max(min, value));\n}\n\nfunction fixedTotalSize(count: number, size: number) {\n  if (\n    !Number.isFinite(count) ||\n    !Number.isFinite(size) ||\n    count <= 0 ||\n    size <= 0\n  ) {\n    return 0;\n  }\n  const totalSize = Math.floor(count) * size;\n  return Number.isFinite(totalSize) ? totalSize : 0;\n}\n\nfunction fixedItemCount(count: number) {\n  return Number.isFinite(count) && count > 0 ? Math.floor(count) : 0;\n}\n\nfunction fixedItemSize(size: number) {\n  return Number.isFinite(size) && size > 0 ? size : 0;\n}\n\nfunction fixedOverscan(overscan: number) {\n  return Number.isFinite(overscan) && overscan > 0 ? Math.floor(overscan) : 0;\n}\n\nfunction fixedViewportMetric(value: number) {\n  return Number.isFinite(value) && value > 0 ? value : 0;\n}\n\nexport function fixedScrollOffset({\n  index,\n  itemSize,\n  viewportSize,\n  align,\n}: {\n  index: number;\n  itemSize: number;\n  viewportSize: number;\n  align: NonNullable<FixedGridScrollTarget[\"align\"]>;\n}) {\n  if (\n    !Number.isSafeInteger(index) ||\n    !Number.isFinite(itemSize) ||\n    !Number.isFinite(viewportSize) ||\n    index < 0 ||\n    itemSize <= 0 ||\n    viewportSize < 0\n  ) {\n    return 0;\n  }\n  const start = index * itemSize;\n  if (align === \"end\") return Math.max(0, start - viewportSize + itemSize);\n  if (align === \"center\") {\n    return Math.max(0, start - viewportSize / 2 + itemSize / 2);\n  }\n  return Math.max(0, start);\n}\n\nfunction fixedGridViewportEqual(\n  left: FixedGridViewport,\n  right: FixedGridViewport,\n) {\n  return (\n    left.scrollTop === right.scrollTop &&\n    left.scrollLeft === right.scrollLeft &&\n    left.clientHeight === right.clientHeight &&\n    left.clientWidth === right.clientWidth &&\n    left.isJumpingRows === right.isJumpingRows &&\n    left.isJumpingColumns === right.isJumpingColumns\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/fixed-grid-virtualization.ts"
    },
    {
      "path": "registry/new-york-v4/ui/fixed-grid-row-style.ts",
      "content": "import type * as React from \"react\";\n\nexport function getFixedGridRowStyle({\n  gridTemplate,\n  rowHeight,\n  top,\n  contain = true,\n}: {\n  gridTemplate?: string;\n  rowHeight: number;\n  top: number;\n  contain?: boolean;\n}): React.CSSProperties {\n  const safeRowHeight =\n    Number.isFinite(rowHeight) && rowHeight > 0 ? rowHeight : 0;\n  const safeTop = Number.isFinite(top) && top > 0 ? top : 0;\n  const style: React.CSSProperties = {\n    height: safeRowHeight,\n    minHeight: safeRowHeight,\n    position: \"absolute\",\n    top: 0,\n    left: 0,\n    width: \"100%\",\n    transform: `translate3d(0, ${safeTop}px, 0)`,\n  };\n  if (gridTemplate?.trim()) style.gridTemplateColumns = gridTemplate;\n  if (contain) style.contain = \"layout paint style\";\n  return style;\n}\n",
      "type": "registry:ui",
      "target": "@ui/fixed-grid-row-style.ts"
    },
    {
      "path": "registry/new-york-v4/ui/fixed-grid-template.ts",
      "content": "export function buildVirtualGridTemplate({\n  leadingWidth,\n  leftPad,\n  columnWidths,\n  rightPad,\n}: {\n  leadingWidth: number;\n  leftPad: number;\n  columnWidths: readonly number[];\n  rightPad: number;\n}) {\n  const columns = columnWidths.map(formatTemplateWidth).join(\" \");\n  return [\n    formatTemplateWidth(leadingWidth),\n    formatTemplateWidth(leftPad),\n    columns,\n    formatTemplateWidth(rightPad),\n  ]\n    .filter(Boolean)\n    .join(\" \");\n}\n\nfunction formatTemplateWidth(width: number) {\n  return `${Number.isFinite(width) && width > 0 ? width : 0}px`;\n}\n",
      "type": "registry:ui",
      "target": "@ui/fixed-grid-template.ts"
    },
    {
      "path": "registry/new-york-v4/ui/fixed-grid-columns.ts",
      "content": "export interface FixedGridColumn<Metadata = unknown> {\n  key: string;\n  widthPx: number;\n  metadata?: Metadata;\n}\n\nexport function buildFixedGridColumns<Item, Metadata = unknown>({\n  items,\n  getKey,\n  getWidthPx,\n  getMetadata,\n}: {\n  items: readonly Item[];\n  getKey: (item: Item, index: number) => string;\n  getWidthPx: (item: Item, index: number) => number;\n  getMetadata?: (item: Item, index: number) => Metadata | undefined;\n}): FixedGridColumn<Metadata>[] {\n  return items.map((item, index) => {\n    const metadata = getMetadata?.(item, index);\n    const widthPx = normalizeFixedGridColumnWidth(getWidthPx(item, index));\n    return metadata === undefined\n      ? {\n          key: getKey(item, index),\n          widthPx,\n        }\n      : {\n          key: getKey(item, index),\n          widthPx,\n          metadata,\n        };\n  });\n}\n\nexport function fixedGridColumnWidths(\n  columns: readonly Pick<FixedGridColumn, \"widthPx\">[],\n) {\n  return columns.map((column) => normalizeFixedGridColumnWidth(column.widthPx));\n}\n\nfunction normalizeFixedGridColumnWidth(widthPx: number) {\n  return Number.isFinite(widthPx) && widthPx > 0 ? widthPx : 0;\n}\n",
      "type": "registry:ui",
      "target": "@ui/fixed-grid-columns.ts"
    },
    {
      "path": "registry/new-york-v4/ui/fixed-grid-layout.ts",
      "content": "import type * as React from \"react\";\n\nexport type CssLength = number | string;\ntype CssLengthProperty = \"height\" | \"marginTop\" | \"minWidth\" | \"width\";\n\nexport interface FixedGridInverseRowWindowGeometry {\n  size: number;\n  start: number;\n}\n\nexport function getFixedGridCanvasStyle({\n  width,\n  minWidth = \"100%\",\n  contain = false,\n}: {\n  width?: CssLength;\n  minWidth?: CssLength;\n  contain?: boolean;\n}): React.CSSProperties {\n  return {\n    position: \"relative\",\n    ...cssLengthProperty(\"width\", width),\n    ...cssLengthProperty(\"minWidth\", minWidth),\n    ...(contain ? { contain: \"layout paint style\" } : null),\n  };\n}\n\nexport function getFixedGridRowWindowStyle({\n  height,\n  minWidth,\n}: {\n  height: CssLength;\n  minWidth?: CssLength;\n}): React.CSSProperties {\n  return {\n    position: \"relative\",\n    ...cssLengthProperty(\"height\", height),\n    ...cssLengthProperty(\"minWidth\", minWidth),\n  };\n}\n\nexport function getFixedGridInverseRowOffsetStyle({\n  height,\n  minWidth,\n}: {\n  height: CssLength;\n  minWidth?: CssLength;\n}): React.CSSProperties {\n  return {\n    ...cssLengthProperty(\"height\", height),\n    ...cssLengthProperty(\"minWidth\", minWidth),\n  };\n}\n\nexport function getFixedGridInverseStickyRowWindowStyle({\n  height,\n  minWidth,\n  viewportHeight,\n}: {\n  height: number;\n  minWidth?: CssLength;\n  viewportHeight: number;\n}): React.CSSProperties {\n  const stickyOffset = fixedGridInverseStickyOffset({\n    viewportSize: viewportHeight,\n    windowSize: height,\n  });\n\n  return {\n    position: \"sticky\",\n    ...cssLengthProperty(\"height\", height),\n    ...cssLengthProperty(\"minWidth\", minWidth),\n    top: `${stickyOffset}px`,\n    bottom: `${stickyOffset}px`,\n  };\n}\n\nexport function getFixedGridInverseRowWindowStyles({\n  minWidth,\n  rowMinWidth,\n  totalSize,\n  viewportHeight,\n  window,\n}: {\n  minWidth?: CssLength;\n  rowMinWidth?: CssLength;\n  totalSize: CssLength;\n  viewportHeight: number;\n  window: FixedGridInverseRowWindowGeometry;\n}): {\n  offsetStyle: React.CSSProperties;\n  spacerStyle: React.CSSProperties;\n  windowStyle: React.CSSProperties;\n} {\n  return {\n    offsetStyle: getFixedGridInverseRowOffsetStyle({\n      height: window.start,\n      minWidth: rowMinWidth,\n    }),\n    spacerStyle: getFixedGridRowWindowStyle({\n      height: totalSize,\n      minWidth,\n    }),\n    windowStyle: getFixedGridInverseStickyRowWindowStyle({\n      height: window.size,\n      minWidth: rowMinWidth,\n      viewportHeight,\n    }),\n  };\n}\n\nexport function getFixedGridInverseRowWindowStyle({\n  height,\n  minWidth,\n  top,\n  viewportHeight,\n}: {\n  height: number;\n  minWidth?: CssLength;\n  top: number;\n  viewportHeight: number;\n}): React.CSSProperties {\n  const stickyOffset = fixedGridInverseStickyOffset({\n    viewportSize: viewportHeight,\n    windowSize: height,\n  });\n\n  return {\n    position: \"sticky\",\n    ...cssLengthProperty(\"height\", height),\n    ...cssLengthProperty(\"marginTop\", top),\n    ...cssLengthProperty(\"minWidth\", minWidth),\n    top: `${stickyOffset}px`,\n    bottom: `${stickyOffset}px`,\n  };\n}\n\nexport function fixedGridInverseStickyOffset({\n  viewportSize,\n  windowSize,\n}: {\n  viewportSize: number;\n  windowSize: number;\n}) {\n  const offset = Math.max(\n    0,\n    safeCssNumber(windowSize) - safeCssNumber(viewportSize),\n  );\n  return offset === 0 ? 0 : -offset;\n}\n\nexport function setFixedGridInverseRowWindowGeometry({\n  rowOffsetElement,\n  rowWindowElement,\n  viewportHeight,\n  window,\n}: {\n  rowOffsetElement: HTMLElement;\n  rowWindowElement: HTMLElement;\n  viewportHeight: number;\n  window: FixedGridInverseRowWindowGeometry;\n}) {\n  const offsetStyle = getFixedGridInverseRowOffsetStyle({\n    height: window.start,\n  });\n  const windowStyle = getFixedGridInverseStickyRowWindowStyle({\n    height: window.size,\n    viewportHeight,\n  });\n  patchStyleProperties(rowOffsetElement.style, offsetStyle, [\"height\"]);\n  patchStyleProperties(rowWindowElement.style, windowStyle, [\n    \"position\",\n    \"height\",\n    \"top\",\n    \"bottom\",\n  ]);\n  setStyleValue(rowWindowElement.style, \"margin-top\", \"\");\n}\n\nfunction formatCssLength(value: CssLength | undefined) {\n  if (typeof value === \"string\") return value.trim() ? value : undefined;\n  if (typeof value !== \"number\") return value;\n  return Number.isFinite(value) && value >= 0 ? `${value}px` : undefined;\n}\n\nfunction cssLengthProperty<Property extends CssLengthProperty>(\n  property: Property,\n  value: CssLength | undefined,\n): Pick<React.CSSProperties, Property> | object {\n  const formattedValue = formatCssLength(value);\n  return formattedValue === undefined ? {} : { [property]: formattedValue };\n}\n\nfunction safeCssNumber(value: number) {\n  return Number.isFinite(value) && value > 0 ? value : 0;\n}\n\nfunction patchStyleProperties(\n  style: CSSStyleDeclaration,\n  values: React.CSSProperties,\n  properties: string[],\n) {\n  for (const property of properties) {\n    const value = values[property as keyof React.CSSProperties];\n    setStyleValue(\n      style,\n      property,\n      typeof value === \"number\" ? `${value}px` : value,\n    );\n  }\n}\n\nfunction setStyleValue(\n  style: CSSStyleDeclaration,\n  propertyName: string,\n  value: unknown,\n) {\n  const nextValue = typeof value === \"string\" ? value : \"\";\n  if (style.getPropertyValue(propertyName) !== nextValue) {\n    style.setProperty(propertyName, nextValue);\n  }\n}\n",
      "type": "registry:ui",
      "target": "@ui/fixed-grid-layout.ts"
    },
    {
      "path": "registry/new-york-v4/ui/fixed-grid-selection.ts",
      "content": "export interface GridCellCoordinate {\n  rowIndex: number;\n  columnIndex: number;\n}\n\nexport function isSameGridCell(\n  left: GridCellCoordinate | null | undefined,\n  right: GridCellCoordinate | null | undefined,\n) {\n  return (\n    isValidGridCellCoordinate(left) &&\n    isValidGridCellCoordinate(right) &&\n    left.rowIndex === right.rowIndex &&\n    left.columnIndex === right.columnIndex\n  );\n}\n\nexport function gridCellKey({ rowIndex, columnIndex }: GridCellCoordinate) {\n  if (!isValidGridCellCoordinate({ rowIndex, columnIndex })) return null;\n  return `${rowIndex}:${columnIndex}`;\n}\n\nexport function parseGridCellKey(key: string): GridCellCoordinate | null {\n  const [rowIndexText, columnIndexText, extra] = key.split(\":\");\n  if (extra !== undefined) return null;\n  if (!rowIndexText || !columnIndexText) return null;\n  if (\n    !isUnsignedIntegerText(rowIndexText) ||\n    !isUnsignedIntegerText(columnIndexText)\n  ) {\n    return null;\n  }\n\n  const rowIndex = Number(rowIndexText);\n  const columnIndex = Number(columnIndexText);\n  if (!isValidGridCellCoordinate({ rowIndex, columnIndex })) {\n    return null;\n  }\n\n  return { rowIndex, columnIndex };\n}\n\nfunction isUnsignedIntegerText(value: string) {\n  return /^(0|[1-9]\\d*)$/.test(value);\n}\n\nfunction isValidGridCellCoordinate(\n  coordinate: GridCellCoordinate | null | undefined,\n): coordinate is GridCellCoordinate {\n  return (\n    !!coordinate &&\n    Number.isSafeInteger(coordinate.rowIndex) &&\n    Number.isSafeInteger(coordinate.columnIndex) &&\n    coordinate.rowIndex >= 0 &&\n    coordinate.columnIndex >= 0\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/fixed-grid-selection.ts"
    },
    {
      "path": "registry/new-york-v4/ui/fixed-grid-viewport.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nexport interface FixedGridViewportRefs {\n  scrollElement: HTMLDivElement | null;\n}\n\nexport interface FixedGridViewportProps\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n  scrollRef: React.Ref<HTMLDivElement>;\n  dataSlot: string;\n  children: React.ReactNode;\n}\n\nexport function FixedGridViewport({\n  scrollRef,\n  dataSlot,\n  className = \"absolute inset-0 overflow-auto outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset\",\n  children,\n  ...props\n}: FixedGridViewportProps) {\n  return (\n    <div ref={scrollRef} data-slot={dataSlot} className={className} {...props}>\n      {children}\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/fixed-grid-viewport.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/header-aware-scrollbar.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\nimport { useKeyedLayoutEffect } from \"@/hooks/use-keyed-layout-effect\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nexport function HeaderAwareScrollbar({\n  scrollRef,\n  headerHeight,\n}: {\n  scrollRef: React.RefObject<HTMLDivElement | null>;\n  headerHeight: number;\n}) {\n  const scrollElement = useResolvedScrollbarElement(scrollRef);\n  const [isVisible, setIsVisible] = React.useState(false);\n  const thumbRef = React.useRef<HTMLDivElement>(null);\n  const thumbMetrics = React.useRef({ height: 0, top: 0 });\n  const drag = React.useRef<{ y: number; scroll: number } | null>(null);\n  const frame = React.useRef(0);\n\n  const measure = React.useCallback(() => {\n    frame.current = 0;\n    if (!scrollElement) {\n      hideThumb(setIsVisible);\n      return;\n    }\n    const { scrollHeight, clientHeight, scrollTop } = scrollElement;\n    if (\n      !Number.isFinite(scrollHeight) ||\n      !Number.isFinite(clientHeight) ||\n      !Number.isFinite(scrollTop) ||\n      !Number.isFinite(headerHeight)\n    ) {\n      hideThumb(setIsVisible);\n      return;\n    }\n    const track = clientHeight - headerHeight;\n    if (scrollHeight <= clientHeight + 1 || track <= 0) {\n      hideThumb(setIsVisible);\n      return;\n    }\n    const height = Math.max(28, (clientHeight / scrollHeight) * track);\n    const maxScroll = scrollHeight - clientHeight;\n    const maxTop = track - height;\n    const top =\n      maxScroll > 0\n        ? clampScrollTop((scrollTop / maxScroll) * maxTop, maxTop)\n        : 0;\n    thumbMetrics.current = { height, top };\n    applyThumbStyle(thumbRef.current, thumbMetrics.current);\n    setIsVisible((current) => (current ? current : true));\n  }, [scrollElement, headerHeight]);\n\n  const scheduleMeasure = React.useCallback(() => {\n    if (frame.current) return;\n    frame.current = requestAnimationFrame(measure);\n  }, [measure]);\n\n  useKeyedMountEffect(\n    joinEffectKey([scrollElement, measure, scheduleMeasure]),\n    () => {\n      if (!scrollElement) {\n        hideThumb(setIsVisible);\n        return;\n      }\n      measure();\n      scrollElement.addEventListener(\"scroll\", scheduleMeasure, {\n        passive: true,\n      });\n      const observer =\n        typeof ResizeObserver !== \"undefined\"\n          ? new ResizeObserver(scheduleMeasure)\n          : null;\n      observer?.observe(scrollElement);\n      return () => {\n        if (frame.current) cancelAnimationFrame(frame.current);\n        scrollElement.removeEventListener(\"scroll\", scheduleMeasure);\n        observer?.disconnect();\n      };\n    },\n  );\n\n  useKeyedLayoutEffect(joinEffectKey([isVisible]), () => {\n    applyThumbStyle(thumbRef.current, thumbMetrics.current);\n  });\n\n  const onPointerDown = (event: React.PointerEvent<HTMLDivElement>) => {\n    if (!scrollElement) return;\n    event.preventDefault();\n    drag.current = { y: event.clientY, scroll: scrollElement.scrollTop };\n    event.currentTarget.setPointerCapture?.(event.pointerId);\n  };\n\n  const onPointerMove = (event: React.PointerEvent<HTMLDivElement>) => {\n    const currentDrag = drag.current;\n    if (!scrollElement || !currentDrag) return;\n    const track = scrollElement.clientHeight - headerHeight;\n    const height = Math.max(\n      28,\n      (scrollElement.clientHeight / scrollElement.scrollHeight) * track,\n    );\n    const denominator = track - height;\n    if (denominator <= 0) return;\n    const maxScroll = scrollElement.scrollHeight - scrollElement.clientHeight;\n    scrollElement.scrollTop = clampScrollTop(\n      currentDrag.scroll +\n        ((event.clientY - currentDrag.y) / denominator) * maxScroll,\n      maxScroll,\n    );\n  };\n\n  const endDrag = (event: React.PointerEvent<HTMLDivElement>) => {\n    drag.current = null;\n    event.currentTarget.releasePointerCapture?.(event.pointerId);\n  };\n\n  if (!isVisible) return null;\n  return (\n    <div\n      aria-hidden\n      className=\"pointer-events-none absolute right-0 z-30 w-2.5\"\n      style={{ top: headerHeight, bottom: 0 }}\n    >\n      <div\n        ref={thumbRef}\n        className=\"bg-foreground/25 hover:bg-foreground/40 pointer-events-auto absolute right-0.5 w-1.5 rounded-full transition-colors\"\n        style={{\n          top: 0,\n          height: thumbMetrics.current.height,\n          transform: `translateY(${thumbMetrics.current.top}px)`,\n        }}\n        onPointerDown={onPointerDown}\n        onPointerMove={onPointerMove}\n        onPointerUp={endDrag}\n        onPointerCancel={endDrag}\n      />\n    </div>\n  );\n}\n\nfunction useResolvedScrollbarElement(\n  scrollRef: React.RefObject<HTMLDivElement | null>,\n) {\n  const [scrollElement, setScrollElement] = React.useState(scrollRef.current);\n\n  useKeyedLayoutEffect(\n    joinEffectKey([scrollRef, scrollRef.current, scrollElement]),\n    () => {\n      const nextScrollElement = scrollRef.current;\n      if (scrollElement !== nextScrollElement) {\n        setScrollElement(nextScrollElement);\n      }\n    },\n  );\n\n  return scrollElement;\n}\n\nfunction hideThumb(setThumb: React.Dispatch<React.SetStateAction<boolean>>) {\n  setThumb((current) => (current ? false : current));\n}\n\nfunction clampScrollTop(value: number, maxScroll: number) {\n  if (!Number.isFinite(value)) return 0;\n  if (!Number.isFinite(maxScroll) || maxScroll <= 0) return 0;\n  return Math.min(maxScroll, Math.max(0, value));\n}\n\nfunction applyThumbStyle(\n  element: HTMLDivElement | null,\n  metrics: { height: number; top: number },\n) {\n  if (!element) return;\n  element.style.height = `${metrics.height}px`;\n  element.style.transform = `translateY(${metrics.top}px)`;\n}\n",
      "type": "registry:ui",
      "target": "@ui/header-aware-scrollbar.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/viewer-scrollbar-css.ts",
      "content": "export function viewerScrollbarCss(slotName: string) {\n  return `\n[data-slot=\"${slotName}\"]::-webkit-scrollbar { width: 10px; height: 10px; }\n[data-slot=\"${slotName}\"]::-webkit-scrollbar:vertical { display: none; }\n[data-slot=\"${slotName}\"]::-webkit-scrollbar-track { background: transparent; }\n[data-slot=\"${slotName}\"]::-webkit-scrollbar-thumb {\n  background-color: color-mix(in oklab, var(--foreground) 22%, transparent);\n  border-radius: 9999px;\n  border: 3px solid transparent;\n  background-clip: content-box;\n}\n[data-slot=\"${slotName}\"]::-webkit-scrollbar-thumb:hover {\n  background-color: color-mix(in oklab, var(--foreground) 38%, transparent);\n}\n`;\n}\n",
      "type": "registry:ui",
      "target": "@ui/viewer-scrollbar-css.ts"
    },
    {
      "path": "registry/new-york-v4/ui/csv-viewer-scrollbar.tsx",
      "content": "import { viewerScrollbarCss } from \"./viewer-scrollbar-css\";\n\nexport { HeaderAwareScrollbar } from \"./header-aware-scrollbar\";\n\nexport const CSV_SCROLLBAR_CSS = viewerScrollbarCss(\"csv-body\");\n",
      "type": "registry:ui",
      "target": "@ui/csv-viewer-scrollbar.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/csv-viewer-resource.ts",
      "content": "import { resolveCsvDialect, type CsvDialect, type CsvTable } from \"@/lib/csv\";\nimport type {\n  ViewerContentPayload,\n  ViewerContentBlob,\n  ViewerContentStream,\n  ViewerResource,\n} from \"@/lib/viewer-resource\";\nimport { viewerContentRenderKey } from \"@/lib/viewer-resource\";\nimport type {\n  BlobViewerSource,\n  TextSource,\n  UrlViewerSource,\n} from \"@/lib/viewer-source\";\nimport { textPayloadKey } from \"@/lib/viewer-source\";\n\nexport type CsvContent = ViewerContentPayload &\n  ViewerContentBlob &\n  ViewerContentStream;\n\nexport type CsvResource =\n  | { kind: \"resource\"; content: CsvContent }\n  | { kind: \"text\"; text: string }\n  | { kind: \"table\"; table: CsvTable; fileName?: string }\n  | { kind: \"empty\" };\n\nexport type CsvDocumentSource = UrlViewerSource | BlobViewerSource | TextSource;\n\nexport interface CsvTableSource {\n  kind: \"table\";\n  table: CsvTable;\n  fileName?: string;\n  identityKey?: string;\n  dialect?: CsvDialect;\n}\n\nexport type CsvViewerSource = CsvDocumentSource | CsvTableSource;\n\nexport interface CsvResourceInput {\n  source?: CsvViewerSource;\n  resource?: ViewerResource | null;\n}\n\nexport interface CsvViewerDialectInput {\n  dialect?: CsvDialect;\n  source?: CsvViewerSource;\n  resource?: ViewerResource | null;\n}\n\nexport function isCsvDocumentSource(\n  source: CsvViewerSource,\n): source is CsvDocumentSource {\n  return source.kind !== \"table\";\n}\n\nexport function resolveCsvResource({\n  source,\n  resource,\n}: CsvResourceInput): CsvResource {\n  if (source?.kind === \"table\") {\n    return {\n      kind: \"table\",\n      table: source.table,\n      fileName: source.fileName,\n    };\n  }\n  if (resource) {\n    if (resource.content.payload.kind === \"text\") {\n      return { kind: \"text\", text: resource.content.payload.text };\n    }\n    return { kind: \"resource\", content: resource.content };\n  }\n  return { kind: \"empty\" };\n}\n\nexport function resolveCsvViewerDialect({\n  dialect,\n  source,\n  resource,\n}: CsvViewerDialectInput): CsvDialect {\n  const tableDialect = source?.kind === \"table\" ? source.dialect : undefined;\n  const tableFileName = source?.kind === \"table\" ? source.fileName : undefined;\n  return resolveCsvDialect({\n    dialect: dialect ?? tableDialect,\n    descriptor: {\n      src: resource?.content.directUrl ?? undefined,\n      fileName: resource?.fileName ?? tableFileName,\n      mimeType: resource?.mimeType,\n    },\n  });\n}\n\nexport function csvViewerSortResetKey({\n  dialect,\n  source,\n  resource,\n}: {\n  dialect: CsvDialect;\n  source?: CsvViewerSource;\n  resource?: ViewerResource | null;\n}): unknown {\n  const dialectKey = `${dialect.delimiter}\\u0000${dialect.hasHeader}`;\n  if (source?.kind === \"text\") {\n    return `${source.identityKey ?? \"\"}\\u0000${textPayloadKey(source.text)}\\u0000${dialectKey}`;\n  }\n  if (resource) {\n    return `${resource.keys.load}\\u0000${viewerContentRenderKey(resource.content)}\\u0000${dialectKey}`;\n  }\n  if (source?.kind === \"table\") return source.identityKey ?? source.table;\n  return \"empty\";\n}\n\nexport function csvViewerExportFileName({\n  dialect,\n  source,\n  resource,\n  fallback,\n}: {\n  dialect: CsvDialect;\n  source?: CsvViewerSource;\n  resource?: ViewerResource | null;\n  fallback: (dialect: CsvDialect) => string;\n}): string {\n  const tableFileName = source?.kind === \"table\" ? source.fileName : undefined;\n  return resource?.fileName ?? tableFileName ?? fallback(dialect);\n}\n",
      "type": "registry:ui",
      "target": "@ui/csv-viewer-resource.ts"
    },
    {
      "path": "registry/new-york-v4/ui/csv-viewer-state.ts",
      "content": "import * as React from \"react\";\n\nimport { useKeyedMountEffect } from \"@/hooks/use-keyed-mount-effect\";\nimport {\n  parseCsv,\n  streamCsv,\n  type CsvDialect,\n  type CsvStreamSource,\n} from \"@/lib/csv\";\nimport {\n  isAbortError,\n  isResourceError,\n  ResourceError,\n} from \"@/lib/viewer-errors\";\n\nimport {\n  resolveCsvResource,\n  type CsvResource,\n  type CsvResourceInput,\n} from \"./csv-viewer-resource\";\nimport {\n  createCsvRowStoreFromRows,\n  createMutableCsvRowStore,\n  emptyCsvRowStore,\n  type CsvRowStore,\n} from \"./csv-row-store\";\nimport {\n  CsvWorkerUnavailableError,\n  parseCsvInWorker,\n  toCsvFormatError,\n} from \"./csv-viewer-worker\";\nimport type { GridCellCoordinate } from \"./fixed-grid-selection\";\nimport { joinEffectKey } from \"@/lib/effect-key\";\n\nconst CSV_STREAM_BATCH_SIZE = 5000;\nconst SYNC_TEXT_PARSE_MAX_BYTES = 256 * 1024;\n\nexport type CsvCellAddress = GridCellCoordinate;\n\nexport type CsvResourceState =\n  | {\n      status: \"idle\";\n      columns: string[];\n      sourceRows: string[][];\n      rowStore: CsvRowStore;\n    }\n  | {\n      status: \"loading\";\n      columns: string[];\n      sourceRows: string[][];\n      rowStore: CsvRowStore;\n    }\n  | {\n      status: \"ready\";\n      columns: string[];\n      sourceRows: string[][];\n      rowStore: CsvRowStore;\n    }\n  | {\n      status: \"empty\";\n      columns: string[];\n      sourceRows: string[][];\n      rowStore: CsvRowStore;\n    }\n  | {\n      status: \"error\";\n      columns: string[];\n      sourceRows: string[][];\n      rowStore: CsvRowStore;\n      error: unknown;\n    };\n\nexport function readyCsvState(\n  columns: string[],\n  sourceRows: string[][],\n): CsvResourceState {\n  const rowStore = createCsvRowStoreFromRows(sourceRows);\n  return sourceRows.length === 0\n    ? { status: \"empty\", columns, sourceRows, rowStore }\n    : { status: \"ready\", columns, sourceRows, rowStore };\n}\n\nexport function useCsvResourceState({\n  source,\n  resource,\n  dialect,\n  retryVersion = 0,\n}: CsvResourceInput & {\n  dialect: CsvDialect;\n  retryVersion?: number;\n}): CsvResourceState {\n  const content = resource?.content ?? null;\n  const { delimiter, hasHeader } = dialect;\n  const csvDialect = React.useMemo(\n    () => ({ delimiter, hasHeader }),\n    [delimiter, hasHeader],\n  );\n  const tableSource = source?.kind === \"table\" ? source : null;\n  const textSource = source?.kind === \"text\" ? source : null;\n  const csvResource = React.useMemo<CsvResource>(() => {\n    if (tableSource) {\n      return resolveCsvResource({ source: tableSource });\n    }\n    if (textSource) {\n      return { kind: \"text\", text: textSource.text };\n    }\n    if (!content) {\n      return { kind: \"empty\" };\n    }\n    if (content.payload.kind === \"text\") {\n      return { kind: \"text\", text: content.payload.text };\n    }\n    return { kind: \"resource\", content };\n  }, [tableSource, textSource, content]);\n  const syncState = React.useMemo<CsvResourceState | null>(() => {\n    if (csvResource.kind === \"table\") {\n      return readyCsvState(csvResource.table.columns, csvResource.table.rows);\n    }\n    if (csvResource.kind === \"text\") {\n      if (csvResource.text.length > SYNC_TEXT_PARSE_MAX_BYTES) return null;\n      const table = parseCsv(csvResource.text, csvDialect);\n      return readyCsvState(table.columns, table.rows);\n    }\n    if (csvResource.kind === \"empty\") {\n      return {\n        status: \"idle\",\n        columns: [],\n        sourceRows: [],\n        rowStore: emptyCsvRowStore(),\n      };\n    }\n    return null;\n  }, [csvResource, csvDialect]);\n\n  const [state, setState] = React.useState<CsvResourceState>({\n    status: \"idle\",\n    columns: [],\n    sourceRows: [],\n    rowStore: emptyCsvRowStore(),\n  });\n\n  const resourceEffectKey =\n    syncState ||\n    (csvResource.kind !== \"resource\" && csvResource.kind !== \"text\")\n      ? null\n      : joinEffectKey([\"csv-resource\", csvResource, csvDialect, retryVersion]);\n  useKeyedMountEffect(resourceEffectKey, () => {\n    if (syncState) return;\n    if (csvResource.kind !== \"resource\" && csvResource.kind !== \"text\") return;\n\n    const controller = new AbortController();\n    const rowStore = createMutableCsvRowStore();\n    let columns: string[] = [];\n    let cancelled = false;\n    setState({\n      status: \"loading\",\n      columns: [],\n      sourceRows: [],\n      rowStore: rowStore.snapshot(),\n    });\n\n    const onColumns = (next: string[]) => {\n      if (cancelled) return;\n      columns = next;\n      rowStore.padRowsToColumnCount(columns.length);\n      setState({\n        status: \"loading\",\n        columns,\n        sourceRows: [],\n        rowStore: rowStore.snapshot(),\n      });\n    };\n\n    const onSourceRows = (sourceRowBatch: string[][]) => {\n      if (cancelled) return;\n      rowStore.appendRows(sourceRowBatch);\n      setState({\n        status: \"loading\",\n        columns,\n        sourceRows: [],\n        rowStore: rowStore.snapshot(),\n      });\n    };\n\n    const onDone = () => {\n      if (cancelled) return;\n      setState(readyCsvState(columns, rowStore.materializeRows()));\n    };\n\n    const onError = (error: unknown) => {\n      if (cancelled || controller.signal.aborted) return;\n      const sourceRows = rowStore.materializeRows();\n      setState({\n        status: \"error\",\n        columns,\n        sourceRows,\n        rowStore: createCsvRowStoreFromRows(sourceRows),\n        error: toCsvPreviewError(error),\n      });\n    };\n\n    const runMainThread = (input: CsvStreamSource) => {\n      void streamCsv(\n        input,\n        { onColumns, onRows: onSourceRows, onDone, onError },\n        {\n          delimiter: csvDialect.delimiter,\n          hasHeader: csvDialect.hasHeader,\n          batchSize: CSV_STREAM_BATCH_SIZE,\n          signal: controller.signal,\n        },\n      );\n    };\n\n    const runResource = async () => {\n      try {\n        if (csvResource.kind === \"text\") {\n          const textBlob = new Blob([csvResource.text], { type: \"text/csv\" });\n          if (typeof Worker !== \"undefined\") {\n            void parseCsvInWorker({\n              source: textBlob,\n              dialect: csvDialect,\n              batchSize: CSV_STREAM_BATCH_SIZE,\n              onColumns,\n              onSourceRows,\n              signal: controller.signal,\n            }).then(onDone, (error) => {\n              if (error instanceof CsvWorkerUnavailableError) {\n                runMainThread(csvResource.text);\n              } else {\n                onError(error);\n              }\n            });\n            return;\n          }\n\n          runMainThread(csvResource.text);\n          return;\n        }\n\n        if (csvResource.content.payload.kind === \"blob\") {\n          const { blob } = csvResource.content.payload;\n          if (typeof Worker !== \"undefined\") {\n            void parseCsvInWorker({\n              source: blob,\n              dialect: csvDialect,\n              batchSize: CSV_STREAM_BATCH_SIZE,\n              onColumns,\n              onSourceRows,\n              signal: controller.signal,\n            }).then(onDone, (error) => {\n              if (error instanceof CsvWorkerUnavailableError) {\n                runMainThread(blob);\n              } else {\n                onError(error);\n              }\n            });\n            return;\n          }\n\n          runMainThread(blob);\n          return;\n        }\n\n        if (\n          csvResource.content.payload.kind === \"url\" &&\n          typeof Worker !== \"undefined\"\n        ) {\n          void csvResource.content\n            .readBlob({ cache: \"no-store\", signal: controller.signal })\n            .then((blob) =>\n              parseCsvInWorker({\n                source: blob,\n                dialect: csvDialect,\n                batchSize: CSV_STREAM_BATCH_SIZE,\n                onColumns,\n                onSourceRows,\n                signal: controller.signal,\n              }),\n            )\n            .then(onDone, (error) => {\n              if (error instanceof CsvWorkerUnavailableError) {\n                void runResourceStream();\n              } else {\n                onError(error);\n              }\n            });\n          return;\n        }\n\n        await runResourceStream();\n      } catch (error) {\n        onError(error);\n      }\n    };\n\n    const runResourceStream = async () => {\n      if (csvResource.kind !== \"resource\") return;\n      try {\n        const cache =\n          csvResource.content.payload.kind === \"url\" ? \"no-store\" : undefined;\n        runMainThread(\n          await csvResource.content.readStream(\n            cache\n              ? { cache, signal: controller.signal }\n              : { signal: controller.signal },\n          ),\n        );\n      } catch (error) {\n        onError(error);\n      }\n    };\n\n    void runResource();\n\n    return () => {\n      cancelled = true;\n      controller.abort();\n    };\n  });\n\n  return syncState ?? state;\n}\n\nfunction toCsvPreviewError(error: unknown): Error {\n  if (isResourceError(error)) return error;\n  if (isAbortError(error)) {\n    return new ResourceError({\n      kind: \"aborted\",\n      message: \"Loading was cancelled.\",\n      cause: error,\n    });\n  }\n  return toCsvFormatError(error);\n}\n",
      "type": "registry:ui",
      "target": "@ui/csv-viewer-state.ts"
    },
    {
      "path": "registry/new-york-v4/ui/csv-viewer-style-scope.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { createPortal } from \"react-dom\";\n\nimport { useMountEffect } from \"@/hooks/use-mount-effect\";\n\nexport function stripHasRules(owner: CSSStyleSheet | CSSGroupingRule) {\n  const rules = owner.cssRules;\n  if (!rules) return;\n  for (let index = rules.length - 1; index >= 0; index--) {\n    const rule = rules[index];\n    if ((rule as CSSStyleRule).selectorText?.includes(\":has(\")) {\n      try {\n        owner.deleteRule(index);\n      } catch {\n        // Ignore rules that cannot be removed.\n      }\n    } else if ((rule as CSSGroupingRule).cssRules?.length) {\n      stripHasRules(rule as CSSGroupingRule);\n    }\n  }\n}\n\nlet sharedSheets: CSSStyleSheet[] | null = null;\nfunction getSharedSheets(): CSSStyleSheet[] {\n  if (sharedSheets) return sharedSheets;\n  const sheets: CSSStyleSheet[] = [];\n  for (const sheet of Array.from(document.styleSheets)) {\n    let rules: CSSRuleList;\n    try {\n      rules = sheet.cssRules;\n    } catch {\n      continue;\n    }\n    let text = \"\";\n    for (const rule of Array.from(rules)) text += rule.cssText + \"\\n\";\n    try {\n      const clone = new CSSStyleSheet();\n      clone.replaceSync(text);\n      stripHasRules(clone);\n      sheets.push(clone);\n    } catch {\n      // Skip stylesheets that cannot be reconstructed.\n    }\n  }\n  sharedSheets = sheets;\n  return sheets;\n}\n\nfunction ShadowScope({\n  className,\n  style,\n  children,\n}: {\n  className?: string;\n  style?: React.CSSProperties;\n  children: React.ReactNode;\n}) {\n  const hostRef = React.useRef<HTMLDivElement>(null);\n  const [root, setRoot] = React.useState<ShadowRoot | null>(null);\n\n  useMountEffect(() => {\n    const host = hostRef.current;\n    if (!host) return;\n    const shadowRoot = host.shadowRoot ?? host.attachShadow({ mode: \"open\" });\n    try {\n      shadowRoot.adoptedStyleSheets = getSharedSheets();\n    } catch {\n      for (const node of Array.from(\n        document.querySelectorAll('style, link[rel=\"stylesheet\"]'),\n      )) {\n        try {\n          shadowRoot.appendChild(node.cloneNode(true));\n        } catch {\n          // Ignore nodes that cannot be cloned.\n        }\n      }\n    }\n    setRoot(shadowRoot);\n  });\n\n  return (\n    <div ref={hostRef} className={className} style={style}>\n      {root ? createPortal(children, root) : null}\n    </div>\n  );\n}\n\nexport function CsvStyleScope({\n  isolate,\n  className,\n  style,\n  children,\n}: {\n  isolate: boolean;\n  className?: string;\n  style?: React.CSSProperties;\n  children: React.ReactNode;\n}) {\n  if (isolate) {\n    return (\n      <ShadowScope className={className} style={style}>\n        {children}\n      </ShadowScope>\n    );\n  }\n  return (\n    <div className={className} style={style}>\n      {children}\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/csv-viewer-style-scope.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/csv-viewer-types.ts",
      "content": "import type { CsvDialect } from \"@/lib/csv\";\n\nimport type { CsvViewerSource } from \"./csv-viewer-resource\";\nimport type { CsvCellAddress } from \"./csv-viewer-state\";\n\nexport interface CsvScrollOptions {\n  behavior?: ScrollBehavior;\n}\n\nexport interface CsvViewerHandle {\n  scrollToCell: (\n    cellAddress: CsvCellAddress,\n    options?: CsvScrollOptions,\n  ) => void;\n  getViewportElement: () => HTMLDivElement | null;\n}\n\nexport interface CsvViewerProps {\n  source?: CsvViewerSource;\n  dialect?: CsvDialect;\n  className?: string;\n  controls?: boolean;\n  height?: number;\n  fillHeight?: boolean;\n  activeCell?: CsvCellAddress | null;\n  isolateStyles?: boolean;\n}\n",
      "type": "registry:ui",
      "target": "@ui/csv-viewer-types.ts"
    },
    {
      "path": "registry/new-york-v4/ui/csv-viewer-worker.ts",
      "content": "import type { CsvDialect } from \"@/lib/csv\";\nimport {\n  isViewerFormatError,\n  ViewerFormatError,\n  type ViewerFormatErrorMapperOptions,\n} from \"@/lib/viewer-errors\";\n\nexport class CsvWorkerUnavailableError extends Error {\n  constructor(message = \"CSV worker unavailable\", options?: ErrorOptions) {\n    super(message, options);\n    this.name = \"CsvWorkerUnavailableError\";\n  }\n}\n\nexport function toCsvFormatError(\n  error: unknown,\n  options: ViewerFormatErrorMapperOptions = {\n    kind: \"parse_failed\",\n    message: \"Failed to parse CSV.\",\n  },\n): ViewerFormatError {\n  if (isViewerFormatError(error)) return error;\n  return new ViewerFormatError({\n    format: \"csv\",\n    kind: options.kind,\n    message: options.message,\n    cause: error,\n  });\n}\n\nexport interface CsvWorkerRequest {\n  parseRequestId: string;\n  source: Blob;\n  dialect: CsvDialect;\n  batchSize: number;\n}\n\nexport type CsvWorkerResponse =\n  | { type: \"columns\"; parseRequestId: string; columns: string[] }\n  | { type: \"sourceRows\"; parseRequestId: string; sourceRows: string[][] }\n  | { type: \"done\"; parseRequestId: string }\n  | { type: \"error\"; parseRequestId: string; message: string };\n\nexport function createCsvWorker(): Worker {\n  return new Worker(new URL(\"./csv-viewer.worker.ts\", import.meta.url), {\n    type: \"module\",\n  });\n}\n\nexport function parseCsvInWorker({\n  source,\n  dialect,\n  batchSize,\n  onColumns,\n  onSourceRows,\n  signal,\n}: {\n  source: Blob;\n  dialect: CsvDialect;\n  batchSize: number;\n  onColumns: (columns: string[]) => void;\n  onSourceRows: (sourceRows: string[][]) => void;\n  signal: AbortSignal;\n}): Promise<void> {\n  return new Promise((resolve, reject) => {\n    let worker: Worker;\n    try {\n      worker = createCsvWorker();\n    } catch (error) {\n      reject(\n        new CsvWorkerUnavailableError(\"CSV worker unavailable\", {\n          cause: error,\n        }),\n      );\n      return;\n    }\n\n    const parseRequestId = crypto.randomUUID();\n    const cleanup = () => {\n      signal.removeEventListener(\"abort\", abort);\n      worker.terminate();\n    };\n    const abort = () => {\n      cleanup();\n      reject(new DOMException(\"Aborted\", \"AbortError\"));\n    };\n\n    signal.addEventListener(\"abort\", abort, { once: true });\n    worker.onerror = (event) => {\n      cleanup();\n      reject(\n        toCsvFormatError(event, {\n          kind: \"worker_failed\",\n          message: event?.message || \"CSV worker failed.\",\n        }),\n      );\n    };\n    worker.onmessage = (event: MessageEvent<CsvWorkerResponse>) => {\n      const message = event.data;\n      if (message.parseRequestId !== parseRequestId) return;\n      if (message.type === \"columns\") onColumns(message.columns);\n      else if (message.type === \"sourceRows\") onSourceRows(message.sourceRows);\n      else if (message.type === \"done\") {\n        cleanup();\n        resolve();\n      } else {\n        cleanup();\n        reject(\n          toCsvFormatError(undefined, {\n            kind: \"parse_failed\",\n            message: message.message || \"Failed to parse CSV.\",\n          }),\n        );\n      }\n    };\n\n    const request: CsvWorkerRequest = {\n      parseRequestId,\n      source,\n      dialect,\n      batchSize,\n    };\n    worker.postMessage(request);\n  });\n}\n",
      "type": "registry:ui",
      "target": "@ui/csv-viewer-worker.ts"
    },
    {
      "path": "registry/new-york-v4/ui/csv-viewer.worker.ts",
      "content": "import {\n  createCsvNormalizer,\n  createCsvParser,\n  padRowsToColumnCount,\n  type CsvDialect,\n} from \"@/lib/csv\";\n\nimport type { CsvWorkerRequest, CsvWorkerResponse } from \"./csv-viewer-worker\";\n\nfunction post(message: CsvWorkerResponse) {\n  self.postMessage(message);\n}\n\nasync function* readWorkerTextChunks(source: Blob): AsyncGenerator<string> {\n  const reader = source.stream().getReader();\n  const decoder = new TextDecoder();\n  try {\n    while (true) {\n      const { done, value } = await reader.read();\n      if (done) break;\n      if (value) {\n        const text = decoder.decode(value, { stream: true });\n        if (text) yield text;\n      }\n    }\n    const rest = decoder.decode();\n    if (rest) yield rest;\n  } finally {\n    reader.releaseLock();\n  }\n}\n\nasync function parseInWorker({\n  parseRequestId,\n  source,\n  dialect,\n  batchSize,\n}: CsvWorkerRequest) {\n  const parser = createCsvParser({ delimiter: dialect.delimiter });\n  const normalizer = createCsvNormalizer({ hasHeader: dialect.hasHeader });\n  let sourceRowBatch: string[][] = [];\n\n  const handleRecords = (records: string[][]) => {\n    for (const record of records) {\n      for (const event of normalizer.accept(record)) {\n        if (event.type === \"columns\") {\n          padRowsToColumnCount(sourceRowBatch, event.columns.length);\n          post({ type: \"columns\", parseRequestId, columns: event.columns });\n        } else {\n          sourceRowBatch.push(event.row);\n        }\n      }\n      if (sourceRowBatch.length >= batchSize) {\n        post({\n          type: \"sourceRows\",\n          parseRequestId,\n          sourceRows: sourceRowBatch,\n        });\n        sourceRowBatch = [];\n      }\n    }\n  };\n\n  for await (const chunk of readWorkerTextChunks(source)) {\n    handleRecords(parser.push(chunk));\n  }\n  handleRecords(parser.flush());\n  if (sourceRowBatch.length) {\n    post({\n      type: \"sourceRows\",\n      parseRequestId,\n      sourceRows: sourceRowBatch,\n    });\n  }\n  post({ type: \"done\", parseRequestId });\n}\n\nself.onmessage = (event: MessageEvent<CsvWorkerRequest>) => {\n  void parseInWorker(event.data).catch((error) => {\n    const message = error instanceof Error ? error.message : String(error);\n    post({\n      type: \"error\",\n      parseRequestId: event.data.parseRequestId,\n      message,\n    });\n  });\n};\n",
      "type": "registry:ui",
      "target": "@ui/csv-viewer.worker.ts"
    },
    {
      "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/fixed-grid-row-window.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n  getFixedGridInverseRowWindowStyles,\n  type CssLength,\n  type FixedGridInverseRowWindowGeometry,\n} from \"./fixed-grid-layout\";\n\ntype FixedGridRowWindowElement = keyof React.JSX.IntrinsicElements;\ntype FixedGridRowWindowElementProps = React.HTMLAttributes<HTMLElement> & {\n  \"data-slot\"?: string;\n};\n\nexport interface FixedGridRowWindowProps extends Omit<\n  FixedGridRowWindowElementProps,\n  \"children\"\n> {\n  as?: FixedGridRowWindowElement;\n  children?: React.ReactNode;\n  minWidth?: CssLength;\n  offsetAs?: FixedGridRowWindowElement;\n  offsetClassName?: string;\n  offsetDataSlot?: string;\n  offsetProps?: FixedGridRowWindowElementProps;\n  rowMinWidth?: CssLength;\n  rowOffsetRef?: React.Ref<HTMLElement>;\n  rowWindowRef?: React.Ref<HTMLElement>;\n  totalSize: CssLength;\n  viewportHeight: number;\n  virtualRowWindow: FixedGridInverseRowWindowGeometry;\n  windowAs?: FixedGridRowWindowElement;\n  windowClassName?: string;\n  windowDataSlot?: string;\n  windowProps?: FixedGridRowWindowElementProps;\n}\n\nexport function FixedGridRowWindow({\n  as = \"div\",\n  children,\n  minWidth,\n  offsetAs = \"div\",\n  offsetClassName,\n  offsetDataSlot,\n  offsetProps,\n  rowMinWidth,\n  rowOffsetRef,\n  rowWindowRef,\n  style,\n  totalSize,\n  viewportHeight,\n  virtualRowWindow,\n  windowAs = \"div\",\n  windowClassName,\n  windowDataSlot,\n  windowProps,\n  className,\n  ...props\n}: FixedGridRowWindowProps) {\n  const { offsetStyle, spacerStyle, windowStyle } =\n    getFixedGridInverseRowWindowStyles({\n      minWidth,\n      rowMinWidth,\n      totalSize,\n      viewportHeight,\n      window: virtualRowWindow,\n    });\n  const offsetElementProps = {\n    ...offsetProps,\n    ...(offsetDataSlot ? { \"data-slot\": offsetDataSlot } : null),\n    \"aria-hidden\": offsetProps?.[\"aria-hidden\"] ?? true,\n    className: offsetClassName ?? offsetProps?.className,\n    ref: rowOffsetRef,\n    style: { ...offsetProps?.style, ...offsetStyle },\n  };\n  const windowElementProps = {\n    ...windowProps,\n    ...(windowDataSlot ? { \"data-slot\": windowDataSlot } : null),\n    className: windowClassName ?? windowProps?.className,\n    ref: rowWindowRef,\n    style: { ...windowProps?.style, ...windowStyle },\n  };\n\n  return React.createElement(\n    as,\n    {\n      ...props,\n      className,\n      style: { ...style, ...spacerStyle },\n    },\n    React.createElement(offsetAs, offsetElementProps),\n    React.createElement(windowAs, windowElementProps, children),\n  );\n}\n",
      "type": "registry:ui",
      "target": "@ui/fixed-grid-row-window.tsx"
    },
    {
      "path": "registry/new-york-v4/ui/fixed-grid-native-find-index.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 { joinEffectKey } from \"@/lib/effect-key\";\n\nexport interface FixedGridNativeFindCellAddress {\n  rowIndex: number;\n  columnIndex: number;\n}\n\nexport interface FixedGridNativeFindChunk {\n  startRowIndex: number;\n  endRowIndex: number;\n  text: string;\n}\n\ninterface FixedGridNativeFindBuild {\n  chunks: FixedGridNativeFindChunk[];\n  indexedCellCount: number;\n}\n\ntype FixedGridNativeFindIdleWindow = Window &\n  typeof globalThis & {\n    cancelIdleCallback?: Window[\"cancelIdleCallback\"];\n    requestIdleCallback?: Window[\"requestIdleCallback\"];\n  };\n\nconst DEFAULT_MAX_CELLS_PER_CHUNK = 512;\nconst DEFAULT_MAX_INDEXED_CELLS = 250_000;\nconst NATIVE_FIND_IDLE_TIMEOUT_MS = 400;\nconst NATIVE_FIND_FALLBACK_DELAY_MS = 80;\n\nexport function FixedGridNativeFindIndex({\n  rowCount,\n  columnCount,\n  getCellText,\n  onCellMatch,\n  dataSlot,\n  enabled = true,\n  maxCellsPerChunk = DEFAULT_MAX_CELLS_PER_CHUNK,\n  maxIndexedCells = DEFAULT_MAX_INDEXED_CELLS,\n}: {\n  rowCount: number;\n  columnCount: number;\n  getCellText: (rowIndex: number, columnIndex: number) => string;\n  onCellMatch: (cell: FixedGridNativeFindCellAddress) => void;\n  dataSlot: string;\n  enabled?: boolean;\n  maxCellsPerChunk?: number;\n  maxIndexedCells?: number;\n}) {\n  const [isReady, setIsReady] = React.useState(false);\n\n  useKeyedMountEffect(\n    joinEffectKey([\n      \"fixed-grid-native-find-ready\",\n      enabled,\n      rowCount,\n      columnCount,\n      getCellText,\n      maxCellsPerChunk,\n      maxIndexedCells,\n    ]),\n    () => {\n      setIsReady(false);\n      if (\n        !enabled ||\n        !canBuildFixedGridNativeFindIndex({\n          rowCount,\n          columnCount,\n          maxIndexedCells,\n        })\n      ) {\n        return;\n      }\n      if (typeof window === \"undefined\") return;\n\n      const show = () => setIsReady(true);\n      const browserWindow = window as FixedGridNativeFindIdleWindow;\n      if (\n        browserWindow.requestIdleCallback &&\n        browserWindow.cancelIdleCallback\n      ) {\n        const idleId = browserWindow.requestIdleCallback(show, {\n          timeout: NATIVE_FIND_IDLE_TIMEOUT_MS,\n        });\n        return () => browserWindow.cancelIdleCallback?.(idleId);\n      }\n\n      const timeoutId = browserWindow.setTimeout(\n        show,\n        NATIVE_FIND_FALLBACK_DELAY_MS,\n      );\n      return () => browserWindow.clearTimeout(timeoutId);\n    },\n  );\n\n  const index = React.useMemo<FixedGridNativeFindBuild | null>(() => {\n    if (!enabled || !isReady) return null;\n    return buildFixedGridNativeFindIndex({\n      rowCount,\n      columnCount,\n      getCellText,\n      maxCellsPerChunk,\n      maxIndexedCells,\n    });\n  }, [\n    columnCount,\n    enabled,\n    getCellText,\n    isReady,\n    maxCellsPerChunk,\n    maxIndexedCells,\n    rowCount,\n  ]);\n\n  if (!index || index.chunks.length === 0) return null;\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-cells={index.indexedCellCount}\n      data-native-find-indexed-chunks={index.chunks.length}\n      data-native-find-indexed-columns={normalizeNativeFindCount(columnCount)}\n      data-native-find-indexed-rows={normalizeNativeFindCount(rowCount)}\n      data-slot={dataSlot}\n    >\n      {index.chunks.map((chunk) => (\n        <FixedGridNativeFindEntry\n          key={chunk.startRowIndex}\n          chunk={chunk}\n          columnCount={columnCount}\n          getCellText={getCellText}\n          onCellMatch={onCellMatch}\n        />\n      ))}\n    </div>\n  );\n}\n\nfunction FixedGridNativeFindEntry({\n  chunk,\n  columnCount,\n  getCellText,\n  onCellMatch,\n}: {\n  chunk: FixedGridNativeFindChunk;\n  columnCount: number;\n  getCellText: (rowIndex: number, columnIndex: number) => string;\n  onCellMatch: (cell: FixedGridNativeFindCellAddress) => void;\n}) {\n  const ref = React.useRef<HTMLSpanElement | null>(null);\n\n  useKeyedLayoutEffect(\n    joinEffectKey([\n      \"fixed-grid-native-find-entry\",\n      chunk.startRowIndex,\n      chunk.endRowIndex,\n      chunk.text,\n      columnCount,\n      getCellText,\n      onCellMatch,\n    ]),\n    () => {\n      const element = ref.current;\n      if (!element) return;\n      element.setAttribute(\"hidden\", \"until-found\");\n\n      const scrollToSelectedCell = () => {\n        onCellMatch(\n          resolveFixedGridNativeFindCell({\n            chunk,\n            columnCount,\n            getCellText,\n            offset: selectedTextOffsetIn(element),\n          }),\n        );\n      };\n\n      const handleBeforeMatch = () => {\n        scrollToSelectedCell();\n        if (typeof requestAnimationFrame === \"function\") {\n          requestAnimationFrame(() => {\n            scrollToSelectedCell();\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-row={chunk.endRowIndex}\n      data-native-find-start-row={chunk.startRowIndex}\n    >\n      {chunk.text || \" \"}\n    </span>\n  );\n}\n\nexport function buildFixedGridNativeFindIndex({\n  rowCount,\n  columnCount,\n  getCellText,\n  maxCellsPerChunk = DEFAULT_MAX_CELLS_PER_CHUNK,\n  maxIndexedCells = DEFAULT_MAX_INDEXED_CELLS,\n}: {\n  rowCount: number;\n  columnCount: number;\n  getCellText: (rowIndex: number, columnIndex: number) => string;\n  maxCellsPerChunk?: number;\n  maxIndexedCells?: number;\n}): FixedGridNativeFindBuild | null {\n  const safeRowCount = normalizeNativeFindCount(rowCount);\n  const safeColumnCount = normalizeNativeFindCount(columnCount);\n  if (safeRowCount === 0 || safeColumnCount === 0) {\n    return { chunks: [], indexedCellCount: 0 };\n  }\n\n  const indexedCellCount = safeRowCount * safeColumnCount;\n  if (!isNativeFindIndexedCellCountAllowed(indexedCellCount, maxIndexedCells)) {\n    return null;\n  }\n\n  const rowsPerChunk = nativeFindRowsPerChunk({\n    columnCount: safeColumnCount,\n    maxCellsPerChunk,\n  });\n  const chunks: FixedGridNativeFindChunk[] = [];\n  for (\n    let startRowIndex = 0;\n    startRowIndex < safeRowCount;\n    startRowIndex += rowsPerChunk\n  ) {\n    const endRowIndex = Math.min(safeRowCount, startRowIndex + rowsPerChunk);\n    chunks.push({\n      startRowIndex,\n      endRowIndex,\n      text: fixedGridNativeFindChunkText({\n        startRowIndex,\n        endRowIndex,\n        columnCount: safeColumnCount,\n        getCellText,\n      }),\n    });\n  }\n\n  return { chunks, indexedCellCount };\n}\n\nfunction canBuildFixedGridNativeFindIndex({\n  rowCount,\n  columnCount,\n  maxIndexedCells,\n}: {\n  rowCount: number;\n  columnCount: number;\n  maxIndexedCells: number;\n}) {\n  const safeRowCount = normalizeNativeFindCount(rowCount);\n  const safeColumnCount = normalizeNativeFindCount(columnCount);\n  if (safeRowCount === 0 || safeColumnCount === 0) return false;\n  return isNativeFindIndexedCellCountAllowed(\n    safeRowCount * safeColumnCount,\n    maxIndexedCells,\n  );\n}\n\nfunction isNativeFindIndexedCellCountAllowed(\n  indexedCellCount: number,\n  maxIndexedCells: number,\n) {\n  return (\n    Number.isSafeInteger(indexedCellCount) &&\n    indexedCellCount <= normalizeNativeFindCellLimit(maxIndexedCells)\n  );\n}\n\nfunction fixedGridNativeFindChunkText({\n  startRowIndex,\n  endRowIndex,\n  columnCount,\n  getCellText,\n}: {\n  startRowIndex: number;\n  endRowIndex: number;\n  columnCount: number;\n  getCellText: (rowIndex: number, columnIndex: number) => string;\n}) {\n  const parts: string[] = [];\n  for (let rowIndex = startRowIndex; rowIndex < endRowIndex; rowIndex += 1) {\n    for (let columnIndex = 0; columnIndex < columnCount; columnIndex += 1) {\n      if (parts.length > 0) parts.push(columnIndex === 0 ? \"\\n\" : \"\\t\");\n      parts.push(getCellText(rowIndex, columnIndex));\n    }\n  }\n  return parts.join(\"\");\n}\n\nfunction resolveFixedGridNativeFindCell({\n  chunk,\n  columnCount,\n  getCellText,\n  offset,\n}: {\n  chunk: FixedGridNativeFindChunk;\n  columnCount: number;\n  getCellText: (rowIndex: number, columnIndex: number) => string;\n  offset: number | null;\n}): FixedGridNativeFindCellAddress {\n  if (offset == null || offset < 0) {\n    return { rowIndex: chunk.startRowIndex, columnIndex: 0 };\n  }\n\n  let cursor = 0;\n  for (\n    let rowIndex = chunk.startRowIndex;\n    rowIndex < chunk.endRowIndex;\n    rowIndex += 1\n  ) {\n    for (let columnIndex = 0; columnIndex < columnCount; columnIndex += 1) {\n      if (cursor > 0) cursor += 1;\n      const text = getCellText(rowIndex, columnIndex);\n      const cellStart = cursor;\n      const cellEnd = cellStart + text.length;\n      if (text.length > 0 && offset >= cellStart && offset <= cellEnd) {\n        return { rowIndex, columnIndex };\n      }\n      cursor = cellEnd;\n    }\n  }\n\n  return { rowIndex: chunk.startRowIndex, columnIndex: 0 };\n}\n\nfunction selectedTextOffsetIn(element: HTMLElement): number | null {\n  const selection =\n    element.ownerDocument.getSelection?.() ??\n    (typeof window === \"undefined\" ? null : window.getSelection());\n  if (!selection || selection.rangeCount === 0) return null;\n\n  const selectedRange = selection.getRangeAt(0);\n  if (!element.contains(selectedRange.startContainer)) return null;\n\n  const prefixRange = element.ownerDocument.createRange();\n  try {\n    prefixRange.selectNodeContents(element);\n    prefixRange.setEnd(selectedRange.startContainer, selectedRange.startOffset);\n    return prefixRange.toString().length;\n  } finally {\n    prefixRange.detach();\n  }\n}\n\nfunction nativeFindRowsPerChunk({\n  columnCount,\n  maxCellsPerChunk,\n}: {\n  columnCount: number;\n  maxCellsPerChunk: number;\n}) {\n  const safeCellLimit = Math.max(\n    1,\n    normalizeNativeFindCellLimit(maxCellsPerChunk),\n  );\n  return Math.max(1, Math.min(128, Math.floor(safeCellLimit / columnCount)));\n}\n\nfunction normalizeNativeFindCount(value: number) {\n  return Number.isSafeInteger(value) && value > 0 ? Math.floor(value) : 0;\n}\n\nfunction normalizeNativeFindCellLimit(value: number) {\n  return Number.isSafeInteger(value) && value > 0 ? Math.floor(value) : 0;\n}\n",
      "type": "registry:ui",
      "target": "@ui/fixed-grid-native-find-index.tsx"
    }
  ],
  "type": "registry:ui"
}